Affiche les coordonnées du référent aux visiteurs connectés

L'annuaire, les pages métier et la fiche adhérent montrent désormais la
personne à joindre chez l'adhérent — nom, prénom, ligne directe — mais
uniquement à un visiteur connecté.

Trois colonnes sur `members` (`contact_first_name`, `contact_last_name`,
`contact_phone`), saisies dans les deux formulaires de fiche via le
composant partagé `MemberContactFields`.

Le filtrage ne repose pas sur du CSS ni sur un rendu conditionnel tardif :
aucune requête publique ne lit ces colonnes. Seules `getMemberContacts()`
et `getMemberContact()` les rapatrient, et les pages ne les appellent
qu'après un `getSession()` positif. Un visiteur anonyme ne reçoit donc
rien — ni dans le HTML, ni dans le JSON-LD, ni dans les props du composant
client de l'annuaire. `tests/security.test.ts` verrouille cette séparation.

`src/lib/member-contact.ts` reste pur (composition du nom, lien `tel:`) et
est couvert par `tests/member-contact.test.ts`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012TAK3c4jAUqxVMzy746wWQ
This commit is contained in:
Claude committed 2026-09-14 11:31:59 +00:00
1 parent fb09bf0e3a
commit 67cd9d6684
20 files changed
+1854 -7

No files matched your search

+30 -1
View File
@@ -1,6 +1,7 @@
import Link from "next/link";
import { VitrineImage } from "@/components/VitrineImage";
import { memberPath } from "@/lib/seo";
import type { MemberContact } from "@/lib/member-contact";
export type MemberCardData = {
id: number;
@@ -44,8 +45,21 @@ const clampLines = (lines: number) =>
/**
* Carte d'un adhérent dans une grille (annuaire, page métier). Composant sans
* état : il se rend aussi bien côté serveur que dans le filtre client.
*
* `contact` (nom du référent, ligne directe) n'est passé qu'aux visiteurs
* connectés : la page ne le rapatrie pas du tout pour un visiteur anonyme, il
* n'y a donc rien à masquer en CSS. Le numéro reste en texte simple — un
* `tel:` imbriqué dans le lien de la carte serait un lien dans un lien.
*/
export function MemberCard({ m, index = 0 }: { m: MemberCardData; index?: number }) {
export function MemberCard({
m,
index = 0,
contact = null,
}: {
m: MemberCardData;
index?: number;
contact?: MemberContact | null;
}) {
const location = [m.address, m.city].filter(Boolean).join(" · ");
return (
<Link
@@ -87,6 +101,21 @@ export function MemberCard({ m, index = 0 }: { m: MemberCardData; index?: number
<span style={{ width: 7, height: 7, borderRadius: "50%", background: m.accent ?? "#E0A63C", flex: "0 0 auto" }} />
<span style={{ overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{location}</span>
</div>
{contact && (
<div style={{ marginTop: 10, borderTop: "1px dashed #e6dcc6", paddingTop: 10 }}>
<div style={{ fontSize: 10.5, letterSpacing: "0.1em", textTransform: "uppercase", fontWeight: 800, color: "#9a8d72", marginBottom: 4 }}>
Contact adhérent
</div>
{contact.name && (
<div style={{ fontSize: 13, fontWeight: 700, color: "#3c3322", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
{contact.name}
</div>
)}
{contact.phone && (
<div style={{ fontSize: 12.5, color: "#6c6150", marginTop: 2 }}>{contact.phone}</div>
)}
</div>
)}
</div>
</Link>
);