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 Navbar from './components/Navbar';
|
||||||
import Home from './components/Home';
|
import Home from './components/Home';
|
||||||
import Spectacles from './components/Spectacles';
|
import Spectacles from './components/Spectacles';
|
||||||
|
import SpectacleDetail from './components/SpectacleDetail';
|
||||||
import Agenda from './components/Agenda';
|
import Agenda from './components/Agenda';
|
||||||
import Ateliers from './components/Ateliers';
|
import Ateliers from './components/Ateliers';
|
||||||
import Contact from './components/Contact';
|
import Contact from './components/Contact';
|
||||||
|
|
@ -10,12 +11,22 @@ import Footer from './components/Footer';
|
||||||
export default function App() {
|
export default function App() {
|
||||||
const [currentPage, setCurrentPage] = useState('home');
|
const [currentPage, setCurrentPage] = useState('home');
|
||||||
|
|
||||||
|
const handleNavigate = (page: string) => {
|
||||||
|
setCurrentPage(page);
|
||||||
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
|
};
|
||||||
|
|
||||||
const renderPage = () => {
|
const renderPage = () => {
|
||||||
|
if (currentPage.startsWith('spectacle-')) {
|
||||||
|
const spectacleId = currentPage.replace('spectacle-', '');
|
||||||
|
return <SpectacleDetail spectacleId={spectacleId} onNavigate={handleNavigate} />;
|
||||||
|
}
|
||||||
|
|
||||||
switch (currentPage) {
|
switch (currentPage) {
|
||||||
case 'home':
|
case 'home':
|
||||||
return <Home onNavigate={setCurrentPage} />;
|
return <Home onNavigate={handleNavigate} />;
|
||||||
case 'spectacles':
|
case 'spectacles':
|
||||||
return <Spectacles />;
|
return <Spectacles onNavigate={handleNavigate} />;
|
||||||
case 'agenda':
|
case 'agenda':
|
||||||
return <Agenda />;
|
return <Agenda />;
|
||||||
case 'ateliers':
|
case 'ateliers':
|
||||||
|
|
@ -23,13 +34,13 @@ export default function App() {
|
||||||
case 'contact':
|
case 'contact':
|
||||||
return <Contact />;
|
return <Contact />;
|
||||||
default:
|
default:
|
||||||
return <Home onNavigate={setCurrentPage} />;
|
return <Home onNavigate={handleNavigate} />;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-cloud min-h-screen flex flex-col">
|
<div className="bg-cloud min-h-screen flex flex-col">
|
||||||
<Navbar currentPage={currentPage} onNavigate={setCurrentPage} />
|
<Navbar currentPage={currentPage} onNavigate={handleNavigate} />
|
||||||
|
|
||||||
<main className="flex-grow">
|
<main className="flex-grow">
|
||||||
{renderPage()}
|
{renderPage()}
|
||||||
|
|
|
||||||
|
|
@ -93,7 +93,7 @@ export default function Home({ onNavigate }: HomeProps) {
|
||||||
viewport={{ once: true }}
|
viewport={{ once: true }}
|
||||||
transition={{ delay: index * 0.2, duration: 0.6 }}
|
transition={{ delay: index * 0.2, duration: 0.6 }}
|
||||||
className="group cursor-pointer"
|
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">
|
<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
|
<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 { motion } from 'motion/react';
|
||||||
import { spectacles } from '../data';
|
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 (
|
return (
|
||||||
<div className="pt-32 pb-24 min-h-screen bg-cloud overflow-hidden relative">
|
<div className="pt-32 pb-24 min-h-screen bg-cloud overflow-hidden relative">
|
||||||
{/* Background Magic */}
|
{/* 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`}
|
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 */}
|
{/* 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 -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>
|
<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>
|
</p>
|
||||||
|
|
||||||
<div className="pt-4 md:pt-8 flex flex-wrap justify-center md:justify-start gap-4 md:gap-6">
|
<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">
|
<button
|
||||||
Dossier Pédagogique
|
onClick={() => onNavigate(`spectacle-${spectacle.id}`)}
|
||||||
</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"
|
||||||
<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">
|
Découvrir
|
||||||
<ExternalLink size={16} />
|
|
||||||
</div>
|
|
||||||
Voir les photos
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue