From 982c89395216fa16fc5427c0f07c9028f03b957a Mon Sep 17 00:00:00 2001 From: David Date: Thu, 9 Apr 2026 17:54:48 +0200 Subject: [PATCH] fix mobile version --- .../app/dashboard/admin/documents/page.tsx | 14 +- .../app/dashboard/admin/logs/page.tsx | 66 ++++--- .../dashboard/admin/organizations/page.tsx | 28 ++- .../app/dashboard/admin/users/page.tsx | 28 ++- apps/frontend/app/dashboard/bookings/page.tsx | 135 ++++++++++----- .../frontend/app/dashboard/documents/page.tsx | 87 +++++----- apps/frontend/app/dashboard/layout.tsx | 43 ++++- apps/frontend/app/dashboard/page.tsx | 16 +- .../app/dashboard/settings/api-keys/page.tsx | 32 ++-- .../app/dashboard/settings/users/page.tsx | 78 +++++---- apps/frontend/app/page.tsx | 74 ++++---- .../frontend/src/components/CookieConsent.tsx | 11 +- .../src/components/layout/LandingHeader.tsx | 162 +++++++++++++++++- .../frontend/src/components/ui/PageHeader.tsx | 30 ++++ 14 files changed, 531 insertions(+), 273 deletions(-) create mode 100644 apps/frontend/src/components/ui/PageHeader.tsx diff --git a/apps/frontend/app/dashboard/admin/documents/page.tsx b/apps/frontend/app/dashboard/admin/documents/page.tsx index e8cc81b..3bbeb15 100644 --- a/apps/frontend/app/dashboard/admin/documents/page.tsx +++ b/apps/frontend/app/dashboard/admin/documents/page.tsx @@ -4,6 +4,7 @@ import { useState, useEffect, useCallback } from 'react'; import { getAllBookings, getAllUsers, deleteAdminDocument } from '@/lib/api/admin'; import { FileText, Image as ImageIcon, FileEdit, FileSpreadsheet, Paperclip } from 'lucide-react'; import type { ReactNode } from 'react'; +import { PageHeader } from '@/components/ui/PageHeader'; interface Document { id: string; @@ -337,15 +338,10 @@ export default function AdminDocumentsPage() { return (
- {/* Header */} -
-
-

Gestion des Documents

-

- Liste de tous les documents des devis CSV -

-
-
+ {/* Stats */}
diff --git a/apps/frontend/app/dashboard/admin/logs/page.tsx b/apps/frontend/app/dashboard/admin/logs/page.tsx index b40fe25..2b5c458 100644 --- a/apps/frontend/app/dashboard/admin/logs/page.tsx +++ b/apps/frontend/app/dashboard/admin/logs/page.tsx @@ -12,6 +12,7 @@ import { Server, } from 'lucide-react'; import { get, download } from '@/lib/api/client'; +import { PageHeader } from '@/components/ui/PageHeader'; const LOGS_PREFIX = '/api/v1/logs'; @@ -189,48 +190,45 @@ export default function AdminLogsPage() { return (
- {/* Header */} -
-
-

Logs système

-

- Visualisation et export des logs applicatifs en temps réel -

-
-
- -
+ -
+
- +
+ + +
-
-
+ } + /> {/* Stats */}
diff --git a/apps/frontend/app/dashboard/admin/organizations/page.tsx b/apps/frontend/app/dashboard/admin/organizations/page.tsx index 485af68..1f37e93 100644 --- a/apps/frontend/app/dashboard/admin/organizations/page.tsx +++ b/apps/frontend/app/dashboard/admin/organizations/page.tsx @@ -3,6 +3,7 @@ import { useState, useEffect } from 'react'; import { getAllOrganizations, verifySiret, approveSiret, rejectSiret } from '@/lib/api/admin'; import { createOrganization, updateOrganization } from '@/lib/api/organizations'; +import { PageHeader } from '@/components/ui/PageHeader'; interface Organization { id: string; @@ -226,21 +227,18 @@ export default function AdminOrganizationsPage() { return (
- {/* Header */} -
-
-

Organization Management

-

- Manage all organizations in the system -

-
- -
+ setShowCreateModal(true)} + className="px-4 py-2 bg-blue-600 text-white text-sm font-medium rounded-lg hover:bg-blue-700 transition-colors" + > + + Create Organization + + } + /> {/* Error Message */} {error && ( diff --git a/apps/frontend/app/dashboard/admin/users/page.tsx b/apps/frontend/app/dashboard/admin/users/page.tsx index 9b68847..3569e26 100644 --- a/apps/frontend/app/dashboard/admin/users/page.tsx +++ b/apps/frontend/app/dashboard/admin/users/page.tsx @@ -5,6 +5,7 @@ import { getAllUsers, updateAdminUser, deleteAdminUser } from '@/lib/api/admin'; import { createUser } from '@/lib/api/users'; import { getAllOrganizations } from '@/lib/api/admin'; import type { UserRole } from '@/types/api'; +import { PageHeader } from '@/components/ui/PageHeader'; interface User { id: string; @@ -160,21 +161,18 @@ export default function AdminUsersPage() { return (
- {/* Header */} -
-
-

User Management

-

- Manage all users in the system -

-
- -
+ setShowCreateModal(true)} + className="px-4 py-2 bg-blue-600 text-white text-sm font-medium rounded-lg hover:bg-blue-700 transition-colors" + > + + Create User + + } + /> {/* Error Message */} {error && ( diff --git a/apps/frontend/app/dashboard/bookings/page.tsx b/apps/frontend/app/dashboard/bookings/page.tsx index 2f31216..ee5a801 100644 --- a/apps/frontend/app/dashboard/bookings/page.tsx +++ b/apps/frontend/app/dashboard/bookings/page.tsx @@ -13,6 +13,7 @@ import Link from 'next/link'; import { Plus, Clock } from 'lucide-react'; import ExportButton from '@/components/ExportButton'; import { useSearchParams } from 'next/navigation'; +import { PageHeader } from '@/components/ui/PageHeader'; type SearchType = 'pallets' | 'weight' | 'route' | 'status' | 'date' | 'quote'; @@ -166,44 +167,43 @@ export default function BookingsListPage() {
)} - {/* Header */} -
-
-

Réservations

-

Gérez et suivez vos envois

-
-
- `${v || 0}` }, - { key: 'weightKG', label: 'Poids (kg)', format: (v) => `${v || 0}` }, - { key: 'volumeCBM', label: 'Volume (CBM)', format: (v) => `${v || 0}` }, - { key: 'origin', label: 'Origine' }, - { key: 'destination', label: 'Destination' }, - { key: 'carrierName', label: 'Transporteur' }, - { key: 'status', label: 'Statut', format: (v) => { - const labels: Record = { - PENDING: 'En attente', - ACCEPTED: 'Accepté', - REJECTED: 'Refusé', - }; - return labels[v] || v; - }}, - { key: 'createdAt', label: 'Date de création', format: (v) => v ? new Date(v).toLocaleDateString('fr-FR') : '' }, - ]} - /> - - - Nouvelle Réservation - -
-
+ + `${v || 0}` }, + { key: 'weightKG', label: 'Poids (kg)', format: (v) => `${v || 0}` }, + { key: 'volumeCBM', label: 'Volume (CBM)', format: (v) => `${v || 0}` }, + { key: 'origin', label: 'Origine' }, + { key: 'destination', label: 'Destination' }, + { key: 'carrierName', label: 'Transporteur' }, + { key: 'status', label: 'Statut', format: (v) => { + const labels: Record = { + PENDING: 'En attente', + ACCEPTED: 'Accepté', + REJECTED: 'Refusé', + }; + return labels[v] || v; + }}, + { key: 'createdAt', label: 'Date de création', format: (v) => v ? new Date(v).toLocaleDateString('fr-FR') : '' }, + ]} + /> + + + Nouvelle Réservation + + + } + /> {/* Filters */}
@@ -284,7 +284,7 @@ export default function BookingsListPage() {
- {/* Bookings Table */} + {/* Bookings List */}
{isLoading ? (
@@ -293,7 +293,62 @@ export default function BookingsListPage() {
) : paginatedBookings && paginatedBookings.length > 0 ? ( <> -
+ {/* Mobile cards */} +
+ {paginatedBookings.map((booking: any) => ( +
+
+
+
+ {booking.type === 'csv' + ? `${booking.origin} → ${booking.destination}` + : booking.route || 'N/A'} +
+
+ {booking.type === 'csv' ? booking.carrierName : booking.carrier || ''} +
+
+ + {getStatusLabel(booking.status)} + +
+
+
+
Palettes
+
+ {booking.type === 'csv' + ? `${booking.palletCount} pal.` + : `${booking.containers?.length || 0} cont.`} +
+
+
+
Poids
+
+ {booking.type === 'csv' + ? `${booking.weightKG} kg` + : booking.totalWeight ? `${booking.totalWeight} kg` : 'N/A'} +
+
+
+
Date
+
+ {(booking.createdAt || booking.requestedAt) + ? new Date(booking.createdAt || booking.requestedAt).toLocaleDateString('fr-FR', { day: '2-digit', month: '2-digit', year: '2-digit' }) + : 'N/A'} +
+
+
+
+ {booking.type === 'csv' + ? `Réf: #${booking.bookingId || booking.id.slice(0, 8).toUpperCase()}` + : `Booking: ${booking.bookingNumber || '-'}`} +
+
+ ))} +
+ + {/* Desktop table */} +
diff --git a/apps/frontend/app/dashboard/documents/page.tsx b/apps/frontend/app/dashboard/documents/page.tsx index 49998e4..e7a5268 100644 --- a/apps/frontend/app/dashboard/documents/page.tsx +++ b/apps/frontend/app/dashboard/documents/page.tsx @@ -5,6 +5,7 @@ import { listCsvBookings, CsvBookingResponse } from '@/lib/api/bookings'; import { FileText, Image as ImageIcon, FileEdit, FileSpreadsheet, Paperclip } from 'lucide-react'; import type { ReactNode } from 'react'; import ExportButton from '@/components/ExportButton'; +import { PageHeader } from '@/components/ui/PageHeader'; interface Document { id: string; @@ -405,53 +406,45 @@ export default function UserDocumentsPage() { return (
- {/* Header */} -
-
-

Mes Documents

-

- Gérez tous les documents de vos réservations -

-
-
- { - const labels: Record = { - PENDING: 'En attente', - ACCEPTED: 'Accepté', - REJECTED: 'Refusé', - CANCELLED: 'Annulé', - }; - return labels[v] || v; - }}, - { key: 'uploadedAt', label: 'Date d\'ajout', format: (v) => v ? new Date(v).toLocaleDateString('fr-FR') : '' }, - ]} - /> - -
-
+ + { + const labels: Record = { + PENDING: 'En attente', + ACCEPTED: 'Accepté', + REJECTED: 'Refusé', + CANCELLED: 'Annulé', + }; + return labels[v] || v; + }}, + { key: 'uploadedAt', label: 'Date d\'ajout', format: (v) => v ? new Date(v).toLocaleDateString('fr-FR') : '' }, + ]} + /> + + + } + /> {/* Stats */}
diff --git a/apps/frontend/app/dashboard/layout.tsx b/apps/frontend/app/dashboard/layout.tsx index 1510854..a2f0a28 100644 --- a/apps/frontend/app/dashboard/layout.tsx +++ b/apps/frontend/app/dashboard/layout.tsx @@ -24,6 +24,8 @@ import { LogOut, Lock, Key, + Home, + User, } from 'lucide-react'; import { useSubscription } from '@/lib/context/subscription-context'; import StatusBadge from '@/components/ui/StatusBadge'; @@ -183,9 +185,9 @@ export default function DashboardLayout({ children }: { children: React.ReactNod {/* Main content */}
{/* Top bar */} -
+
-

+

{navigation.find(item => isActive(item.href))?.name || 'Tableau de bord'}

-
+
{/* Notifications */} {/* User Initials */} - + {user?.firstName?.[0]}{user?.lastName?.[0]}
- {/* Page content */} -
{children}
+ {/* Page content — extra bottom padding on mobile for bottom nav */} +
{children}
+ + {/* Mobile bottom navigation bar */} +
); } diff --git a/apps/frontend/app/dashboard/page.tsx b/apps/frontend/app/dashboard/page.tsx index 29deaad..71a5b77 100644 --- a/apps/frontend/app/dashboard/page.tsx +++ b/apps/frontend/app/dashboard/page.tsx @@ -82,17 +82,17 @@ export default function DashboardPage() { return (
-
+
{/* Header - Compact */} -
+
-

Tableau de Bord

-

+

Tableau de Bord

+

Vue d'ensemble de vos réservations et performances

-
+
-
diff --git a/apps/frontend/app/dashboard/settings/api-keys/page.tsx b/apps/frontend/app/dashboard/settings/api-keys/page.tsx index bfa95ca..3855294 100644 --- a/apps/frontend/app/dashboard/settings/api-keys/page.tsx +++ b/apps/frontend/app/dashboard/settings/api-keys/page.tsx @@ -17,6 +17,7 @@ import { ShieldCheck, Lock, } from 'lucide-react'; +import { PageHeader } from '@/components/ui/PageHeader'; // ─── Helpers ──────────────────────────────────────────────────────────────── @@ -376,23 +377,20 @@ export default function ApiKeysPage() { /> )} - {/* Page header */} -
-
-

Clés API

-

- Gérez les clés d'accès programmatique à l'API Xpeditis. -

-
- -
+ setShowCreateModal(true)} + disabled={activeKeys.length >= 20} + className="flex items-center gap-2 px-4 py-2.5 bg-[#10183A] hover:bg-[#1a2550] disabled:opacity-50 text-white text-sm font-medium rounded-xl transition-colors" + > + + Nouvelle clé + + } + /> {/* Info banner */}
diff --git a/apps/frontend/app/dashboard/settings/users/page.tsx b/apps/frontend/app/dashboard/settings/users/page.tsx index 2b10987..a7ab6fb 100644 --- a/apps/frontend/app/dashboard/settings/users/page.tsx +++ b/apps/frontend/app/dashboard/settings/users/page.tsx @@ -14,6 +14,7 @@ import { createInvitation, listInvitations, cancelInvitation } from '@/lib/api/i import { useAuth } from '@/lib/context/auth-context'; import Link from 'next/link'; import ExportButton from '@/components/ExportButton'; +import { PageHeader } from '@/components/ui/PageHeader'; const PAGE_SIZE = 5; @@ -279,44 +280,45 @@ export default function UsersManagementPage() {
)} - {/* Header */} -
-
-

Gestion des Utilisateurs

-

Gérez les membres de l'équipe et leurs permissions

-
-
- ({ ADMIN: 'Administrateur', MANAGER: 'Manager', USER: 'Utilisateur', VIEWER: 'Lecteur' }[v] || v) }, - { key: 'isActive', label: 'Statut', format: (v) => v ? 'Actif' : 'Inactif' }, - { key: 'createdAt', label: 'Date de création', format: (v) => v ? new Date(v).toLocaleDateString('fr-FR') : '' }, - ]} - /> - {licenseStatus?.canInvite ? ( - - ) : ( - - + - Mettre à niveau - - )} -
-
+ + ({ ADMIN: 'Administrateur', MANAGER: 'Manager', USER: 'Utilisateur', VIEWER: 'Lecteur' }[v] || v) }, + { key: 'isActive', label: 'Statut', format: (v) => v ? 'Actif' : 'Inactif' }, + { key: 'createdAt', label: 'Date de création', format: (v) => v ? new Date(v).toLocaleDateString('fr-FR') : '' }, + ]} + /> + {licenseStatus?.canInvite ? ( + + ) : ( + + + + Mettre à niveau + Upgrade + + )} + + } + /> {success && (
diff --git a/apps/frontend/app/page.tsx b/apps/frontend/app/page.tsx index f2b319f..f57c77d 100644 --- a/apps/frontend/app/page.tsx +++ b/apps/frontend/app/page.tsx @@ -346,7 +346,7 @@ export default function LandingPage() {
-
+
- - + + Plateforme B2B de Fret Maritime #1 en Europe @@ -369,7 +369,7 @@ export default function LandingPage() { initial={{ opacity: 0, y: 20 }} animate={isHeroInView ? { opacity: 1, y: 0 } : {}} transition={{ duration: 0.8, delay: 0.3 }} - className="text-5xl lg:text-7xl font-bold text-white mb-6 leading-tight" + className="text-3xl sm:text-5xl lg:text-7xl font-bold text-white mb-4 sm:mb-6 leading-tight" > Réservez votre fret
@@ -382,7 +382,7 @@ export default function LandingPage() { initial={{ opacity: 0, y: 20 }} animate={isHeroInView ? { opacity: 1, y: 0 } : {}} transition={{ duration: 0.8, delay: 0.4 }} - className="text-xl lg:text-2xl text-white/80 mb-12 max-w-3xl mx-auto leading-relaxed" + className="text-base sm:text-xl lg:text-2xl text-white/80 mb-8 sm:mb-12 max-w-3xl mx-auto leading-relaxed px-2" > Comparez les tarifs de 50+ compagnies maritimes, réservez en ligne et suivez vos envois en temps réel. @@ -392,14 +392,14 @@ export default function LandingPage() { initial={{ opacity: 0, y: 20 }} animate={isHeroInView ? { opacity: 1, y: 0 } : {}} transition={{ duration: 0.8, delay: 0.5 }} - className="flex flex-col sm:flex-row items-center justify-center space-y-4 sm:space-y-0 sm:space-x-6 mb-12" + className="flex flex-col sm:flex-row items-center justify-center gap-3 sm:gap-6 mb-12 px-4 sm:px-0" > {isAuthenticated && user ? ( Accéder au tableau de bord @@ -410,7 +410,7 @@ export default function LandingPage() { href="/register" target="_blank" rel="noopener noreferrer" - className="group px-8 py-4 bg-brand-turquoise text-white rounded-lg hover:bg-brand-turquoise/90 transition-all hover:shadow-2xl hover:scale-105 font-semibold text-lg w-full sm:w-auto flex items-center justify-center space-x-2" + className="group px-6 sm:px-8 py-3 sm:py-4 bg-brand-turquoise text-white rounded-lg hover:bg-brand-turquoise/90 transition-all hover:shadow-2xl hover:scale-105 font-semibold text-base sm:text-lg w-full sm:w-auto flex items-center justify-center space-x-2" > Créer un compte gratuit @@ -419,7 +419,7 @@ export default function LandingPage() { href="/login" target="_blank" rel="noopener noreferrer" - className="px-8 py-4 bg-white text-brand-navy rounded-lg hover:bg-gray-50 transition-all hover:shadow-xl font-semibold text-lg w-full sm:w-auto" + className="px-6 sm:px-8 py-3 sm:py-4 bg-white text-brand-navy rounded-lg hover:bg-gray-50 transition-all hover:shadow-xl font-semibold text-base sm:text-lg w-full sm:w-auto text-center" > Voir la démo @@ -452,12 +452,12 @@ export default function LandingPage() { {/* Stats Section */} -
+
{stats.map((stat, index) => { @@ -477,7 +477,7 @@ export default function LandingPage() { initial={{ scale: 0 }} animate={isStatsInView ? { scale: 1 } : {}} transition={{ duration: 0.5, delay: index * 0.1 }} - className="text-5xl lg:text-6xl font-bold text-brand-navy mb-2 tabular-nums" + className="text-3xl sm:text-5xl lg:text-6xl font-bold text-brand-navy mb-2 tabular-nums" > {/* Features Section */} -
-
+
+
-

+

Pourquoi choisir Xpeditis ?

-

+

Une plateforme complète pour gérer tous vos besoins en fret maritime

@@ -620,23 +620,23 @@ export default function LandingPage() {
-
+
{/* Header */} Tarifs -

+

Des plans adaptés à votre activité

-

+

De l'accès découverte au partenariat sur mesure — évoluez à tout moment.

@@ -959,19 +959,19 @@ export default function LandingPage() { {/* Testimonials Section */}
-
+
-

+

Ils nous font confiance

-

+

Découvrez les témoignages de nos clients satisfaits

@@ -1014,18 +1014,18 @@ export default function LandingPage() {
{/* CTA Section */} -
+
-

+

Prêt à simplifier votre fret maritime ?

-

+

Rejoignez des centaines de transitaires qui font confiance à Xpeditis pour leurs expéditions maritimes.

@@ -1033,14 +1033,14 @@ export default function LandingPage() { {isAuthenticated && user ? ( Accéder au tableau de bord @@ -1051,7 +1051,7 @@ export default function LandingPage() { href="/register" target="_blank" rel="noopener noreferrer" - className="group px-8 py-4 bg-brand-turquoise text-white rounded-lg hover:bg-brand-turquoise/90 transition-all hover:shadow-2xl hover:scale-105 font-semibold text-lg w-full sm:w-auto flex items-center justify-center space-x-2" + className="group px-6 sm:px-8 py-3 sm:py-4 bg-brand-turquoise text-white rounded-lg hover:bg-brand-turquoise/90 transition-all hover:shadow-2xl hover:scale-105 font-semibold text-base sm:text-lg w-full sm:w-auto flex items-center justify-center space-x-2" > Créer un compte gratuit @@ -1060,7 +1060,7 @@ export default function LandingPage() { href="/login" target="_blank" rel="noopener noreferrer" - className="px-8 py-4 bg-brand-navy text-white rounded-lg hover:bg-brand-navy/90 transition-all hover:shadow-xl font-semibold text-lg w-full sm:w-auto" + className="px-6 sm:px-8 py-3 sm:py-4 bg-brand-navy text-white rounded-lg hover:bg-brand-navy/90 transition-all hover:shadow-xl font-semibold text-base sm:text-lg w-full sm:w-auto text-center" > Se connecter @@ -1070,7 +1070,7 @@ export default function LandingPage() {
diff --git a/apps/frontend/src/components/CookieConsent.tsx b/apps/frontend/src/components/CookieConsent.tsx index 0050b1d..76af003 100644 --- a/apps/frontend/src/components/CookieConsent.tsx +++ b/apps/frontend/src/components/CookieConsent.tsx @@ -11,6 +11,7 @@ import { motion, AnimatePresence } from 'framer-motion'; import { Cookie, X, Settings, Check, Shield } from 'lucide-react'; import { useCookieConsent } from '@/lib/context/cookie-context'; import type { CookiePreferences } from '@/lib/api/gdpr'; +import { usePathname } from 'next/navigation'; export default function CookieConsent() { const { @@ -27,6 +28,12 @@ export default function CookieConsent() { } = useCookieConsent(); const [localPrefs, setLocalPrefs] = useState(preferences); + const pathname = usePathname(); + // On dashboard pages, mobile has a bottom nav bar (h-16 = 64px). Offset to avoid overlap. + const isDashboard = pathname?.startsWith('/dashboard'); + // Classes to apply only on mobile when on the dashboard + const mobileOffset = isDashboard ? 'bottom-16 lg:bottom-0' : 'bottom-0'; + const mobileButtonOffset = isDashboard ? 'bottom-20 lg:bottom-4' : 'bottom-4'; // Sync local prefs when context changes React.useEffect(() => { @@ -53,7 +60,7 @@ export default function CookieConsent() { exit={{ scale: 0, opacity: 0 }} transition={{ type: 'spring', stiffness: 260, damping: 20 }} onClick={openPreferences} - className="fixed bottom-4 left-4 z-40 p-3 bg-brand-navy text-white rounded-full shadow-lg hover:bg-brand-navy/90 focus:outline-none focus:ring-2 focus:ring-brand-turquoise focus:ring-offset-2 transition-colors" + className={`fixed left-4 z-40 p-3 bg-brand-navy text-white rounded-full shadow-lg hover:bg-brand-navy/90 focus:outline-none focus:ring-2 focus:ring-brand-turquoise focus:ring-offset-2 transition-colors ${mobileButtonOffset}`} aria-label="Ouvrir les paramètres de cookies" > @@ -69,7 +76,7 @@ export default function CookieConsent() { animate={{ y: 0, opacity: 1 }} exit={{ y: 100, opacity: 0 }} transition={{ type: 'spring', stiffness: 300, damping: 30 }} - className="fixed bottom-0 left-0 right-0 z-50 bg-white border-t border-gray-200 shadow-2xl" + className={`fixed left-0 right-0 z-50 bg-white border-t border-gray-200 shadow-2xl ${mobileOffset}`} >
diff --git a/apps/frontend/src/components/layout/LandingHeader.tsx b/apps/frontend/src/components/layout/LandingHeader.tsx index e225a22..4e9ca88 100644 --- a/apps/frontend/src/components/layout/LandingHeader.tsx +++ b/apps/frontend/src/components/layout/LandingHeader.tsx @@ -10,8 +10,11 @@ import { BookOpen, LayoutDashboard, Code2, + Menu, + X, } from 'lucide-react'; import { useAuth } from '@/lib/context/auth-context'; +import { usePathname } from 'next/navigation'; interface LandingHeaderProps { transparentOnTop?: boolean; @@ -21,7 +24,14 @@ interface LandingHeaderProps { export function LandingHeader({ transparentOnTop = false, activePage }: LandingHeaderProps) { const [isScrolled, setIsScrolled] = useState(false); const [isCompanyMenuOpen, setIsCompanyMenuOpen] = useState(false); + const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false); const { user, isAuthenticated, loading } = useAuth(); + const pathname = usePathname(); + + // Close mobile menu on route change + useEffect(() => { + setIsMobileMenuOpen(false); + }, [pathname]); const companyMenuItems = [ { href: '/about', label: 'À propos', icon: Info, description: 'Notre histoire et mission' }, @@ -66,6 +76,7 @@ export function LandingHeader({ transparentOnTop = false, activePage }: LandingH }; return ( + <> -
-
+
+
+ + {/* Mobile hamburger button */} + +
+ + {/* Mobile menu drawer */} + + {isMobileMenuOpen && ( + <> + {/* Backdrop */} + setIsMobileMenuOpen(false)} + /> + {/* Drawer */} + + {/* Drawer header */} +
+ + +
+ + {/* Nav links */} + + + {/* Auth section */} +
+ {loading ? ( +
+ ) : isAuthenticated && user ? ( + +
+ {getUserInitials()} +
+ {getFullName()} + + + ) : ( + <> + + Connexion + + + Commencer Gratuitement + + + )} +
+ + + )} + + ); } diff --git a/apps/frontend/src/components/ui/PageHeader.tsx b/apps/frontend/src/components/ui/PageHeader.tsx new file mode 100644 index 0000000..2a566f1 --- /dev/null +++ b/apps/frontend/src/components/ui/PageHeader.tsx @@ -0,0 +1,30 @@ +import { ReactNode } from 'react'; + +interface PageHeaderProps { + title: string; + description?: string; + actions?: ReactNode; +} + +/** + * Consistent page header for dashboard pages. + * Mobile: actions appear above title (right-aligned). + * Desktop: title on the left, actions on the right. + */ +export function PageHeader({ title, description, actions }: PageHeaderProps) { + return ( +
+ {actions && ( +
+ {actions} +
+ )} +
+

{title}

+ {description && ( +

{description}

+ )} +
+
+ ); +}