From 107a37d197d9a6f3431c6a7d78bdadab7558e0fd Mon Sep 17 00:00:00 2001 From: Hermann_Kitio Date: Sat, 18 Apr 2026 02:00:12 +0300 Subject: [PATCH] =?UTF-8?q?feat(entities/user):=20PlanStatus=20+=20getPlan?= =?UTF-8?q?Status=20+=20hook=20usePlan=20(Sprint=201=20=C3=A9tape=201)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fondations data plan utilisateur pour le dashboard conditionnel : - entities/user/types.ts : interface PlanStatus (plan, permissions, simulations_used/remaining, plan_expires_at) - entities/user/api.ts : getPlanStatus() via apiFetch('/plans/status') - features/dashboard/hooks/usePlan.ts : useQuery + PLAN_QUERY_KEY + staleTime 5 min Co-Authored-By: Claude Opus 4.7 (1M context) --- src/entities/user/api.ts | 22 +++++++++++++++++ src/entities/user/types.ts | 32 +++++++++++++++++++++++++ src/features/dashboard/hooks/usePlan.ts | 24 +++++++++++++++++++ 3 files changed, 78 insertions(+) create mode 100644 src/entities/user/api.ts create mode 100644 src/entities/user/types.ts create mode 100644 src/features/dashboard/hooks/usePlan.ts diff --git a/src/entities/user/api.ts b/src/entities/user/api.ts new file mode 100644 index 0000000..3866873 --- /dev/null +++ b/src/entities/user/api.ts @@ -0,0 +1,22 @@ +/** + * Appels API du domaine `user`. + * + * Toutes les requêtes passent par `apiFetch` (Règle J) qui gère auth, retry, + * timeout et erreurs typées. Les consommateurs consomment ces fonctions via + * TanStack Query (cf. `features/dashboard/hooks/usePlan`). + */ + +import { apiFetch } from '@/shared/lib/api-client' +import type { PlanStatus } from './types' + +/** + * Récupère le statut courant du plan de l'utilisateur connecté. + * + * Endpoint : `GET /plans/status` + * Auth : JWT Bearer requis (ajouté automatiquement par `apiFetch`). + * + * @throws ApiError — notamment `AUTH_REQUIRED` si le JWT est absent/expiré. + */ +export function getPlanStatus(): Promise { + return apiFetch('/plans/status') +} diff --git a/src/entities/user/types.ts b/src/entities/user/types.ts new file mode 100644 index 0000000..e86ae34 --- /dev/null +++ b/src/entities/user/types.ts @@ -0,0 +1,32 @@ +/** + * Types publics du domaine `user`. + * + * Porte d'entrée unique pour les consommateurs frontend : toute UI ou hook + * qui manipule un plan ou une permission importe depuis ce fichier (ou `./lib` + * pour les fonctions), jamais directement depuis `./access` (cf. ADR 005). + */ + +import type { Feature, Plan } from './access' + +/** + * Réponse du backend pour `GET /plans/status`. + * + * Format confirmé par l'audit backend 2026-04-17 (cf. ARCHITECTURE.md §5). + * + * - `permissions` : dictionnaire booléen par feature. Les consommateurs + * doivent passer par `hasAccess(plan, feature)` plutôt que lire ce champ + * directement (Règle D / ADR 005). Il est exposé ici uniquement parce que + * le backend le renvoie et qu'il peut servir à du debug côté DevTools. + * - `simulations_remaining` : `null` si le plan est illimité (standard/premium), + * sinon nombre de simulations restantes sur le quota à vie (Free : 5). + * - `plan_expires_at` : ISO 8601 pour un plan payant actif, `null` pour Free. + */ +export interface PlanStatus { + plan: Plan + permissions: Record + simulations_used: number + simulations_remaining: number | null + plan_expires_at: string | null +} + +export type { Feature, Plan } diff --git a/src/features/dashboard/hooks/usePlan.ts b/src/features/dashboard/hooks/usePlan.ts new file mode 100644 index 0000000..4bd5cee --- /dev/null +++ b/src/features/dashboard/hooks/usePlan.ts @@ -0,0 +1,24 @@ +/** + * Hook TanStack Query sur le statut du plan utilisateur. + * + * Source unique de vérité côté frontend pour `plan`, `permissions`, et + * compteurs de simulations. Consommé par `DashboardPage`, les gardes de + * permission dans les pages simulations/t2-live, et le router. + * + * `staleTime: 5 min` — le plan change peu (upgrade Stripe, expiration). Les + * flux d'upgrade appellent `queryClient.invalidateQueries(['plan'])` pour + * forcer un refetch immédiat après webhook. + */ + +import { useQuery } from '@tanstack/react-query' +import { getPlanStatus } from '@/entities/user/api' + +export const PLAN_QUERY_KEY = ['plan'] as const + +export function usePlan() { + return useQuery({ + queryKey: PLAN_QUERY_KEY, + queryFn: getPlanStatus, + staleTime: 5 * 60 * 1000, + }) +}