-
- {step.step}
+ {/* Steps grid with animated connecting line */}
+
+ {/* Animated progress line — desktop only */}
+
+
+
+
+
+ {[
+ {
+ step: '01',
+ title: 'Recherchez',
+ description: "Entrez vos ports de départ et d'arrivée",
+ icon: Search,
+ },
+ {
+ step: '02',
+ title: 'Comparez',
+ description: 'Analysez les tarifs de 50+ compagnies',
+ icon: BarChart3,
+ },
+ {
+ step: '03',
+ title: 'Réservez',
+ description: 'Confirmez votre réservation en un clic',
+ icon: CheckCircle2,
+ },
+ {
+ step: '04',
+ title: 'Suivez',
+ description: 'Suivez votre envoi en temps réel',
+ icon: Container,
+ },
+ ].map((step, index) => {
+ const IconComponent = step.icon;
+ return (
+
+ {/* Clean number circle — no icon overlay */}
+
-
-
+ {/* Icon badge — separate from the number */}
+
- {index < 3 && (
-
- )}
-
- {step.title}
- {step.description}
-
- );
- })}
+
{step.title}
+
{step.description}
+
+ );
+ })}
+
+
+ {/* CTA after last step */}
+
+
+ Essayer maintenant
+
+
+
+ Inscription gratuite · Aucune carte bancaire requise
+
+
@@ -831,6 +947,8 @@ export default function LandingPage() {
{isAuthenticated && user ? (
Accéder au tableau de bord
@@ -840,6 +958,8 @@ export default function LandingPage() {
<>
Créer un compte gratuit
@@ -847,6 +967,8 @@ export default function LandingPage() {
Se connecter
diff --git a/apps/frontend/middleware.ts b/apps/frontend/middleware.ts
index 62a59fd..39e5660 100644
--- a/apps/frontend/middleware.ts
+++ b/apps/frontend/middleware.ts
@@ -7,37 +7,46 @@
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
-const publicPaths = [
- '/',
+// Exact-match public paths (no sub-path matching)
+const exactPublicPaths = ['/'];
+
+// Prefix-match public paths (plus their sub-paths)
+const prefixPublicPaths = [
'/login',
'/register',
'/forgot-password',
'/reset-password',
'/verify-email',
+ '/about',
+ '/careers',
+ '/blog',
+ '/press',
+ '/contact',
+ '/carrier',
];
export function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
// Check if path is public
- const isPublicPath = publicPaths.some(path => pathname.startsWith(path));
+ const isPublicPath =
+ exactPublicPaths.includes(pathname) ||
+ prefixPublicPaths.some(path => pathname === path || pathname.startsWith(path + '/'));
- // Get token from cookies or headers
+ // Get token from cookie (synced by client.ts setAuthTokens)
const token = request.cookies.get('accessToken')?.value;
// Redirect to login if accessing protected route without token
if (!isPublicPath && !token) {
- return NextResponse.redirect(new URL('/login', request.url));
- }
-
- // Redirect to dashboard if accessing public auth pages while logged in
- if (isPublicPath && token && pathname !== '/') {
- return NextResponse.redirect(new URL('/dashboard', request.url));
+ const loginUrl = new URL('/login', request.url);
+ loginUrl.searchParams.set('redirect', pathname);
+ return NextResponse.redirect(loginUrl);
}
return NextResponse.next();
}
export const config = {
- matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
+ // Exclude Next.js internals, API routes, and all public static assets
+ matcher: ['/((?!_next/static|_next/image|api|assets|favicon\\.ico|manifest\\.json|.*\\.(?:png|jpg|jpeg|gif|webp|svg|ico|mp4|mp3|pdf|txt|xml|csv|json)$).*)'],
};
diff --git a/apps/frontend/src/components/layout/LandingHeader.tsx b/apps/frontend/src/components/layout/LandingHeader.tsx
index ba44ebf..174db90 100644
--- a/apps/frontend/src/components/layout/LandingHeader.tsx
+++ b/apps/frontend/src/components/layout/LandingHeader.tsx
@@ -8,7 +8,6 @@ import {
ChevronDown,
Briefcase,
Newspaper,
- Mail,
Info,
BookOpen,
LayoutDashboard,
@@ -27,12 +26,15 @@ export function LandingHeader({ transparentOnTop = false, activePage }: LandingH
const companyMenuItems = [
{ href: '/about', label: 'À propos', icon: Info, description: 'Notre histoire et mission' },
- { href: '/contact', label: 'Contact', icon: Mail, description: 'Nous contacter' },
{ href: '/careers', label: 'Carrières', icon: Briefcase, description: 'Rejoignez-nous' },
{ href: '/blog', label: 'Blog', icon: BookOpen, description: 'Actualités et insights' },
{ href: '/press', label: 'Presse', icon: Newspaper, description: 'Espace presse' },
];
+ // "Entreprise" dropdown is active only for its own sub-pages (not contact)
+ const isCompanyMenuActive =
+ activePage !== undefined && ['about', 'careers', 'blog', 'press'].includes(activePage);
+
const getUserInitials = () => {
if (!user) return '';
const firstInitial = user.firstName?.charAt(0)?.toUpperCase() || '';
@@ -106,6 +108,18 @@ export function LandingHeader({ transparentOnTop = false, activePage }: LandingH
Tarifs
+ {/* Contact — lien direct dans la nav principale */}
+
+ Contact
+
+
{/* Menu Entreprise */}