Galerie photos illimitée pour les rencontres passées + page publique dédiée + carrousel
Photos : - Nouvelle table rencontre_passee_photos (photos illimitées par rencontre passée, avec position). Migration idempotente au démarrage des anciennes colonnes image_path/_2/_3 vers cette table (puis colonnes nullées) - Le nombre de photos affiché est désormais celui réellement uploadé (calculé), le champ manuel nb_photos n'est plus utilisé - Gestion admin : ajout illimité, choix de la photo « principale » (mise en avant), suppression réservée aux admins (les modérateurs ajoutent et réordonnent mais ne suppriment pas) Participants auto : - rencontres_passees.rencontre_id (FK, ON DELETE SET NULL) ; quand une rencontre passée est liée à une rencontre, le nombre de participants est calculé depuis ses inscriptions ; sinon valeur manuelle de repli. Le formulaire admin propose la liaison et affiche le compte auto Public : - Nouvelle page /rencontres-passees (menu « Rencontres passées ») listant toutes les rencontres passées - Carrousel plein écran (composant Carousel) montrant TOUTES les photos (flèches, clavier ←/→/Échap, compteur, miniatures) ; ouvert au clic sur une carte. La grille d'accueil montre 3 aperçus avec badge « +N » et un lien « Voir toutes les rencontres → » - Composant PastEventCard partagé entre l'accueil et la page dédiée Vérifié : 34 + 21 + 26 tests existants au vert, 19 nouveaux tests (participants auto liés/déliés, upload illimité, nb_photos calculé, principale, restrictions modérateur sur suppression de photo, migration legacy des colonnes inline), parcours navigateur complet (galerie admin, page publique, carrousel). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Fkg15RCxNgUys4ru73He2o
This commit is contained in:
13 files changed
+476
-139
No files matched your search
+11
-28
@@ -3,6 +3,7 @@ import { Link } from 'react-router-dom';
|
||||
import { usePublicData } from '../lib/usePublic.js';
|
||||
import { api, dateParts } from '../lib/api.js';
|
||||
import Modal from '../components/Modal.jsx';
|
||||
import PastEventCard from '../components/PastEventCard.jsx';
|
||||
|
||||
function InscriptionModal({ rencontre, onClose, onDone }) {
|
||||
const [form, setForm] = useState({ nom: '', entreprise: '', email: '', tel: '' });
|
||||
@@ -246,36 +247,18 @@ export default function Home() {
|
||||
{/* PAST */}
|
||||
<section style={{ background: 'var(--beige)', marginTop: 56, padding: '96px 0' }}>
|
||||
<div className="container">
|
||||
<div style={{ marginBottom: 52 }}>
|
||||
<div className="kicker" style={{ marginBottom: 14 }}>Ils y étaient</div>
|
||||
<h2 className="serif" style={{ fontWeight: 500, fontSize: 44, lineHeight: 1.08 }}>Rencontres passées</h2>
|
||||
<div style={{ marginBottom: 52, display: 'flex', alignItems: 'flex-end', justifyContent: 'space-between', gap: 24, flexWrap: 'wrap' }}>
|
||||
<div>
|
||||
<div className="kicker" style={{ marginBottom: 14 }}>Ils y étaient</div>
|
||||
<h2 className="serif" style={{ fontWeight: 500, fontSize: 44, lineHeight: 1.08 }}>Rencontres passées</h2>
|
||||
</div>
|
||||
<Link to="/rencontres-passees" style={{ textDecoration: 'none' }}>
|
||||
<span className="btn-link" style={{ fontSize: 14, whiteSpace: 'nowrap' }}>Voir toutes les rencontres →</span>
|
||||
</Link>
|
||||
</div>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 34 }}>
|
||||
{passees.map((p) => (
|
||||
<article key={p.id} className="card hero-grid" style={{ display: 'grid', gridTemplateColumns: '1.15fr .85fr', gap: 40, alignItems: 'center', padding: 36 }}>
|
||||
<div>
|
||||
<div style={{ fontSize: 12, letterSpacing: '.14em', textTransform: 'uppercase', color: 'var(--gray-light)', fontWeight: 600, marginBottom: 12 }}>
|
||||
{p.date_label} · {p.lieu}
|
||||
</div>
|
||||
<h3 className="serif" style={{ fontSize: 30, fontWeight: 600, lineHeight: 1.12, marginBottom: 16 }}>{p.titre}</h3>
|
||||
<p style={{ fontSize: 15.5, lineHeight: 1.7, color: 'var(--gray)', marginBottom: 20 }}>{p.texte}</p>
|
||||
<div style={{ display: 'flex', gap: 20, fontSize: 13, color: 'var(--gray-light)' }}>
|
||||
<span>👥 {p.participants} participants</span>
|
||||
<span>📷 {p.nb_photos} photos</span>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gridTemplateRows: '1fr 1fr', gap: 10, height: 240 }}>
|
||||
<div className={p.image_path ? '' : 'placeholder-pattern'} style={{ gridRow: 'span 2', borderRadius: 3, overflow: 'hidden' }}>
|
||||
{p.image_path && <img src={p.image_path} alt="" style={{ width: '100%', height: '100%', objectFit: 'cover' }} />}
|
||||
</div>
|
||||
<div className={p.image_path_2 ? '' : 'placeholder-pattern'} style={{ borderRadius: 3, overflow: 'hidden' }}>
|
||||
{p.image_path_2 && <img src={p.image_path_2} alt="" style={{ width: '100%', height: '100%', objectFit: 'cover' }} />}
|
||||
</div>
|
||||
<div className={p.image_path_3 ? '' : 'placeholder-pattern'} style={{ borderRadius: 3, overflow: 'hidden' }}>
|
||||
{p.image_path_3 && <img src={p.image_path_3} alt="" style={{ width: '100%', height: '100%', objectFit: 'cover' }} />}
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
{passees.slice(0, 3).map((p) => (
|
||||
<PastEventCard key={p.id} event={p} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import { usePublicData } from '../lib/usePublic.js';
|
||||
import PastEventCard from '../components/PastEventCard.jsx';
|
||||
|
||||
export default function RencontresPassees() {
|
||||
const { data } = usePublicData();
|
||||
const passees = data?.rencontresPassees ?? [];
|
||||
|
||||
return (
|
||||
<main>
|
||||
<section style={{ background: '#fff', borderBottom: '1px solid var(--border)' }}>
|
||||
<div className="container" style={{ padding: '70px 32px 60px' }}>
|
||||
<div className="kicker" style={{ marginBottom: 16 }}>Ils y étaient</div>
|
||||
<h1 className="serif" style={{ fontWeight: 500, fontSize: 'clamp(36px, 5vw, 54px)', lineHeight: 1.05, marginBottom: 18 }}>
|
||||
Nos rencontres passées
|
||||
</h1>
|
||||
<p style={{ fontSize: 17, lineHeight: 1.6, color: 'var(--gray)', maxWidth: 600 }}>
|
||||
Revivez en images les temps forts du Business Club : galas, visites privées, tables rondes
|
||||
et afterworks. Cliquez sur une rencontre pour parcourir toutes ses photos.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section style={{ background: 'var(--beige)', padding: '64px 0 96px' }}>
|
||||
<div className="container">
|
||||
{passees.length === 0 ? (
|
||||
<div style={{ textAlign: 'center', padding: '80px 20px', color: 'var(--gray-light)' }}>
|
||||
<div className="serif" style={{ fontSize: 26, color: 'var(--ink)', marginBottom: 10 }}>Aucune rencontre passée</div>
|
||||
<p style={{ fontSize: 15 }}>Les prochains événements apparaîtront ici une fois terminés.</p>
|
||||
</div>
|
||||
) : (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 34 }}>
|
||||
{passees.map((p) => (
|
||||
<PastEventCard key={p.id} event={p} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user