Files
PleinR/src/app/annuaire/page.tsx
T
Claude a6718c90f1 Ajoute la page Annuaire publique (/annuaire) reliée aux fiches adhérents
Liste des adhérents actifs avec recherche (nom/métier), filtre par commune,
filtres catégories (chips + compteurs), tri (A–Z / avec promotions), état vide
et CTA d'adhésion — fidèle à la maquette. Chaque carte ouvre la fiche adhérent
(/adherents/[id]). La recherche de l'accueil et la nav pointent vers /annuaire
(la barre de recherche pré-remplit via ?q=). Nouvelles requêtes annuaire.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XwfhYHzC9hQWPDnQ2p53GL
2026-06-26 21:13:59 +00:00

167 lines
6.6 KiB
TypeScript

import Link from "next/link";
import type { Metadata } from "next";
import { Sparkle } from "@/components/Sparkle";
import {
getActiveMembersWithCategory,
getAllCategories,
getLiveBadgesByMember,
} from "@/lib/queries";
import { AnnuaireClient, type DirectoryMember } from "./AnnuaireClient";
export const dynamic = "force-dynamic";
export const metadata: Metadata = {
title: "Annuaire — Plein R",
description:
"L'annuaire des commerçants et entreprises du Bassin de Pompey. Trouvez un professionnel et ses bons plans.",
};
export default async function AnnuairePage({
searchParams,
}: {
searchParams: Promise<{ q?: string }>;
}) {
const { q } = await searchParams;
const [rawMembers, categories, badges] = await Promise.all([
getActiveMembersWithCategory(),
getAllCategories(),
getLiveBadgesByMember(),
]);
const badgeByMember = new Map<number, string | null>();
for (const b of badges) {
if (b.memberId != null && !badgeByMember.has(b.memberId)) {
badgeByMember.set(b.memberId, b.badge);
}
}
const members: DirectoryMember[] = rawMembers.map((m) => ({
...m,
hasPromo: badgeByMember.has(m.id),
promoBadge: badgeByMember.get(m.id) ?? null,
}));
return (
<div style={{ background: "#F6F2E8", minHeight: "100vh", fontFamily: "'Public Sans',sans-serif", color: "#33291D" }}>
{/* header */}
<header
className="container"
style={{ display: "flex", alignItems: "center", justifyContent: "space-between", padding: "18px 56px", gap: 20 }}
>
<Link href="/" style={{ display: "flex", alignItems: "center", gap: 10, textDecoration: "none" }}>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img src="/assets/logo.png" alt="Plein R" style={{ height: 44, width: "auto" }} />
<span className="font-display" style={{ fontWeight: 800, fontSize: 20, color: "#13324F" }}>
Plein R
</span>
</Link>
<nav style={{ display: "flex", alignItems: "center", gap: 30, flexWrap: "wrap" }}>
<Link href="/" style={{ textDecoration: "none", color: "#6f6450", fontWeight: 500, fontSize: 15 }}>
Accueil
</Link>
<Link href="/annuaire" style={{ textDecoration: "none", color: "#3c3322", fontWeight: 600, fontSize: 15 }}>
Annuaire
</Link>
<Link href="/#promotions" style={{ textDecoration: "none", color: "#6f6450", fontWeight: 500, fontSize: 15 }}>
Promotions
</Link>
<Link
href="/backend"
style={{ textDecoration: "none", color: "#fff", background: "#9a6638", fontWeight: 600, fontSize: 14.5, padding: "10px 18px", borderRadius: 999 }}
>
Espace adhérent
</Link>
</nav>
</header>
<div className="container" style={{ paddingBottom: 56 }}>
{/* title block */}
<section style={{ position: "relative", padding: "14px 0 22px", overflow: "hidden" }}>
<Sparkle color="#E0A63C" size={18} style={{ top: 22, right: 30 }} duration={3.2} />
<Sparkle color="#6FB0C6" size={12} style={{ top: 60, right: 90 }} duration={2.7} delay={0.5} />
<div
style={{
display: "inline-flex",
alignItems: "center",
gap: 9,
background: "#fff",
border: "1px solid #e6dcc6",
color: "#9a6638",
fontSize: 12,
letterSpacing: "0.12em",
textTransform: "uppercase",
fontWeight: 800,
padding: "6px 13px",
borderRadius: 999,
marginBottom: 14,
}}
>
<span style={{ width: 7, height: 7, borderRadius: "50%", background: "#E0A63C" }} />
{members.length} adhérents
</div>
<h1 className="font-display" style={{ fontWeight: 800, fontSize: 40, letterSpacing: "-0.02em", margin: "0 0 8px", color: "#26201a" }}>
L&apos;annuaire des commerçants &amp; entreprises
</h1>
<p style={{ margin: 0, fontSize: 16.5, color: "#6c6150", maxWidth: 560 }}>
Trouvez un professionnel du Bassin de Pompey, découvrez sa fiche et ses bons plans.
</p>
</section>
<AnnuaireClient members={members} categories={categories} initialQuery={q ?? ""} />
{/* join CTA */}
<section
style={{
marginTop: 34,
position: "relative",
background: "linear-gradient(120deg,#13324F,#1d4a72)",
borderRadius: 20,
padding: "34px 40px",
display: "flex",
alignItems: "center",
justifyContent: "space-between",
gap: 28,
overflow: "hidden",
flexWrap: "wrap",
}}
>
<Sparkle color="#E0A63C" size={18} style={{ top: 20, right: 40 }} duration={3.2} />
<div>
<div style={{ fontSize: 12.5, letterSpacing: "0.12em", textTransform: "uppercase", color: "#9fc6e6", fontWeight: 700, marginBottom: 8 }}>
Vous n&apos;êtes pas encore référencé ?
</div>
<h2 className="font-display" style={{ fontWeight: 800, fontSize: 26, margin: 0, color: "#fff" }}>
Rejoignez l&apos;annuaire Plein R
</h2>
</div>
<Link
href="/backend"
className="font-display"
style={{ textDecoration: "none", background: "#E0A63C", color: "#33291D", fontWeight: 700, fontSize: 15.5, padding: "14px 26px", borderRadius: 12, whiteSpace: "nowrap" }}
>
Adhérer à l&apos;association
</Link>
</section>
</div>
{/* footer */}
<footer style={{ background: "#EFE9DA", borderTop: "1px solid #e2d6bd", padding: "28px 56px" }}>
<div className="container" style={{ display: "flex", justifyContent: "space-between", alignItems: "center", gap: 16, flexWrap: "wrap" }}>
<div style={{ display: "flex", alignItems: "center", gap: 10 }}>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img src="/assets/logo.png" alt="Plein R" style={{ height: 36, width: "auto" }} />
<span className="font-display" style={{ fontWeight: 800, fontSize: 16, color: "#13324F" }}>
Plein R
</span>
</div>
<div style={{ fontSize: 13, color: "#8c8068", textAlign: "right", lineHeight: 1.7 }}>
contact@plein-r.fr · Bassin de Pompey
<br />
Réseau · Rencontre · Réussite
</div>
</div>
</footer>
</div>
);
}