mirror of
https://github.com/R0m1k3/PleinR.git
synced 2026-10-22 22:58:04 +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
@@ -69,6 +69,7 @@ export async function getLivePromotions(limit = 6) {
|
||||
validUntil: promotions.validUntil,
|
||||
memberId: promotions.memberId,
|
||||
memberName: members.name,
|
||||
memberCity: members.city,
|
||||
})
|
||||
.from(promotions)
|
||||
.leftJoin(members, eq(promotions.memberId, members.id))
|
||||
@@ -174,3 +175,44 @@ export async function getActiveMemberCount(): Promise<number> {
|
||||
.where(eq(members.status, "active"));
|
||||
return Number(row?.total ?? 0);
|
||||
}
|
||||
|
||||
/** Une catégorie par son slug d'URL, ou `null`. */
|
||||
export async function getCategoryBySlug(slug: string) {
|
||||
const [row] = await db
|
||||
.select({
|
||||
id: categories.id,
|
||||
slug: categories.slug,
|
||||
label: categories.label,
|
||||
accent: categories.accent,
|
||||
tint: categories.tint,
|
||||
})
|
||||
.from(categories)
|
||||
.where(eq(categories.slug, slug));
|
||||
return row ?? null;
|
||||
}
|
||||
|
||||
/** Adhérents actifs d'une catégorie, pour la page métier de l'annuaire. */
|
||||
export async function getActiveMembersByCategory(categoryId: number) {
|
||||
return db
|
||||
.select({
|
||||
id: members.id,
|
||||
name: members.name,
|
||||
description: members.description,
|
||||
city: members.city,
|
||||
address: members.address,
|
||||
coverUrl: members.coverUrl,
|
||||
logoUrl: members.logoUrl,
|
||||
categoryLabel: categories.label,
|
||||
accent: categories.accent,
|
||||
})
|
||||
.from(members)
|
||||
.leftJoin(categories, eq(members.categoryId, categories.id))
|
||||
.where(and(eq(members.status, "active"), eq(members.categoryId, categoryId)))
|
||||
.orderBy(asc(members.name));
|
||||
}
|
||||
|
||||
/** Catégories ayant au moins un adhérent actif, avec leur effectif. */
|
||||
export async function getCategoriesInUse() {
|
||||
const rows = await getCategoriesWithCounts(1000);
|
||||
return rows.filter((c) => Number(c.memberCount) > 0);
|
||||
}
|
||||
Reference in new issue
Block a user