Logo annuaire + modals devenir membre & contact

- Logo en haut a gauche de la page Annuaire (prop logo sur SiteHeader)
- Modal "Devenir adherent" sur les CTA accueil + annuaire : cree une
  demande d'adhesion (membership_requests) et alerte les admins via le
  journal d'activite (compteur tableau de bord deja cable)
- Formulaire de contact (modal depuis le pied de page) : nouvelle table
  contact_messages + alerte admin
- Composant Modal partage, server actions publiques (sans auth)
- Migration drizzle 0002

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
MichaelandClaude Opus 4.8 committed 2026-06-27 09:16:59 +02:00
1 parent e2cf03c9f3
commit 141acffc1c
12 files changed
+1057 -17

No files matched your search

+73
View File
@@ -0,0 +1,73 @@
"use server";
import { revalidatePath } from "next/cache";
import { db } from "@/db";
import { activityLog, contactMessages, membershipRequests } from "@/db/schema";
export type SubmitResult = { ok: boolean; error?: string };
const isEmail = (v: string) => /^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(v);
async function logActivity(message: string, dot: string) {
await db.insert(activityLog).values({ message, dot });
}
// ---- Demande pour devenir membre (public, sans authentification) ----
export async function submitMembershipRequest(formData: FormData): Promise<SubmitResult> {
const name = String(formData.get("name") ?? "").trim();
const email = String(formData.get("email") ?? "").trim();
const phone = String(formData.get("phone") ?? "").trim();
const note = String(formData.get("message") ?? "").trim();
if (!name) return { ok: false, error: "Le nom est requis." };
if (email && !isEmail(email)) return { ok: false, error: "Adresse e-mail invalide." };
// Pas de colonne téléphone dédiée : on l'intègre au message.
const message = [phone ? `Téléphone : ${phone}` : null, note || null]
.filter(Boolean)
.join("\n")
.slice(0, 2000);
await db.insert(membershipRequests).values({
name,
email: email || null,
message: message || null,
status: "new",
});
await logActivity(
`<strong>${name}</strong> souhaite devenir adhérent`,
"#9a6638",
);
revalidatePath("/backend");
return { ok: true };
}
// ---- Message via le formulaire de contact (public) ----
export async function submitContactMessage(formData: FormData): Promise<SubmitResult> {
const name = String(formData.get("name") ?? "").trim();
const email = String(formData.get("email") ?? "").trim();
const subject = String(formData.get("subject") ?? "").trim();
const message = String(formData.get("message") ?? "").trim();
if (!name) return { ok: false, error: "Le nom est requis." };
if (!isEmail(email)) return { ok: false, error: "Adresse e-mail invalide." };
if (!message) return { ok: false, error: "Le message est requis." };
await db.insert(contactMessages).values({
name,
email,
subject: subject ? subject.slice(0, 200) : null,
message: message.slice(0, 4000),
status: "new",
});
await logActivity(
`Nouveau message de contact de <strong>${name}</strong>`,
"#2C6FB3",
);
revalidatePath("/backend");
return { ok: true };
}
+6 -8
View File
@@ -1,8 +1,8 @@
import Link from "next/link";
import type { Metadata } from "next";
import { Sparkle } from "@/components/Sparkle";
import { SiteHeader } from "@/components/SiteHeader";
import { SiteFooter } from "@/components/SiteFooter";
import { MembershipModalButton } from "@/components/MembershipModalButton";
import {
getActiveMembersWithCategory,
getAllCategories,
@@ -45,7 +45,7 @@ export default async function AnnuairePage({
return (
<div style={{ background: "#F6F2E8", minHeight: "100vh", fontFamily: "'Public Sans',sans-serif", color: "#33291D" }}>
<SiteHeader active="annuaire" />
<SiteHeader active="annuaire" logo />
<div className="container" style={{ paddingBottom: 56 }}>
{/* title block */}
@@ -107,13 +107,11 @@ export default async function AnnuairePage({
Rejoignez l&apos;annuaire Plein R
</h2>
</div>
<Link
href="/backend"
<MembershipModalButton
label="Adhérer à l'association"
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>
style={{ border: "none", background: "#E0A63C", color: "#33291D", fontWeight: 700, fontSize: 15.5, padding: "14px 26px", borderRadius: 12, whiteSpace: "nowrap" }}
/>
</section>
</div>
+5 -6
View File
@@ -3,6 +3,7 @@ import { Sparkle } from "@/components/Sparkle";
import { Reveal } from "@/components/Reveal";
import { SiteHeader } from "@/components/SiteHeader";
import { SiteFooter } from "@/components/SiteFooter";
import { MembershipModalButton } from "@/components/MembershipModalButton";
import { getHighlightedMembers, getLivePromotions } from "@/lib/queries";
export const dynamic = "force-dynamic";
@@ -456,11 +457,11 @@ export default async function AccueilPage() {
Rejoignez plus de 120 commerçants et entreprises qui font réseau, se rencontrent et réussissent
ensemble.
</p>
<a
href="#"
<MembershipModalButton
label="Devenir adhérent"
className="font-display lift-cta"
style={{
textDecoration: "none",
border: "none",
background: "#fff",
color: "#9a6638",
fontWeight: 700,
@@ -469,9 +470,7 @@ export default async function AccueilPage() {
borderRadius: 12,
display: "inline-block",
}}
>
Devenir adhérent
</a>
/>
</Reveal>
</section>
</div>