Ajouter page détail pour chaque spectacle

This commit is contained in:
Jalil Arfaoui 2026-02-21 15:23:52 +01:00
parent 25bc265e21
commit 457779e829
4 changed files with 211 additions and 16 deletions

View file

@ -2,6 +2,7 @@ import { useState } from 'react';
import Navbar from './components/Navbar';
import Home from './components/Home';
import Spectacles from './components/Spectacles';
import SpectacleDetail from './components/SpectacleDetail';
import Agenda from './components/Agenda';
import Ateliers from './components/Ateliers';
import Contact from './components/Contact';
@ -10,12 +11,22 @@ import Footer from './components/Footer';
export default function App() {
const [currentPage, setCurrentPage] = useState('home');
const handleNavigate = (page: string) => {
setCurrentPage(page);
window.scrollTo({ top: 0, behavior: 'smooth' });
};
const renderPage = () => {
if (currentPage.startsWith('spectacle-')) {
const spectacleId = currentPage.replace('spectacle-', '');
return <SpectacleDetail spectacleId={spectacleId} onNavigate={handleNavigate} />;
}
switch (currentPage) {
case 'home':
return <Home onNavigate={setCurrentPage} />;
return <Home onNavigate={handleNavigate} />;
case 'spectacles':
return <Spectacles />;
return <Spectacles onNavigate={handleNavigate} />;
case 'agenda':
return <Agenda />;
case 'ateliers':
@ -23,13 +34,13 @@ export default function App() {
case 'contact':
return <Contact />;
default:
return <Home onNavigate={setCurrentPage} />;
return <Home onNavigate={handleNavigate} />;
}
};
return (
<div className="bg-cloud min-h-screen flex flex-col">
<Navbar currentPage={currentPage} onNavigate={setCurrentPage} />
<Navbar currentPage={currentPage} onNavigate={handleNavigate} />
<main className="flex-grow">
{renderPage()}

View file

@ -93,7 +93,7 @@ export default function Home({ onNavigate }: HomeProps) {
viewport={{ once: true }}
transition={{ delay: index * 0.2, duration: 0.6 }}
className="group cursor-pointer"
onClick={() => onNavigate('spectacles')}
onClick={() => onNavigate(`spectacle-${spectacle.id}`)}
>
<div className="relative aspect-[4/5] overflow-hidden rounded-[32px] md:rounded-[40px] mb-6 md:mb-8 shadow-2xl shadow-night/5 group-hover:shadow-dream-purple/20 transition-all duration-500">
<img

View file

@ -0,0 +1,183 @@
import { motion } from 'motion/react';
import { ArrowLeft, Clock, Users, Download, MapPin, Calendar } from 'lucide-react';
import { spectacles, agenda } from '../data';
interface SpectacleDetailProps {
spectacleId: string;
onNavigate: (page: string) => void;
}
export default function SpectacleDetail({ spectacleId, onNavigate }: SpectacleDetailProps) {
const spectacle = spectacles.find(s => s.id === spectacleId);
if (!spectacle) {
return (
<div className="pt-32 pb-24 min-h-screen bg-cloud text-center">
<p className="font-sans text-night/60 text-xl">Spectacle introuvable.</p>
<button onClick={() => onNavigate('spectacles')} className="mt-8 text-dream-purple font-sans font-bold">
Retour aux spectacles
</button>
</div>
);
}
const upcomingDates = agenda
.filter(event => event.spectacleId === spectacleId)
.sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime());
return (
<div className="pt-24 md:pt-32 pb-24 min-h-screen bg-cloud overflow-hidden relative">
{/* Background */}
<div className="absolute top-0 right-0 w-96 h-96 bg-dream-purple/20 rounded-full filter blur-[100px] -translate-y-1/2 translate-x-1/2"></div>
<div className="absolute bottom-0 left-0 w-96 h-96 bg-dream-blue/20 rounded-full filter blur-[100px] translate-y-1/2 -translate-x-1/2"></div>
<div className="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 relative z-10">
{/* Back button */}
<motion.button
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
onClick={() => onNavigate('spectacles')}
className="group flex items-center gap-3 mb-10 font-sans font-bold text-night/60 hover:text-dream-purple transition-colors"
>
<ArrowLeft size={20} className="group-hover:-translate-x-1 transition-transform" />
Tous les spectacles
</motion.button>
{/* Hero */}
<div className="flex flex-col md:flex-row gap-10 md:gap-16 items-center mb-16 md:mb-24">
<motion.div
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ duration: 0.6 }}
className="w-full md:w-1/2"
>
<div className="relative aspect-[4/3] overflow-hidden rounded-[32px] md:rounded-[60px] shadow-2xl shadow-night/10 border-4 md:border-8 border-white">
<img
src={spectacle.image}
alt={spectacle.title}
className="object-cover w-full h-full"
referrerPolicy="no-referrer"
/>
</div>
</motion.div>
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.2 }}
className="w-full md:w-1/2 space-y-6 text-center md:text-left"
>
<h1 className="font-display text-4xl sm:text-6xl md:text-7xl text-night">{spectacle.title}</h1>
<div className="flex flex-wrap justify-center md:justify-start gap-3">
<div className="flex items-center gap-2 bg-dream-blue/20 px-5 py-2 rounded-2xl text-sm font-sans font-bold text-dream-blue-dark">
<Users size={16} className="text-dream-blue" />
<span>{spectacle.age}</span>
</div>
<div className="flex items-center gap-2 bg-dream-pink/20 px-5 py-2 rounded-2xl text-sm font-sans font-bold text-dream-pink-dark">
<Clock size={16} className="text-dream-pink" />
<span>{spectacle.duration}</span>
</div>
</div>
<p className="font-sans text-lg md:text-xl text-night/70 leading-relaxed">
{spectacle.summary}
</p>
{spectacle.dossierPro && spectacle.dossierPro !== '#' && (
<a
href={spectacle.dossierPro}
target="_blank"
rel="noreferrer"
className="inline-flex items-center gap-3 bg-night text-cloud px-8 py-4 rounded-full font-sans font-bold tracking-wide hover:bg-night/90 transition-all hover:scale-105 shadow-lg shadow-night/20"
>
<Download size={18} />
Dossier Pédagogique
</a>
)}
</motion.div>
</div>
{/* Gallery */}
{spectacle.gallery.length > 0 && (
<motion.section
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.6 }}
className="mb-16 md:mb-24"
>
<h2 className="font-display text-3xl md:text-5xl text-night mb-8 md:mb-12 text-center">Galerie</h2>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-6 md:gap-8">
{spectacle.gallery.map((img, index) => (
<div
key={index}
className="aspect-[4/3] overflow-hidden rounded-[24px] md:rounded-[40px] shadow-xl shadow-night/5 border-4 border-white"
>
<img
src={img}
alt={`${spectacle.title} - photo ${index + 1}`}
className="object-cover w-full h-full"
referrerPolicy="no-referrer"
/>
</div>
))}
</div>
</motion.section>
)}
{/* Upcoming dates */}
{upcomingDates.length > 0 && (
<motion.section
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.6 }}
>
<h2 className="font-display text-3xl md:text-5xl text-night mb-8 md:mb-12 text-center">Prochaines dates</h2>
<div className="space-y-4 max-w-2xl mx-auto">
{upcomingDates.map(event => {
const dateObj = new Date(event.date);
return (
<div
key={event.id}
className="flex flex-col sm:flex-row gap-6 p-6 md:p-8 rounded-[24px] bg-white shadow-lg shadow-night/5 border border-dream-purple/10 items-center"
>
<div className="flex-shrink-0 flex flex-col items-center justify-center w-20 h-20 md:w-24 md:h-24 rounded-2xl bg-gradient-to-br from-dream-purple to-dream-blue text-white shadow-md">
<span className="font-sans text-[10px] md:text-xs font-bold uppercase tracking-widest opacity-80">
{dateObj.toLocaleString('fr-FR', { month: 'short' })}
</span>
<span className="font-display text-3xl md:text-4xl">
{dateObj.getDate()}
</span>
</div>
<div className="flex-grow text-center sm:text-left">
<div className="flex items-center justify-center sm:justify-start gap-2 font-sans text-sm text-night/50 mb-1">
<Calendar size={14} />
<span>{dateObj.toLocaleString('fr-FR', { weekday: 'long', hour: '2-digit', minute: '2-digit' })}</span>
</div>
<div className="flex items-center justify-center sm:justify-start gap-2 font-sans font-bold text-night">
<MapPin size={14} className="text-dream-coral" />
<span>{event.location}</span>
</div>
</div>
{event.bookingLink && (
<a
href={event.bookingLink}
target="_blank"
rel="noreferrer"
className="bg-dream-coral text-white px-6 py-3 rounded-full font-sans font-bold text-sm hover:scale-105 transition-transform shadow-md"
>
Réserver
</a>
)}
</div>
);
})}
</div>
</motion.section>
)}
</div>
</div>
);
}

View file

@ -1,8 +1,12 @@
import { motion } from 'motion/react';
import { spectacles } from '../data';
import { ExternalLink, Clock, Users, Star, Sparkles } from 'lucide-react';
import { Clock, Users, Star, Sparkles } from 'lucide-react';
export default function Spectacles() {
interface SpectaclesProps {
onNavigate: (page: string) => void;
}
export default function Spectacles({ onNavigate }: SpectaclesProps) {
return (
<div className="pt-32 pb-24 min-h-screen bg-cloud overflow-hidden relative">
{/* Background Magic */}
@ -37,7 +41,7 @@ export default function Spectacles() {
className={`flex flex-col ${index % 2 === 1 ? 'md:flex-row-reverse' : 'md:flex-row'} gap-10 md:gap-16 items-center`}
>
{/* Image Side - Dream Bubble Look */}
<div className="w-full md:w-1/2 relative">
<div className="w-full md:w-1/2 relative cursor-pointer" onClick={() => onNavigate(`spectacle-${spectacle.id}`)}>
<div className="absolute -top-4 -left-4 md:-top-6 md:-left-6 w-16 h-16 md:w-24 md:h-24 bg-star/20 rounded-full filter blur-xl md:blur-2xl animate-pulse"></div>
<div className="absolute -bottom-4 -right-4 md:-bottom-6 md:-right-6 w-20 h-20 md:w-32 md:h-32 bg-dream-blue/30 rounded-full filter blur-xl md:blur-2xl animate-pulse animation-delay-2000"></div>
@ -77,14 +81,11 @@ export default function Spectacles() {
</p>
<div className="pt-4 md:pt-8 flex flex-wrap justify-center md:justify-start gap-4 md:gap-6">
<button className="w-full sm:w-auto bg-night text-cloud px-8 py-4 md:px-10 md:py-4 rounded-full font-sans font-bold tracking-wide hover:bg-night/90 transition-all hover:scale-105 shadow-lg shadow-night/20">
Dossier Pédagogique
</button>
<button className="group flex items-center gap-3 text-night font-sans font-bold hover:text-dream-purple transition-colors">
<div className="w-10 h-10 md:w-12 md:h-12 rounded-full border-2 border-night/10 flex items-center justify-center group-hover:border-dream-purple group-hover:bg-dream-purple/10 transition-all">
<ExternalLink size={16} />
</div>
Voir les photos
<button
onClick={() => onNavigate(`spectacle-${spectacle.id}`)}
className="w-full sm:w-auto bg-night text-cloud px-8 py-4 md:px-10 md:py-4 rounded-full font-sans font-bold tracking-wide hover:bg-night/90 transition-all hover:scale-105 shadow-lg shadow-night/20"
>
Découvrir
</button>
</div>
</div>