Files
PleinR/src/app/login/page.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

127 lines
3.6 KiB
TypeScript

"use client";
import { useActionState } from "react";
import Link from "next/link";
import { authenticate } from "./actions";
export default function LoginPage() {
const [errorMessage, formAction, isPending] = useActionState(
authenticate,
undefined
);
return (
<div
style={{
minHeight: "100vh",
background: "#EDE7D7",
display: "flex",
alignItems: "center",
justifyContent: "center",
padding: 24,
fontFamily: "'Public Sans',sans-serif",
}}
>
<div style={{ width: "100%", maxWidth: 420 }}>
<div style={{ textAlign: "center", marginBottom: 22 }}>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img src="/assets/logo.svg" alt="Plein R" style={{ height: 92, width: "auto" }} />
</div>
<div
style={{
background: "#fff",
border: "1px solid #e6dcc6",
borderRadius: 18,
padding: 30,
boxShadow: "0 26px 60px -40px rgba(40,30,15,0.5)",
}}
>
<h1
className="font-display"
style={{ fontWeight: 800, fontSize: 24, margin: "0 0 4px", color: "#26201a" }}
>
Espace adhérent
</h1>
<p style={{ margin: "0 0 22px", fontSize: 14, color: "#9a8d72" }}>
Connectez-vous pour accéder à votre espace.
</p>
<form action={formAction}>
<label className="field-label" htmlFor="email">
E-mail
</label>
<input
id="email"
name="email"
type="email"
required
autoComplete="email"
placeholder="prenom@plein-r.fr"
className="field"
style={{ marginBottom: 16 }}
/>
<label className="field-label" htmlFor="password">
Mot de passe
</label>
<input
id="password"
name="password"
type="password"
required
autoComplete="current-password"
placeholder="••••••••"
className="field"
style={{ marginBottom: 18 }}
/>
{errorMessage && (
<div
style={{
background: "#fbe9e6",
border: "1px solid #f0c4bb",
color: "#d8472b",
borderRadius: 10,
padding: "10px 13px",
fontSize: 13.5,
fontWeight: 600,
marginBottom: 16,
}}
>
{errorMessage}
</div>
)}
<button
type="submit"
disabled={isPending}
className="font-display"
style={{
width: "100%",
border: "none",
background: "#13324F",
color: "#fff",
fontWeight: 700,
fontSize: 15,
padding: 14,
borderRadius: 11,
cursor: isPending ? "default" : "pointer",
opacity: isPending ? 0.7 : 1,
}}
>
{isPending ? "Connexion…" : "Se connecter"}
</button>
</form>
</div>
<div style={{ textAlign: "center", marginTop: 18 }}>
<Link href="/" style={{ fontSize: 13.5, color: "#6f6450", textDecoration: "none" }}>
← Retour au site
</Link>
</div>
</div>
</div>
);
}