Page Association : mission développée et bloc « ce que l'association apporte »

Le bandeau Mission tenait en une phrase et ne disait pas ce que l'adhésion
apporte concrètement.

- Mission réécrite en trois paragraphes : le réseau et les rencontres, la mise
  en avant de chaque adhérent auprès des habitants et des professionnels, et
  l'objectif double clients de proximité / courants d'affaires locaux.
- Nouveau bloc de quatre cartes sous le bandeau : se rencontrer, gagner en
  visibilité (site et réseaux sociaux), attirer des clients en B to C,
  développer des affaires en B to B.
- Les deux contenus sont éditables dans Paramètres : la mission accepte des
  paragraphes séparés par une ligne vide, les apports suivent la convention
  « Titre|Description » déjà utilisée pour le directoire.
- Migration 0009 : le texte de mission n'est remplacé que s'il est resté celui
  d'origine, une version réécrite à la main n'est jamais écrasée.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QXNXRC4j5VLfKvpyyisrnb
This commit is contained in:
Claude committed 2026-07-25 08:36:51 +00:00
1 parent e86c67c17e
commit fb3d5e337e
7 files changed
+1525 -13

No files matched your search

+15
View File
@@ -0,0 +1,15 @@
-- Texte de mission enrichi sur la page Association.
--
-- La clause sur `value` est volontaire : le texte n'est remplacé que s'il est
-- resté celui d'origine. Une mission déjà réécrite depuis Paramètres n'est
-- jamais écrasée. Les « apports » n'ont pas de ligne en base tant qu'ils n'ont
-- pas été édités : leur valeur par défaut s'applique d'elle-même.
UPDATE "site_settings"
SET "value" = 'Plein R fédère les commerçants, artisans et entreprises du Bassin de Pompey autour d''une conviction simple : on va plus loin ensemble. L''association crée les occasions de se rencontrer, de se connaître et de travailler les uns avec les autres.
Nous mettons chaque adhérent en avant auprès des habitants comme des autres professionnels du territoire : une fiche dans l''annuaire, des bons plans relayés sur le site et sur nos réseaux sociaux, une place dans nos rencontres et nos publications.
L''objectif est double : amener des clients aux commerces de proximité, et faire naître des courants d''affaires entre les entreprises du bassin.',
"updated_at" = now()
WHERE "key" = 'association_mission'
AND "value" = 'Créer du lien entre adhérents, encourager les échanges de proximité et porter une dynamique collective au service du Bassin de Pompey.';
File diff suppressed because it is too large. Load diff
+7
View File
@@ -64,6 +64,13 @@
"when": 1784933339220,
"tag": "0008_neat_makkari",
"breakpoints": true
},
{
"idx": 9,
"version": "7",
"when": 1784968500537,
"tag": "0009_texte_mission",
"breakpoints": true
}
]
}
+54 -11
View File
@@ -6,7 +6,13 @@ import { meetingRegistrations, meetings, pastMeetingPhotos, pastMeetings } from
import { PastMeetingGallery } from "@/components/PastMeetingGallery";
import { SiteFooter } from "@/components/SiteFooter";
import { SiteHeader } from "@/components/SiteHeader";
import { getSiteSettings, parseBoardMembers, splitLines } from "@/lib/site-settings";
import {
getSiteSettings,
parseBoardMembers,
parseParagraphs,
parsePillars,
splitLines,
} from "@/lib/site-settings";
export const dynamic = "force-dynamic";
@@ -96,6 +102,8 @@ export default async function AssociationPage() {
["Secrétariat", settings.association_secretary],
].filter(([, name]) => name);
const board = parseBoardMembers(settings.association_board_members);
const missionParagraphs = parseParagraphs(settings.association_mission);
const pillars = parsePillars(settings.association_pillars);
return (
<div style={{ background: "#F6F2E8", minHeight: "100vh", fontFamily: "'Public Sans',sans-serif", color: "#33291D" }}>
@@ -117,18 +125,53 @@ export default async function AssociationPage() {
</section>
<section style={{ background: "#13324F", color: "#fff" }}>
<div className="container grid grid-2" style={{ paddingTop: 44, paddingBottom: 44, alignItems: "start" }}>
<div>
<div style={{ color: "#E0A63C", fontSize: 12, letterSpacing: "0.14em", textTransform: "uppercase", fontWeight: 800, marginBottom: 10 }}>
Mission
<div className="container" style={{ paddingTop: 44, paddingBottom: 48 }}>
<div className="grid grid-2" style={{ alignItems: "start" }}>
<div>
<div style={{ color: "#E0A63C", fontSize: 12, letterSpacing: "0.14em", textTransform: "uppercase", fontWeight: 800, marginBottom: 10 }}>
Mission
</div>
<h2 className="font-display" style={{ margin: 0, fontSize: "clamp(28px, 4vw, 44px)" }}>
Réseau, rencontres, réussite locale.
</h2>
</div>
<div>
{missionParagraphs.map((paragraph, index) => (
<p
key={index}
style={{
margin: index === 0 ? 0 : "14px 0 0",
color: "#cfe0ee",
fontSize: 16,
lineHeight: 1.75,
}}
>
{paragraph}
</p>
))}
</div>
<h2 className="font-display" style={{ margin: 0, fontSize: "clamp(28px, 4vw, 44px)" }}>
Réseau, rencontres, réussite locale.
</h2>
</div>
<p style={{ margin: 0, color: "#cfe0ee", fontSize: 16, lineHeight: 1.75 }}>
{settings.association_mission}
</p>
{pillars.length > 0 && (
<>
<div style={{ color: "#E0A63C", fontSize: 12, letterSpacing: "0.14em", textTransform: "uppercase", fontWeight: 800, margin: "44px 0 16px" }}>
Ce que l&apos;association vous apporte
</div>
<div className="pillars-grid">
{pillars.map((pillar, index) => (
<article key={pillar.title} className="pillar-card">
<span className="font-display pillar-card__index">
{String(index + 1).padStart(2, "0")}
</span>
<h3 className="font-display pillar-card__title">{pillar.title}</h3>
{pillar.description && (
<p className="pillar-card__text">{pillar.description}</p>
)}
</article>
))}
</div>
</>
)}
</div>
</section>
+10 -1
View File
@@ -41,7 +41,16 @@ export default async function ParametresPage() {
<Field label="Adresse" name="association_address" value={settings.association_address} />
</div>
<Area label="Texte d'introduction Association" name="association_intro" value={settings.association_intro} />
<Area label="Mission" name="association_mission" value={settings.association_mission} />
<Area
label="Mission (une ligne vide sépare les paragraphes)"
name="association_mission"
value={settings.association_mission}
/>
<Area
label="Ce que l'association apporte (Titre|Description, une ligne par point)"
name="association_pillars"
value={settings.association_pillars}
/>
</div>
<div style={{ marginTop: 14, color: "#9a6638", fontSize: 13, lineHeight: 1.6 }}>
Les pages Facebook et LinkedIn s&apos;affichent sur la page d&apos;accueil et dans le pied
+43
View File
@@ -449,6 +449,49 @@ img {
-webkit-overflow-scrolling: touch;
}
/* ---- « Ce que l'association vous apporte » (page Association) ---- */
.pillars-grid {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 14px;
}
.pillar-card {
padding: 18px 18px 20px;
border: 1px solid rgba(255, 255, 255, 0.14);
border-radius: 16px;
background: rgba(255, 255, 255, 0.05);
}
.pillar-card__index {
color: var(--gold);
font-size: 13px;
font-weight: 800;
letter-spacing: 0.08em;
}
.pillar-card__title {
margin: 8px 0 7px;
color: #fff;
font-size: 17px;
font-weight: 700;
line-height: 1.25;
}
.pillar-card__text {
margin: 0;
color: #a9c4da;
font-size: 13.5px;
line-height: 1.65;
}
@media (max-width: 1024px) {
.pillars-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@media (max-width: 620px) {
.pillars-grid {
grid-template-columns: 1fr;
}
}
/* ---- Compteurs du tableau de bord ---- */
.stat-card {
padding: 20px;
+26 -1
View File
@@ -11,7 +11,14 @@ export const SITE_SETTING_DEFAULTS = {
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.",
"Plein R fédère les commerçants, artisans et entreprises du Bassin de Pompey autour d'une conviction simple : on va plus loin ensemble. L'association crée les occasions de se rencontrer, de se connaître et de travailler les uns avec les autres.\n\n" +
"Nous mettons chaque adhérent en avant auprès des habitants comme des autres professionnels du territoire : une fiche dans l'annuaire, des bons plans relayés sur le site et sur nos réseaux sociaux, une place dans nos rencontres et nos publications.\n\n" +
"L'objectif est double : amener des clients aux commerces de proximité, et faire naître des courants d'affaires entre les entreprises du bassin.",
association_pillars:
"Se rencontrer|Des rendez-vous réguliers entre adhérents pour échanger, partager les bonnes pratiques et rompre l'isolement du quotidien.\n" +
"Gagner en visibilité|Une fiche dans l'annuaire, vos promotions relayées sur le site et sur les pages Facebook et LinkedIn de l'association.\n" +
"Attirer des clients (B to C)|Vos offres mises sous les yeux des habitants du bassin, pour faire vivre le commerce de proximité toute l'année.\n" +
"Développer des affaires (B to B)|Un réseau de professionnels qui se connaissent et se recommandent : prestataires, fournisseurs et partenaires à deux pas.",
association_address: "",
association_email: "",
association_phone: "",
@@ -91,6 +98,24 @@ export function splitLines(value: string) {
.filter(Boolean);
}
/** Paragraphes d'un champ libre : une ligne vide sépare deux paragraphes. */
export function parseParagraphs(value: string) {
return value
.split(/\r?\n\s*\r?\n/)
.map((block) => block.replace(/\s*\r?\n\s*/g, " ").trim())
.filter(Boolean);
}
/** « Titre|Description », une ligne par apport de l'association. */
export function parsePillars(value: string) {
return splitLines(value)
.map((line) => {
const [title, description] = line.split("|").map((part) => part.trim());
return { title, description: description ?? "" };
})
.filter((pillar) => pillar.title);
}
export function parseBoardMembers(value: string) {
return splitLines(value).map((line) => {
const [name, role] = line.split("|").map((part) => part.trim());