feat(ui-polish): sidebar icons + topbar + dashboard redesign

- Sidebar: lucide-react icons, lock on gated items, upgrade badge on "Mon plan", user footer with avatar initials + plan label, "EX|PRIA" logo header
- Topbar: sticky with backdrop-blur, breadcrumb via centralized route-titles.ts, search placeholder, keyboard shortcuts + notifications icons
- Dashboard: split into Free/Standard/Premium views (ARCHITECTURE.md §3 aligned)
- NclcHero: NCLC display + gauge 5→10 + SVG score ring
- StatCards: simulations remaining + NCLC estimé + dernier score with delta
- RecentSimulations: 3 latest with NCLC badge + chevron nav
- NextStepCard: static recommendation per plan
- PaywallBanner: full-width redesign + fixed dead Boréal tokens
- Removed orphan MobileHeader.tsx (0 consumers)

Typecheck: OK · Tests: 122/122 

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Hermann_Kitio 2026-04-25 00:50:36 +03:00
parent b68f160bce
commit 4005673ae8
16 changed files with 1188 additions and 171 deletions

View file

@ -0,0 +1,111 @@
/**
* DashboardFreeView vue Dashboard pour le plan Découverte.
*
* Spécificités Free :
* - Pas d'appel `useSimulationsList` (gate 'dashboard' à false côté backend).
* - Hero NCLC en état placeholder (pas d'historique lisible).
* - Stat cards avec "NCLC estimé —" et "Dernier score —".
* - Recommandation statique vers la première simulation EE T2.
* - Bannière upsell Standard en bas.
*
* Règle D : aucun `plan === 'free'` c'est le parent (DashboardPage) qui
* route vers cette vue via hasAccess.
* Règle H : aucune logique métier les données viennent des props.
* Règle L : tokens du design system exclusivement.
*/
import { useNavigate } from 'react-router-dom'
import { Plus } from 'lucide-react'
import { Button } from '@/shared/ui/Button'
import { Badge } from '@/shared/ui/Badge'
import { NclcHero } from './NclcHero'
import { StatCards } from './StatCards'
import { NextStepCard } from './NextStepCard'
import { PaywallBanner } from './PaywallBanner'
interface DashboardFreeViewProps {
displayName: string
simulationsUsed: number
simulationsRemaining: number
canStartSimulation: boolean
}
const FREE_CONSEIL =
"Commencez par une simulation d'Expression Écrite pour découvrir votre niveau. " +
'Le rapport détaillé et le suivi NCLC se débloquent avec le plan Standard.'
export function DashboardFreeView({
displayName,
simulationsUsed,
simulationsRemaining,
canStartSimulation,
}: DashboardFreeViewProps) {
const navigate = useNavigate()
return (
<div className="space-y-6">
{/* Header */}
<header className="flex flex-wrap items-center justify-between gap-4">
<div className="flex flex-wrap items-center gap-3">
<h1 className="text-2xl font-bold text-ink-primary">Bonjour, {displayName}</h1>
<Badge variant="plan" planValue="free">
Plan Découverte
</Badge>
</div>
<div className="flex flex-wrap items-center gap-2">
<Button variant="secondary" size="sm" onClick={() => navigate('/plan')}>
Passer en Premium
</Button>
<Button
variant="primary"
size="sm"
icon={<Plus className="size-4" />}
disabled={!canStartSimulation}
onClick={() => navigate('/simulation/ee')}
>
Nouvelle simulation
</Button>
</div>
</header>
{/* Hero NCLC — placeholder en Free */}
<NclcHero currentNclc={null} conseil={FREE_CONSEIL} lastScore={null} />
{/* Stat cards — NCLC et dernier score vides */}
<StatCards
plan="free"
simulationsUsed={simulationsUsed}
simulationsRemaining={simulationsRemaining}
recentSimulations={[]}
/>
{/* Prochaine étape + (pas de simulations récentes en Free) */}
<div className="grid gap-4 lg:grid-cols-[1fr_360px]">
<section
aria-label="Premiers pas"
className="rounded-[var(--radius-md)] border border-border bg-surface p-6"
>
<p className="text-[11px] font-semibold uppercase tracking-wider text-ink-tertiary">
Pour bien démarrer
</p>
<h2 className="mt-2 text-lg font-semibold text-ink-primary">Votre première simulation</h2>
<p className="mt-2 text-sm text-ink-secondary">
Choisissez une tâche d'Expression Écrite pour obtenir un premier score et une estimation
NCLC. Vos 5 simulations gratuites vous attendent.
</p>
</section>
<NextStepCard
title="Démarrez par l'Écrit T2"
conseil="Article d'opinion — le format le plus représentatif du TCF Canada."
tags={['20 min', '120-150 mots']}
ctaLabel="Commencer"
ctaTo="/simulation/ee"
/>
</div>
{/* Bannière upsell */}
<PaywallBanner />
</div>
)
}