Ajouter page détail pour chaque spectacle
This commit is contained in:
parent
25bc265e21
commit
457779e829
4 changed files with 211 additions and 16 deletions
19
src/App.tsx
19
src/App.tsx
|
|
@ -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()}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
183
src/components/SpectacleDetail.tsx
Normal file
183
src/components/SpectacleDetail.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue