From 050c29b5dfae88b6f95fb92b17e899052d29673a Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 10 Sep 2026 13:39:11 +0000 Subject: [PATCH] =?UTF-8?q?Publie=20les=20r=C3=A9unions=20sur=20le=20site,?= =?UTF-8?q?=20sans=20publier=20ce=20qui=20s'y=20dit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Les réunions restaient entre membres ; l'association veut que son agenda complet soit lisible des familles. Une réunion publiée apparaît donc comme n'importe quel rendez-vous : sur l'accueil, dans l'agenda de la page Rejoindre, et sur sa propre page publique. Ce qui la protège n'est plus son type mais son statut. En brouillon, elle reste interne — son lien de partage répond « indisponible ». Publiée, elle montre son titre, sa date, son lieu et sa description publique, et rien d'autre : les notes internes, la check-list de préparation et la liste des présences n'ont aucun chemin vers une page publique. Sa page publique change de propos : ni créneaux de bénévoles, ni billetterie, mais une invitation à venir et deux portes — poser une question, rejoindre l'association. Un badge « Réunion » la distingue des manifestations partout où les deux se côtoient. Le formulaire suit : la description publique redevient disponible et s'annonce comme l'ordre du jour tel qu'on le dirait aux familles. Et puisqu'une réunion se tient souvent chez quelqu'un, le champ Lieu porte un avertissement — une réunion publiée affiche son adresse, mieux vaut écrire « chez un parent, adresse communiquée aux inscrits ». Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_014SfQYBU4xXTeSEHKhHQXdD --- src/app/inscription/[token]/page.tsx | 42 ++++++++++++++++++++++++++-- src/app/page.tsx | 15 ++++++++-- src/app/rejoindre/page.tsx | 11 ++++++-- src/components/event-form.tsx | 35 ++++++++++++++--------- src/lib/data.ts | 25 ++++++++--------- 5 files changed, 95 insertions(+), 33 deletions(-) diff --git a/src/app/inscription/[token]/page.tsx b/src/app/inscription/[token]/page.tsx index 4b9801a..c0cc3e9 100644 --- a/src/app/inscription/[token]/page.tsx +++ b/src/app/inscription/[token]/page.tsx @@ -7,6 +7,7 @@ import { Hand, MapPin, Ticket, + Users, } from "lucide-react"; import Link from "next/link"; @@ -108,6 +109,7 @@ export default async function InscriptionPage({ ); // `trim()` : un espace collé depuis un traitement de texte est truthy, et // afficherait un bouton « Réserver » qui ne mène nulle part. + const estReunion = event.kind === "meeting"; const billetterie = event.ticketingUrl?.trim() || null; const hote = ticketingHostLabel(billetterie) ?? "la billetterie en ligne"; const aDesCreneaux = event.volunteerSlots.length > 0; @@ -141,7 +143,12 @@ export default async function InscriptionPage({ permet au bandeau de billetterie de le déborder en -mx-6. */}
- {billetterie ? ( + {estReunion ? ( + <> + + Réunion de l’association + + ) : billetterie ? ( <> {aDesCreneaux ? "Billetterie et bénévoles" : "Billetterie en ligne"} @@ -252,7 +259,7 @@ export default async function InscriptionPage({ {/* Sans ce garde, un événement à billetterie seule afficherait « 0 coup de main » à côté du bouton « Réserver » : le parent lit « complet » et referme. */} - {aDesCreneaux && ( + {aDesCreneaux && !estReunion && (
+ {estReunion ? ( +
+

+ Venir à la réunion +

+

+ Les réunions sont ouvertes aux parents +

+

+ C’est là qu’on décide de ce que fait l’association. Venez + écouter, ou prendre la parole : les deux se valent. +

+ {/* Aucune liste de présents ici : qui vient à une réunion est une + information réservée aux membres connectés. */} +
+ + Poser une question + + + Rejoindre l’association + +
+
+ ) : (

Aider à l’organisation @@ -320,6 +357,7 @@ export default async function InscriptionPage({ /> )}

+ )}
diff --git a/src/app/page.tsx b/src/app/page.tsx index 09310d0..643d224 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -8,6 +8,7 @@ import { MessageCircle, MousePointerClick, Ticket, + Users, } from "lucide-react"; import Link from "next/link"; @@ -249,6 +250,12 @@ export default async function HomePage() { lien, une ancre imbriquée serait invalide — et personne ne doit partir payer depuis l'accueil sans avoir lu la date ni le lieu. */} + {event.kind === "meeting" && ( + + + Réunion + + )} {event.ticketingUrl && ( @@ -281,9 +288,11 @@ export default async function HomePage() { /> )}
- {remaining > 0 && !event.ticketingUrl - ? "Voir et se proposer" - : "Voir l’événement"} + {event.kind === "meeting" + ? "Voir la réunion" + : remaining > 0 && !event.ticketingUrl + ? "Voir et se proposer" + : "Voir l’événement"}
diff --git a/src/app/rejoindre/page.tsx b/src/app/rejoindre/page.tsx index ab2f54c..abc406a 100644 --- a/src/app/rejoindre/page.tsx +++ b/src/app/rejoindre/page.tsx @@ -187,6 +187,7 @@ export default async function RejoindrePage() { const prochains = events.slice(0, 3).map((event, index) => ({ id: event.id, titre: event.title, + reunion: event.kind === "meeting", date: formatDateTime(event.startAt), lieu: event.location, jeton: event.shareToken, @@ -497,7 +498,13 @@ export default async function RejoindrePage() {

)}
- {event.restantes > 0 && ( + {event.reunion && ( + + + Réunion + + )} + {event.restantes > 0 && !event.reunion && ( {event.restantes} place @@ -505,7 +512,7 @@ export default async function RejoindrePage() { )} - {event.restantes > 0 + {event.restantes > 0 && !event.reunion ? "Se proposer" : "Voir le rendez-vous"} diff --git a/src/components/event-form.tsx b/src/components/event-form.tsx index 49f4f55..a652b67 100644 --- a/src/components/event-form.tsx +++ b/src/components/event-form.tsx @@ -114,11 +114,7 @@ export function EventForm({
@@ -136,7 +132,7 @@ export function EventForm({ icone: Users, titre: "Une réunion", texte: - "Bureau, conseil, assemblée générale. Visible des seuls membres, chacun annonce s’il sera présent.", + "Bureau, conseil, assemblée générale. Annoncée sur le site comme les autres rendez-vous, et chaque membre indique s’il sera présent.", }, ].map(({ valeur, icone: Icone, titre, texte }) => (
-
+

- Affichée sur l’accueil du site et sur la page d’inscription des - bénévoles, dès que l’événement est publié. Ne rien y mettre que - vous ne diriez pas devant l’école. + {estReunion + ? "Affichée sur le site dès que la réunion est publiée : l’ordre du jour tel que vous l’annonceriez aux familles. Vos notes internes, la préparation et les présences n’en sortent jamais." + : "Affichée sur l’accueil du site et sur la page d’inscription des bénévoles, dès que l’événement est publié. Ne rien y mettre que vous ne diriez pas devant l’école."}

+ {estReunion && ( +

+ Une réunion publiée affiche son lieu sur le site. Si vous vous + retrouvez chez quelqu’un, écrivez plutôt « chez un parent, adresse + communiquée aux inscrits » que l’adresse elle-même. +

+ )}
@@ -319,7 +322,11 @@ export function EventForm({
@@ -330,7 +337,9 @@ export function EventForm({ > diff --git a/src/lib/data.ts b/src/lib/data.ts index 09a6673..9623c4a 100644 --- a/src/lib/data.ts +++ b/src/lib/data.ts @@ -15,17 +15,18 @@ import { } from "@/lib/db/schema"; /** - * Événements publiés à venir — pour la page d'accueil publique. + * Rendez-vous publiés à venir — pour les pages publiques. Réunions comprises : + * l'association veut que son agenda complet soit lisible des familles. * - * Le filtre `kind = 'event'` est le premier des deux verrous qui tiennent les - * réunions hors du site public ; le second est dans getEventByShareToken. - * Ne jamais le retirer « pour simplifier » : une réunion de bureau publiée - * afficherait son ordre du jour et l'adresse d'un domicile sur Internet. + * Ce qui protège une réunion n'est donc plus son type mais son STATUT : tant + * qu'elle est en brouillon, elle reste interne. Ce que les pages publiques + * affichent d'un rendez-vous se limite au titre, à la date, au lieu et à la + * description publique ; les notes internes, la check-list et les présences + * n'en sortent jamais (voir les composants publics). */ export async function getUpcomingPublishedEvents() { return db.query.events.findMany({ where: and( - eq(events.kind, "event"), eq(events.status, "published"), gte(events.startAt, new Date()), ), @@ -99,17 +100,15 @@ export const getEventWithDetails = cache(async (id: string) => { }); /** - * Un événement via son jeton public — pour l'inscription des bénévoles. - * `cache()` évite le double appel (generateMetadata + corps de la page). + * Un rendez-vous via son jeton public — page publique d'un événement ou d'une + * réunion. `cache()` évite le double appel (generateMetadata + corps de page). * - * Second verrou : une réunion possède un jeton de partage comme tout - * événement, mais ce jeton ne doit ouvrir aucune page. Le filtre est porté par - * la requête et non par la page, pour qu'un futur appelant hérite de la - * protection sans avoir à y penser. + * La page n'affiche que ce qui est publiable ; en particulier elle ne charge + * pas les présences, qui sont une donnée de membres. */ export const getEventByShareToken = cache(async (token: string) => { return db.query.events.findFirst({ - where: and(eq(events.kind, "event"), eq(events.shareToken, token)), + where: eq(events.shareToken, token), with: { volunteerSlots: { // Heure de début d'abord : un tableau de créneaux se lit dans l'ordre