feat(billing): add useFeature, useFeatureLimit, useTrialBanner hooks

Phase 2 Task 33. Components can now ask "is this feature on?", "how many
sessions left?", and "what stage is the trial in?" without re-implementing
the read against useBillingStore.

- useFeature(flagKey): boolean — reads enabledFeatures from store
- useFeatureLimit(field): { used, limit, percentage, isAtLimit, isLoading }
  with non-blocking 60s module-level cache and graceful 404 degradation
- useTrialBanner(): derives stage from subscription status + trial countdown,
  returns null on initial load to prevent flicker
- usageApi.getCount(field) — calls /api/v1/usage/{field}; backend endpoint
  is not yet implemented (planned), so the hook degrades to used=0

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-06 20:52:18 -04:00
parent 7a9cb4b03b
commit 0b5ed9aa10
8 changed files with 538 additions and 0 deletions

23
frontend/src/api/usage.ts Normal file
View File

@@ -0,0 +1,23 @@
import apiClient from './client'
/**
* Usage counters API.
*
* TODO: backend `/usage/{field}` endpoint not yet implemented (planned).
* Tracked under self-serve signup Phase 2 — Task 33 calls this lazily; today
* it 404s and the consuming hook (`useFeatureLimit`) cleanly degrades to
* `used = 0`.
*/
export const usageApi = {
/**
* Fetch the current count for a usage field (e.g. `active_users`,
* `flowpilot_sessions_this_month`). The field name is the same key used in
* `BillingState.planLimits`.
*/
async getCount(field: string): Promise<{ used: number }> {
const response = await apiClient.get<{ used: number }>(`/usage/${field}`)
return response.data
},
}
export default usageApi