feat(billing): add useBillingStore and /billing/state integration

T32: Single frontend source of truth for subscription / plan / feature
state. New Zustand `useBillingStore` fetches `/billing/state` (auto-fetch
on login via authStore, reset on logout), exposes `refetch` for
post-Checkout refresh, and is supported by a `useBillingPoll` hook
that re-fetches every 60s while authenticated. The new `billingApi`
client transforms the snake_case backend payload to camelCase at a
single boundary so the rest of the frontend never sees `plan_billing`
or `enabled_features`.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-06 20:44:20 -04:00
parent 80baf89b00
commit 7a9cb4b03b
9 changed files with 330 additions and 0 deletions

View File

@@ -0,0 +1,27 @@
import apiClient from './client'
import type { BillingStateApiResponse, BillingStatePayload } from '@/types'
/**
* Single boundary where the snake_case backend payload is transformed
* into the camelCase shape used by the rest of the frontend.
*
* Keeping the transform here means the store, hooks, and components
* never see snake_case keys.
*/
function transformBillingState(raw: BillingStateApiResponse): BillingStatePayload {
return {
subscription: raw.subscription ?? null,
planBilling: raw.plan_billing ?? null,
planLimits: raw.plan_limits ?? {},
enabledFeatures: raw.enabled_features ?? {},
}
}
export const billingApi = {
async getState(): Promise<BillingStatePayload> {
const response = await apiClient.get<BillingStateApiResponse>('/billing/state')
return transformBillingState(response.data)
},
}
export default billingApi