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. */}
+
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"}
@@ -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