From bf778a5a4d6c07dbd90a37d7057fdd43cdeba025 Mon Sep 17 00:00:00 2001 From: Hermann_Kitio Date: Sat, 18 Apr 2026 02:50:34 +0300 Subject: [PATCH] =?UTF-8?q?feat(dashboard):=20PaywallBanner=20+=20Dashboar?= =?UTF-8?q?dPage=20conditionnel=20(Sprint=201=20=C3=A9tape=205)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/router.tsx | 14 +- .../dashboard/components/PaywallBanner.tsx | 25 ++++ .../dashboard/pages/DashboardPage.tsx | 130 ++++++++++++++++++ 3 files changed, 157 insertions(+), 12 deletions(-) create mode 100644 src/features/dashboard/components/PaywallBanner.tsx create mode 100644 src/features/dashboard/pages/DashboardPage.tsx diff --git a/src/app/router.tsx b/src/app/router.tsx index c2882d2..2654f7f 100644 --- a/src/app/router.tsx +++ b/src/app/router.tsx @@ -4,22 +4,12 @@ import { Navigate, Routes, Route } from 'react-router-dom' import { LoginPage } from '@/features/auth/pages/LoginPage' import { RegisterPage } from '@/features/auth/pages/RegisterPage' import { ProtectedRoute } from '@/features/auth/components/ProtectedRoute' +import { DashboardPage } from '@/features/dashboard/pages/DashboardPage' const DesignSystemPage = import.meta.env.DEV ? React.lazy(() => import('@/features/design-system/DesignSystemPage')) : () => null -function DashboardStub() { - return ( -
-

Dashboard — stub

-

- Cette vue sera remplacée par DashboardPage au prochain lot du Sprint 1. -

-
- ) -} - export function AppRouter() { return ( @@ -30,7 +20,7 @@ export function AppRouter() { path="/dashboard" element={ - + } /> diff --git a/src/features/dashboard/components/PaywallBanner.tsx b/src/features/dashboard/components/PaywallBanner.tsx new file mode 100644 index 0000000..10acfd7 --- /dev/null +++ b/src/features/dashboard/components/PaywallBanner.tsx @@ -0,0 +1,25 @@ +/** + * Bannière inline affichée sur le dashboard pour les utilisateurs Free. + * Présente les features débloquées par Standard et oriente vers /pricing. + * Pas de modale — intégrée dans le flux de la page (cf. PARCOURS_UTILISATEURS §2). + */ + +import { Link } from 'react-router-dom' +import { Button } from '@/shared/components/ui/button' + +export function PaywallBanner() { + return ( +
+

Passez à Standard pour débloquer :

+
    +
  • Simulations illimitées
  • +
  • Rapport détaillé par critère
  • +
  • Historique de vos productions
  • +
  • Suivi de progression
  • +
+ +
+ ) +} diff --git a/src/features/dashboard/pages/DashboardPage.tsx b/src/features/dashboard/pages/DashboardPage.tsx new file mode 100644 index 0000000..5923240 --- /dev/null +++ b/src/features/dashboard/pages/DashboardPage.tsx @@ -0,0 +1,130 @@ +/** + * Page dashboard — affichage conditionnel selon le plan utilisateur. + * + * Toute logique de permission passe par hasAccess() et canSimulate() + * (Règles D et H — jamais de if plan === '...'). + */ + +import { useQueryClient } from '@tanstack/react-query' +import { Logo } from '@/shared/components/Logo' +import { ThemeToggle } from '@/shared/components/ThemeToggle' +import { Button } from '@/shared/components/ui/button' +import { Badge } from '@/shared/components/ui/badge' +import { hasAccess, canSimulate } from '@/entities/user/lib' +import type { Plan } from '@/entities/user/types' +import { useAuth } from '@/features/auth/hooks/useAuth' +import { usePlan } from '../hooks/usePlan' +import { PaywallBanner } from '../components/PaywallBanner' +import { PLAN_QUERY_KEY } from '../hooks/usePlan' + +const PLAN_LABELS: Record = { + free: 'Plan Découverte', + standard: 'Plan Standard', + premium: 'Plan Premium', +} + +function getDisplayName(user: { user_metadata?: { full_name?: string }; email?: string } | null): string { + const fullName = user?.user_metadata?.full_name + if (fullName) return fullName.split(' ')[0] + const email = user?.email + if (email) return email.split('@')[0] + return 'vous' +} + +function DashboardSkeleton() { + return ( +
+
+
+
+
+
+
+
+
+ ) +} + +export function DashboardPage() { + const { user } = useAuth() + const { data, isLoading, isError } = usePlan() + const queryClient = useQueryClient() + + const displayName = getDisplayName(user) + + return ( +
+
+ + +
+ +
+ {isLoading && } + + {isError && ( +
+

+ Impossible de charger votre tableau de bord. Réessayez dans quelques instants. +

+ +
+ )} + + {data && ( +
+ {/* Salutation */} +
+

+ Bonjour, {displayName} +

+ {PLAN_LABELS[data.plan]} +
+ + {/* Bannière upgrade — plan Free uniquement */} + {!hasAccess(data.plan, 'dashboard') && } + + {/* Métriques */} +
+
+

Simulations restantes

+

+ {data.simulations_remaining === null + ? 'Illimitées' + : data.simulations_remaining} +

+
+
+

Niveau NCLC estimé

+

+
+
+ + {/* CTA Nouvelle simulation */} + + + {/* Dernières simulations */} +
+

Dernières simulations

+

Aucune simulation pour l'instant.

+
+
+ )} +
+
+ ) +}