Files
PleinR/src/lib/site-settings.ts
T
Claude e3a4771edf Refonte du bloc infos pratiques, suspension des promos et publication réseaux
Bloc « Informations pratiques » (fiche adhérent)
- Suppression des dégradés hors palette (radial doré de la section, dégradés
  crème/bleu des deux sous-cartes) au profit du vocabulaire visuel du site :
  carte blanche bordée, sous-cartes plates sur le fond de champ, variables CSS.
- La pastille « Préparer votre visite » devient une puce arrondie identique aux
  badges du reste du site ; le chip d'ouverture reprend les teintes de statut.
- Les glyphes texte ◷ et ↗ sont remplacés par des icônes SVG cohérentes.

Suspension des promotions
- Nouveau statut `suspended` + colonnes `suspended_by` / `suspended_by_id` /
  `suspended_at`.
- L'adhérent suspend et réactive ses propres promotions depuis Mon espace ;
  l'association suspend et réactive n'importe laquelle depuis le backoffice.
- Une suspension décidée par l'association ne peut pas être levée par
  l'adhérent : le motif est affiché dans son espace.
- Les lectures publiques filtrant déjà sur `live`, une promo suspendue quitte
  immédiatement l'accueil, l'annuaire et la fiche.

Publication Facebook / LinkedIn
- `src/lib/social.ts` : publication d'une promo en ligne sur la page Facebook
  (Graph API, upload multipart car les images sont en data-URI) et sur la page
  LinkedIn (Posts API + Images API en trois étapes).
- Jetons d'accès exclusivement en variables d'environnement ; un réseau non
  configuré masque son bouton.
- Table `social_posts` : chaque tentative est tracée, un échec est rattrapé et
  affiché sur la carte de la promo sans interrompre le backoffice.
- Nouvelle capacité RBAC `publishSocial` (admin + modérateur).

Liens sociaux publics
- Réglages `association_facebook` / `association_linkedin` éditables dans
  Paramètres, préremplis avec les pages de l'association.
- Section « Plein R sur les réseaux » sur l'accueil et icônes dans le pied de
  page, masquées si le réglage est vide.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QXNXRC4j5VLfKvpyyisrnb
2026-07-24 22:20:51 +00:00

97 lines
3.0 KiB
TypeScript

import { asc } from "drizzle-orm";
import { db } from "@/db";
import { siteSettings } from "@/db/schema";
export const SITE_SETTING_DEFAULTS = {
association_name: "Plein R",
association_tagline: "Association des commerçants et entreprises du Bassin de Pompey.",
association_intro:
"Plein R rassemble les acteurs économiques locaux autour du réseau, des rencontres et de la mise en valeur du territoire.",
association_mission:
"Créer du lien entre adhérents, encourager les échanges de proximité et porter une dynamique collective au service du Bassin de Pompey.",
association_address: "",
association_email: "",
association_phone: "",
association_website: "",
association_facebook:
"https://www.facebook.com/search/top?q=pleinr%20-%20les%20bons%20plan%20du%20bassin%20de%20pompey",
association_linkedin:
"https://www.linkedin.com/company/association-plein-r-bassin-de-pompey/",
association_siret: "",
association_rna: "",
association_contact: "",
association_president: "",
association_president_role: "Présidence",
association_president_message: "",
association_president_photo: "",
association_vice_president: "",
association_treasurer: "",
association_secretary: "",
association_board_members: "",
association_host_name: "",
association_host_address: "",
association_host_phone: "",
association_publication_director: "",
association_privacy_contact: "",
legal_updated: "",
};
export type SiteSettings = typeof SITE_SETTING_DEFAULTS;
export type SiteSettingKey = keyof SiteSettings;
export async function getSiteSettings(): Promise<SiteSettings> {
const rows = await db.select().from(siteSettings).orderBy(asc(siteSettings.key));
const merged = { ...SITE_SETTING_DEFAULTS };
for (const row of rows) {
if (row.key in merged) {
merged[row.key as SiteSettingKey] = row.value ?? "";
}
}
return merged;
}
export type SocialLink = {
network: "facebook" | "linkedin";
label: string;
handle: string;
href: string;
};
/** Réseaux sociaux publics de l'association, dans l'ordre d'affichage. */
export function socialLinks(settings: SiteSettings): SocialLink[] {
const links: SocialLink[] = [];
const facebook = settings.association_facebook.trim();
const linkedin = settings.association_linkedin.trim();
if (facebook) {
links.push({
network: "facebook",
label: "Facebook",
handle: "Plein R — Les bons plans du Bassin de Pompey",
href: facebook,
});
}
if (linkedin) {
links.push({
network: "linkedin",
label: "LinkedIn",
handle: "Association Plein R — Bassin de Pompey",
href: linkedin,
});
}
return links;
}
export function splitLines(value: string) {
return value
.split(/\r?\n/)
.map((line) => line.trim())
.filter(Boolean);
}
export function parseBoardMembers(value: string) {
return splitLines(value).map((line) => {
const [name, role] = line.split("|").map((part) => part.trim());
return { name, role: role || "Membre du directoire" };
});
}