mirror of
https://github.com/R0m1k3/PleinR.git
synced 2026-10-16 19:56:57 +02:00
SEO : URLs de fiche parlantes, pages métier indexables, aides de rédaction
- Fiches adhérents : URL canonique /adherents/12-au-bon-pain-frouard (memberPath). L'identifiant en tête suffit ; /adherents/12 et tout slug périmé sont redirigés de façon permanente vers la forme canonique, donc les anciens liens et les partages sociaux restent valides. Tous les liens (accueil, carrousel, annuaire, JSON-LD, sitemap, publications Facebook / LinkedIn) passent par memberPath. - Pages métier /annuaire/[categorie] : une page indexable par catégorie avec titre, introduction (effectif, communes), grille d'adhérents, fil d'Ariane et ItemList. Sans adhérent : noindex et hors sitemap. Section « Parcourir par métier » sur l'annuaire et « Autres métiers » sur chaque page pour le maillage interne. - Composants partagés MemberCard et CategoryLinks ; slugify déplacé dans src/lib/slug.ts. - Backend : conseils de rédaction sous les champs description et tags des formulaires adhérent (espace adhérent et fiche staff). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HDbKbJrPWweXXYSxA8MXW7
This commit is contained in:
19 files changed
+489
-99
No files matched your search
@@ -0,0 +1,39 @@
|
||||
import Link from "next/link";
|
||||
import { categoryPath } from "@/lib/seo";
|
||||
|
||||
export type CategoryLink = { slug: string; label: string; accent: string; memberCount: number };
|
||||
|
||||
/** Pastilles-liens vers les pages métier : contenu statique, lisible sans JavaScript. */
|
||||
export function CategoryLinks({ categories, currentSlug }: { categories: CategoryLink[]; currentSlug?: string }) {
|
||||
return (
|
||||
<nav aria-label="Parcourir par métier" style={{ display: "flex", flexWrap: "wrap", gap: 9 }}>
|
||||
{categories.map((c) => {
|
||||
const active = c.slug === currentSlug;
|
||||
return (
|
||||
<Link
|
||||
key={c.slug}
|
||||
href={categoryPath(c.slug)}
|
||||
aria-current={active ? "page" : undefined}
|
||||
style={{
|
||||
textDecoration: "none",
|
||||
fontSize: 13.5,
|
||||
fontWeight: 600,
|
||||
padding: "8px 14px",
|
||||
borderRadius: 999,
|
||||
border: `1px solid ${active ? "#13324F" : "#e6dcc6"}`,
|
||||
background: active ? "#13324F" : "#fff",
|
||||
color: active ? "#fff" : "#3c3322",
|
||||
display: "inline-flex",
|
||||
alignItems: "center",
|
||||
gap: 8,
|
||||
}}
|
||||
>
|
||||
<span style={{ width: 7, height: 7, borderRadius: "50%", background: active ? "#E0A63C" : c.accent, flexShrink: 0 }} />
|
||||
{c.label}
|
||||
<span style={{ opacity: 0.6 }}>{c.memberCount}</span>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import Link from "next/link";
|
||||
import { VitrineImage } from "@/components/VitrineImage";
|
||||
import { memberPath } from "@/lib/seo";
|
||||
|
||||
export type MemberCardData = {
|
||||
id: number;
|
||||
name: string;
|
||||
description: string | null;
|
||||
city: string | null;
|
||||
address: string | null;
|
||||
coverUrl: string | null;
|
||||
logoUrl: string | null;
|
||||
categoryLabel: string | null;
|
||||
accent: string | null;
|
||||
hasPromo?: boolean;
|
||||
promoBadge?: string | null;
|
||||
};
|
||||
|
||||
const STRIPE_WARM =
|
||||
"repeating-linear-gradient(45deg,#efe9da,#efe9da 12px,#e6ddc9 12px,#e6ddc9 24px)";
|
||||
const STRIPE_COOL =
|
||||
"repeating-linear-gradient(45deg,#eef0ec,#eef0ec 12px,#e2e8e6 12px,#e2e8e6 24px)";
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
export function MemberCard({ m, index = 0 }: { m: MemberCardData; index?: number }) {
|
||||
return (
|
||||
<Link
|
||||
href={memberPath(m)}
|
||||
className="lift-card"
|
||||
style={{ textDecoration: "none", color: "inherit", background: "#fff", border: "1px solid #e6dcc6", borderRadius: 18, overflow: "hidden", display: "flex", flexDirection: "column" }}
|
||||
>
|
||||
<VitrineImage
|
||||
coverUrl={m.coverUrl}
|
||||
logoUrl={m.logoUrl}
|
||||
height={150}
|
||||
stripe={index % 2 === 0 ? STRIPE_WARM : STRIPE_COOL}
|
||||
>
|
||||
{m.categoryLabel && (
|
||||
<span style={{ position: "absolute", top: 12, left: 12, background: m.accent ?? "#9a6638", color: "#fff", borderRadius: 999, padding: "5px 12px", fontSize: 11, fontWeight: 700 }}>
|
||||
{m.categoryLabel}
|
||||
</span>
|
||||
)}
|
||||
{m.hasPromo && m.promoBadge && (
|
||||
<span className="font-display" style={{ position: "absolute", top: 0, right: 0, background: "#d8472b", color: "#fff", fontWeight: 800, fontSize: 12.5, padding: "6px 12px", borderRadius: "0 0 0 14px" }}>
|
||||
{m.promoBadge}
|
||||
</span>
|
||||
)}
|
||||
</VitrineImage>
|
||||
<div style={{ padding: "16px 18px 18px", display: "flex", flexDirection: "column", flex: 1 }}>
|
||||
<h3 className="font-display" style={{ fontWeight: 700, fontSize: 18, margin: 0, color: "#26201a" }}>
|
||||
{m.name}
|
||||
</h3>
|
||||
<p style={{ margin: "6px 0 12px", fontSize: 13, color: "#8c8068", lineHeight: 1.5, flex: 1 }}>
|
||||
{m.description}
|
||||
</p>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: 7, fontSize: 12.5, color: "#6c6150", borderTop: "1px solid #f0e8d6", paddingTop: 11 }}>
|
||||
<span style={{ width: 7, height: 7, borderRadius: "50%", background: m.accent ?? "#E0A63C" }} />
|
||||
{[m.address, m.city].filter(Boolean).join(" · ")}
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import Link from "next/link";
|
||||
import { VitrineImage } from "./VitrineImage";
|
||||
import { memberPath } from "@/lib/seo";
|
||||
|
||||
export type CarouselMember = {
|
||||
id: number;
|
||||
@@ -24,7 +25,7 @@ const FADE_MS = 450;
|
||||
function Card({ m, visible, index }: { m: CarouselMember; visible: boolean; index: number }) {
|
||||
return (
|
||||
<Link
|
||||
href={`/adherents/${m.id}`}
|
||||
href={memberPath(m)}
|
||||
className="lift-card"
|
||||
style={{
|
||||
background: "#fff",
|
||||
|
||||
Reference in new issue
Block a user