Files
PleinR/src/app/backend/adherents/AddMemberPanel.tsx
T
Claude 7e22ec50d4 Implémente le site Plein R (Accueil + Backend) sur Next.js + Postgres + Docker
Application unique Next.js (App Router, TypeScript) servant :
- Accueil public : héro, recherche, métiers, promotions, mises à l'honneur
  (données issues de Postgres)
- Backend authentifié avec RBAC : tableau de bord, adhérents (CRUD), modération
  des promotions, administrateurs, espace adhérent (publication avec aperçu)

Technique :
- Drizzle ORM + drizzle-kit (migrations versionnées) sur driver pg
- Auth.js v5 (credentials, sessions JWT), rôles admin/moderator/editor/member
- Middleware de protection de /backend + gardes par rôle dans chaque vue
- Server actions pour toutes les mutations (vérif auth + capacité + revalidate)
- Design fidèle aux maquettes (design-system CSS, animations, responsive)

Conteneurisation :
- 1 conteneur applicatif (sortie standalone) + 1 conteneur Postgres externe
- docker-compose avec healthcheck ; migrations + seed au démarrage
- scripts migrate/seed bundlés (esbuild) pour un runtime sans devDeps

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XwfhYHzC9hQWPDnQ2p53GL
2026-06-26 12:06:21 +00:00

77 lines
2.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { useState } from "react";
import { addMember } from "../actions";
export function AddMemberPanel({
categories,
}: {
categories: { id: number; label: string }[];
}) {
const [open, setOpen] = useState(false);
return (
<div style={{ display: "flex", flexDirection: "column", gap: 0 }}>
<div style={{ display: "flex", justifyContent: "flex-end" }}>
<button
onClick={() => setOpen((o) => !o)}
className="font-display"
style={{ border: "none", background: "#13324F", color: "#fff", fontWeight: 700, fontSize: 14, padding: "11px 18px", borderRadius: 10, cursor: "pointer" }}
>
{open ? "× Fermer" : "+ Ajouter un adhérent"}
</button>
</div>
{open && (
<form
action={async (fd) => {
await addMember(fd);
setOpen(false);
}}
style={{ background: "#fff", border: "1px solid #e6dcc6", borderRadius: 16, padding: 22, marginTop: 14 }}
>
<div className="grid grid-2" style={{ gap: 16 }}>
<div>
<label className="field-label">Nom de l&apos;adhérent</label>
<input name="name" required placeholder="ex : Au Bon Pain" className="field" />
</div>
<div>
<label className="field-label">E-mail</label>
<input name="email" type="email" placeholder="contact@exemple.fr" className="field" />
</div>
<div>
<label className="field-label">Catégorie</label>
<select name="categoryId" className="field" defaultValue="">
<option value="">—</option>
{categories.map((c) => (
<option key={c.id} value={c.id}>
{c.label}
</option>
))}
</select>
</div>
<div>
<label className="field-label">Commune</label>
<input name="city" placeholder="ex : Pompey" className="field" />
</div>
<div>
<label className="field-label">Statut</label>
<select name="status" className="field" defaultValue="pending">
<option value="pending">En attente</option>
<option value="active">Actif</option>
</select>
</div>
</div>
<button
type="submit"
className="font-display"
style={{ marginTop: 18, border: "none", background: "#9a6638", color: "#fff", fontWeight: 700, fontSize: 14.5, padding: "12px 22px", borderRadius: 11, cursor: "pointer" }}
>
Enregistrer l&apos;adhérent
</button>
</form>
)}
</div>
);
}