Compare commits

...

3 Commits

Author SHA1 Message Date
David
dcd459ee90 Merge branch 'dev' into preprod
All checks were successful
CD Preprod / Backend — Lint (push) Successful in 10m28s
CD Preprod / Frontend — Lint & Type-check (push) Successful in 11m12s
CD Preprod / Backend — Unit Tests (push) Successful in 10m8s
CD Preprod / Frontend — Unit Tests (push) Successful in 10m41s
CD Preprod / Backend — Integration Tests (push) Successful in 9m56s
CD Preprod / Build Backend (push) Successful in 42s
CD Preprod / Build Log Exporter (push) Successful in 30s
CD Preprod / Build Frontend (push) Successful in 27m19s
CD Preprod / Deploy to Preprod (push) Successful in 23s
CD Preprod / Notify Failure (push) Has been skipped
CD Preprod / Notify Success (push) Successful in 2s
2026-07-28 14:24:27 +02:00
David
a2a06c008b Merge branch 'video' into dev
All checks were successful
Dev CI / Backend — Lint (push) Successful in 10m22s
Dev CI / Frontend — Lint & Type-check (push) Successful in 11m6s
Dev CI / Backend — Unit Tests (push) Successful in 10m8s
Dev CI / Frontend — Unit Tests (push) Successful in 10m37s
Dev CI / Notify Failure (push) Has been skipped
2026-07-21 22:06:11 +02:00
David
75d20dc613 fix video demo 2026-07-21 22:05:54 +02:00
4 changed files with 94 additions and 19 deletions

View File

@ -27,6 +27,7 @@ import {
import { useTranslations, useLocale } from 'next-intl'; import { useTranslations, useLocale } from 'next-intl';
import { useAuth } from '@/lib/context/auth-context'; import { useAuth } from '@/lib/context/auth-context';
import { LandingHeader, LandingFooter } from '@/components/layout'; import { LandingHeader, LandingFooter } from '@/components/layout';
import { DemoVideoModal } from '@/components/DemoVideoModal';
function AnimatedCounter({ function AnimatedCounter({
end, end,
@ -96,6 +97,7 @@ export default function LandingPage() {
const isHowInView = useInView(howRef, { once: true, amount: 0.2 }); const isHowInView = useInView(howRef, { once: true, amount: 0.2 });
const [billingYearly, setBillingYearly] = useState(false); const [billingYearly, setBillingYearly] = useState(false);
const [isDemoOpen, setIsDemoOpen] = useState(false);
const { scrollYProgress } = useScroll(); const { scrollYProgress } = useScroll();
const backgroundY = useTransform(scrollYProgress, [0, 1], ['0%', '50%']); const backgroundY = useTransform(scrollYProgress, [0, 1], ['0%', '50%']);
@ -374,7 +376,6 @@ export default function LandingPage() {
<LayoutDashboard className="w-5 h-5 group-hover:translate-x-1 transition-transform" /> <LayoutDashboard className="w-5 h-5 group-hover:translate-x-1 transition-transform" />
</Link> </Link>
) : ( ) : (
<>
<Link <Link
href="/register" href="/register"
target="_blank" target="_blank"
@ -384,16 +385,14 @@ export default function LandingPage() {
<span>{t('hero.ctaRegister')}</span> <span>{t('hero.ctaRegister')}</span>
<ArrowRight className="w-5 h-5 group-hover:translate-x-1 transition-transform" /> <ArrowRight className="w-5 h-5 group-hover:translate-x-1 transition-transform" />
</Link> </Link>
<Link )}
href="/login" <button
target="_blank" type="button"
rel="noopener noreferrer" onClick={() => setIsDemoOpen(true)}
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" 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"
> >
{t('hero.ctaDemo')} {t('hero.ctaDemo')}
</Link> </button>
</>
)}
</motion.div> </motion.div>
</motion.div> </motion.div>
</div> </div>
@ -1076,6 +1075,8 @@ export default function LandingPage() {
</section> </section>
<LandingFooter /> <LandingFooter />
<DemoVideoModal isOpen={isDemoOpen} onClose={() => setIsDemoOpen(false)} />
</div> </div>
); );
} }

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,74 @@
'use client';
import { useEffect, useCallback } from 'react';
import { createPortal } from 'react-dom';
import { motion, AnimatePresence } from 'framer-motion';
import { X } from 'lucide-react';
interface DemoVideoModalProps {
isOpen: boolean;
onClose: () => void;
}
export function DemoVideoModal({ isOpen, onClose }: DemoVideoModalProps) {
const handleKeyDown = useCallback(
(e: KeyboardEvent) => {
if (e.key === 'Escape') onClose();
},
[onClose]
);
useEffect(() => {
if (!isOpen) return;
document.addEventListener('keydown', handleKeyDown);
document.body.style.overflow = 'hidden';
return () => {
document.removeEventListener('keydown', handleKeyDown);
document.body.style.overflow = '';
};
}, [isOpen, handleKeyDown]);
if (typeof document === 'undefined') return null;
return createPortal(
<AnimatePresence>
{isOpen && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.25 }}
className="fixed inset-0 z-[100] flex items-center justify-center bg-brand-navy/90 backdrop-blur-sm p-4 sm:p-8"
onClick={onClose}
role="dialog"
aria-modal="true"
aria-label="Démonstration Xpeditis"
>
<motion.div
initial={{ opacity: 0, scale: 0.95, y: 20 }}
animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.95, y: 20 }}
transition={{ duration: 0.3, ease: 'easeOut' }}
className="relative w-full max-w-5xl"
onClick={e => e.stopPropagation()}
>
<button
onClick={onClose}
aria-label="Fermer"
className="absolute -top-12 right-0 p-2 text-white/80 hover:text-white transition-colors rounded-full hover:bg-white/10"
>
<X className="w-7 h-7" />
</button>
<div className="rounded-2xl overflow-hidden shadow-2xl ring-1 ring-white/20 bg-black aspect-video">
<video controls autoPlay playsInline className="w-full h-full object-contain">
<source src="/assets/videos/demo-xpeditis.mp4" type="video/mp4" />
<source src="/assets/videos/demo-xpeditis.webm" type="video/webm" />
</video>
</div>
</motion.div>
</motion.div>
)}
</AnimatePresence>,
document.body
);
}