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

@ -1,20 +1,20 @@
/**
* Layout applicatif enveloppe toutes les routes privées.
*
* Desktop ( 1024px) : Sidebar fixe 240px + zone contenu principale.
* Mobile (< 1024px) : MobileHeader sticky + drawer slide-in + BottomNav fixe.
* Desktop ( 1024px) : Sidebar fixe 230px + Topbar sticky + zone contenu.
* Mobile (< 1024px) : Topbar avec hamburger + drawer slide-in + BottomNav fixe.
*
* Le drawer mobile se ferme automatiquement à chaque changement de route
* (useEffect sur location.pathname).
*
* Règle L : tokens Direction H exclusivement.
* Règle L : tokens du design system exclusivement.
* Règle H : aucune logique métier plan lu depuis le cache TanStack Query.
*/
import { useState, useEffect } from 'react'
import { useLocation } from 'react-router-dom'
import { Sidebar } from './Sidebar'
import { MobileHeader } from './MobileHeader'
import { Topbar } from './Topbar'
import { BottomNav } from './BottomNav'
import { usePlan } from '@/features/dashboard/hooks/usePlan'
import { cn } from '@/shared/lib/utils'
@ -31,8 +31,6 @@ export function AppLayout({ children }: AppLayoutProps) {
const plan: Plan = data?.plan ?? 'free'
// Ferme le drawer à chaque changement de route.
// Synchronisation UI → router state : pattern légitime (source externe = React
// Router). Bail-out React si déjà fermé = zéro cascading render en pratique.
useEffect(() => {
// eslint-disable-next-line react-hooks/set-state-in-effect
setIsMobileMenuOpen(false)
@ -51,9 +49,6 @@ export function AppLayout({ children }: AppLayoutProps) {
<Sidebar plan={plan} />
</aside>
{/* ── MOBILE — Header sticky ─────────────────────────────────── */}
<MobileHeader onMenuOpen={() => setIsMobileMenuOpen(true)} />
{/* ── MOBILE — Drawer overlay ────────────────────────────────── */}
<div
aria-hidden="true"
@ -81,6 +76,7 @@ export function AppLayout({ children }: AppLayoutProps) {
className="min-h-screen pb-16 lg:pb-0 lg:pl-[230px]"
style={{ background: mainBackground }}
>
<Topbar onMobileMenuOpen={() => setIsMobileMenuOpen(true)} />
<div className="mx-auto max-w-[1100px] px-5 py-6 lg:px-9 lg:py-9">{children}</div>
</main>