diff --git a/apps/frontend/app/about/page.tsx b/apps/frontend/app/about/page.tsx index caea7ca..b89b7c0 100644 --- a/apps/frontend/app/about/page.tsx +++ b/apps/frontend/app/about/page.tsx @@ -350,21 +350,30 @@ export default function AboutPage() {
- {/* Timeline line */} -
+ {/* Timeline vertical rail + animated fill */} +
+ +
{timeline.map((item, index) => (
-
-
+
+
{item.year}
@@ -372,9 +381,18 @@ export default function AboutPage() {

{item.description}

-
-
+ + {/* Animated center dot */} +
+
+
))} diff --git a/apps/frontend/app/dashboard/layout.tsx b/apps/frontend/app/dashboard/layout.tsx index b57a3c3..5bac1c5 100644 --- a/apps/frontend/app/dashboard/layout.tsx +++ b/apps/frontend/app/dashboard/layout.tsx @@ -8,8 +8,8 @@ import { useAuth } from '@/lib/context/auth-context'; import Link from 'next/link'; -import { usePathname } from 'next/navigation'; -import { useState } from 'react'; +import { usePathname, useRouter } from 'next/navigation'; +import { useState, useEffect } from 'react'; import NotificationDropdown from '@/components/NotificationDropdown'; import AdminPanelDropdown from '@/components/admin/AdminPanelDropdown'; import Image from 'next/image'; @@ -25,10 +25,29 @@ import { } from 'lucide-react'; export default function DashboardLayout({ children }: { children: React.ReactNode }) { - const { user, logout } = useAuth(); + const { user, logout, loading, isAuthenticated } = useAuth(); const pathname = usePathname(); + const router = useRouter(); const [sidebarOpen, setSidebarOpen] = useState(false); + useEffect(() => { + if (!loading && !isAuthenticated) { + router.replace(`/login?redirect=${encodeURIComponent(pathname)}`); + } + }, [loading, isAuthenticated, router, pathname]); + + if (loading) { + return ( +
+
+
+ ); + } + + if (!isAuthenticated) { + return null; + } + const navigation = [ { name: 'Tableau de bord', href: '/dashboard', icon: BarChart3 }, { name: 'Réservations', href: '/dashboard/bookings', icon: Package }, diff --git a/apps/frontend/app/login/page.tsx b/apps/frontend/app/login/page.tsx index 17ed57d..d06a37d 100644 --- a/apps/frontend/app/login/page.tsx +++ b/apps/frontend/app/login/page.tsx @@ -8,9 +8,10 @@ 'use client'; -import { useState } from 'react'; +import { useState, Suspense } from 'react'; import Link from 'next/link'; import Image from 'next/image'; +import { useSearchParams } from 'next/navigation'; import { useAuth } from '@/lib/context/auth-context'; interface FieldErrors { @@ -73,8 +74,10 @@ function getErrorMessage(error: any): { message: string; field?: 'email' | 'pass }; } -export default function LoginPage() { +function LoginPageContent() { const { login } = useAuth(); + const searchParams = useSearchParams(); + const redirectTo = searchParams.get('redirect') || '/dashboard'; const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [rememberMe, setRememberMe] = useState(false); @@ -126,7 +129,7 @@ export default function LoginPage() { setIsLoading(true); try { - await login(email, password); + await login(email, password, redirectTo); // Navigation is handled by the login function in auth context } catch (err: any) { const { message, field } = getErrorMessage(err); @@ -462,3 +465,11 @@ export default function LoginPage() {
); } + +export default function LoginPage() { + return ( + + + + ); +} diff --git a/apps/frontend/app/page.tsx b/apps/frontend/app/page.tsx index 7af29bf..be04465 100644 --- a/apps/frontend/app/page.tsx +++ b/apps/frontend/app/page.tsx @@ -1,6 +1,6 @@ 'use client'; -import { useRef } from 'react'; +import { useRef, useState, useEffect } from 'react'; import Link from 'next/link'; import Image from 'next/image'; import { motion, useInView, useScroll, useTransform } from 'framer-motion'; @@ -27,6 +27,43 @@ import { import { useAuth } from '@/lib/context/auth-context'; import { LandingHeader, LandingFooter } from '@/components/layout'; +function AnimatedCounter({ + end, + suffix = '', + prefix = '', + decimals = 0, + isActive, + duration = 2, +}: { + end: number; + suffix?: string; + prefix?: string; + decimals?: number; + isActive: boolean; + duration?: number; +}) { + const [count, setCount] = useState(0); + + useEffect(() => { + if (!isActive) return; + let startTime: number | undefined; + + const animate = (timestamp: number) => { + if (!startTime) startTime = timestamp; + const progress = Math.min((timestamp - startTime) / (duration * 1000), 1); + const eased = 1 - Math.pow(1 - progress, 3); + setCount(eased * end); + if (progress < 1) requestAnimationFrame(animate); + else setCount(end); + }; + + requestAnimationFrame(animate); + }, [end, duration, isActive]); + + const display = decimals > 0 ? count.toFixed(decimals) : Math.floor(count).toString(); + return <>{prefix}{display}{suffix}; +} + export default function LandingPage() { const { user, isAuthenticated } = useAuth(); @@ -37,14 +74,16 @@ export default function LandingPage() { const pricingRef = useRef(null); const testimonialsRef = useRef(null); const ctaRef = useRef(null); + const howRef = useRef(null); const isHeroInView = useInView(heroRef, { once: true }); const isFeaturesInView = useInView(featuresRef, { once: true }); - const isStatsInView = useInView(statsRef, { once: true }); + const isStatsInView = useInView(statsRef, { once: true, amount: 0.3 }); const isToolsInView = useInView(toolsRef, { once: true }); const isPricingInView = useInView(pricingRef, { once: true }); const isTestimonialsInView = useInView(testimonialsRef, { once: true }); const isCtaInView = useInView(ctaRef, { once: true }); + const isHowInView = useInView(howRef, { once: true, amount: 0.2 }); const { scrollYProgress } = useScroll(); const backgroundY = useTransform(scrollYProgress, [0, 1], ['0%', '50%']); @@ -138,10 +177,10 @@ export default function LandingPage() { ]; const stats = [ - { value: '50+', label: 'Compagnies Maritimes', icon: Ship }, - { value: '10K+', label: 'Ports Mondiaux', icon: Anchor }, - { value: '<2s', label: 'Temps de Réponse', icon: Zap }, - { value: '99.5%', label: 'Disponibilité', icon: CheckCircle2 }, + { end: 50, prefix: '', suffix: '+', decimals: 0, label: 'Compagnies Maritimes', icon: Ship }, + { end: 10, prefix: '', suffix: 'K+', decimals: 0, label: 'Ports Mondiaux', icon: Anchor }, + { end: 2, prefix: '<', suffix: 's', decimals: 0, label: 'Temps de Réponse', icon: Zap }, + { end: 99.5, prefix: '', suffix: '%', decimals: 1, label: 'Disponibilité', icon: CheckCircle2 }, ]; const pricingPlans = [ @@ -252,20 +291,31 @@ export default function LandingPage() { {/* Hero Section */}
- {/* Background Image */} + {/* Background Video */} - {/* Container background image */} -
+