mirror of
https://github.com/R0m1k3/PleinR.git
synced 2026-10-15 11:20:06 +02:00
Add association meetings and GDPR settings
This commit is contained in:
1 parent
b4c8967a3c
commit
28ada88155
20 files changed
+2663
-27
No files matched your search
@@ -0,0 +1,167 @@
|
||||
"use client";
|
||||
|
||||
import { useRef, useState } from "react";
|
||||
|
||||
export function ImageConsentForm({
|
||||
saveAction,
|
||||
defaultName,
|
||||
compact = false,
|
||||
}: {
|
||||
saveAction: (formData: FormData) => Promise<void>;
|
||||
defaultName: string;
|
||||
compact?: boolean;
|
||||
}) {
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||
const [drawing, setDrawing] = useState(false);
|
||||
const [signature, setSignature] = useState("");
|
||||
const [error, setError] = useState("");
|
||||
|
||||
function point(event: React.PointerEvent<HTMLCanvasElement>) {
|
||||
const canvas = canvasRef.current;
|
||||
if (!canvas) return null;
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
return {
|
||||
x: ((event.clientX - rect.left) / rect.width) * canvas.width,
|
||||
y: ((event.clientY - rect.top) / rect.height) * canvas.height,
|
||||
};
|
||||
}
|
||||
|
||||
function draw(event: React.PointerEvent<HTMLCanvasElement>) {
|
||||
if (!drawing) return;
|
||||
const p = point(event);
|
||||
const ctx = canvasRef.current?.getContext("2d");
|
||||
if (!p || !ctx) return;
|
||||
ctx.lineTo(p.x, p.y);
|
||||
ctx.strokeStyle = "#13324F";
|
||||
ctx.lineWidth = 3;
|
||||
ctx.lineCap = "round";
|
||||
ctx.stroke();
|
||||
setSignature(canvasRef.current?.toDataURL("image/png") ?? "");
|
||||
}
|
||||
|
||||
function start(event: React.PointerEvent<HTMLCanvasElement>) {
|
||||
const p = point(event);
|
||||
const ctx = canvasRef.current?.getContext("2d");
|
||||
if (!p || !ctx) return;
|
||||
event.currentTarget.setPointerCapture(event.pointerId);
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(p.x, p.y);
|
||||
setDrawing(true);
|
||||
}
|
||||
|
||||
function clear() {
|
||||
const canvas = canvasRef.current;
|
||||
const ctx = canvas?.getContext("2d");
|
||||
if (canvas && ctx) ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
setSignature("");
|
||||
setError("");
|
||||
}
|
||||
|
||||
function onSubmit(event: React.FormEvent<HTMLFormElement>) {
|
||||
const submitter = (event.nativeEvent as SubmitEvent).submitter as HTMLButtonElement | null;
|
||||
if (submitter?.value === "accepted" && !signature) {
|
||||
event.preventDefault();
|
||||
setError("Signez dans le cadre pour valider l'autorisation.");
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<form
|
||||
action={saveAction}
|
||||
onSubmit={onSubmit}
|
||||
style={{
|
||||
background: compact ? "#fff" : "#F6F2E8",
|
||||
border: "1px solid #e6dcc6",
|
||||
borderRadius: 16,
|
||||
padding: compact ? 18 : 24,
|
||||
}}
|
||||
>
|
||||
<input type="hidden" name="signaturePng" value={signature} />
|
||||
<div style={{ display: "grid", gap: 14 }}>
|
||||
<div>
|
||||
<label className="field-label">Nom du signataire</label>
|
||||
<input name="signatoryName" defaultValue={defaultName} className="field" required />
|
||||
</div>
|
||||
|
||||
<div style={{ color: "#6c6150", fontSize: 13.5, lineHeight: 1.65 }}>
|
||||
J'autorise l'association Plein R à utiliser mon image dans les supports liés à la vie
|
||||
associative: site internet, réseaux sociaux et supports imprimés. Autorisation gratuite,
|
||||
valable 5 ans, sans vente à des tiers. Je peux retirer mon accord à tout moment.
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="field-label">Signature</label>
|
||||
<canvas
|
||||
ref={canvasRef}
|
||||
width={760}
|
||||
height={190}
|
||||
onPointerDown={start}
|
||||
onPointerMove={draw}
|
||||
onPointerUp={() => setDrawing(false)}
|
||||
onPointerCancel={() => setDrawing(false)}
|
||||
style={{
|
||||
width: "100%",
|
||||
height: compact ? 120 : 150,
|
||||
display: "block",
|
||||
touchAction: "none",
|
||||
background: "#fff",
|
||||
border: "1px dashed #d6c9ad",
|
||||
borderRadius: 12,
|
||||
}}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={clear}
|
||||
style={{
|
||||
marginTop: 8,
|
||||
border: "none",
|
||||
background: "transparent",
|
||||
color: "#9a6638",
|
||||
fontSize: 12.5,
|
||||
fontWeight: 700,
|
||||
cursor: "pointer",
|
||||
}}
|
||||
>
|
||||
Effacer la signature
|
||||
</button>
|
||||
{error && <div style={{ color: "#d8472b", fontSize: 12.5, marginTop: 4 }}>{error}</div>}
|
||||
</div>
|
||||
|
||||
<div style={{ display: "flex", gap: 10, flexWrap: "wrap" }}>
|
||||
<button
|
||||
type="submit"
|
||||
name="decision"
|
||||
value="accepted"
|
||||
style={{
|
||||
border: "none",
|
||||
background: "#13324F",
|
||||
color: "#fff",
|
||||
fontWeight: 800,
|
||||
borderRadius: 10,
|
||||
padding: "11px 17px",
|
||||
cursor: "pointer",
|
||||
}}
|
||||
>
|
||||
J'autorise et je signe
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
name="decision"
|
||||
value="refused"
|
||||
style={{
|
||||
border: "1px solid #e0c3bb",
|
||||
background: "#fff",
|
||||
color: "#d8472b",
|
||||
fontWeight: 800,
|
||||
borderRadius: 10,
|
||||
padding: "11px 17px",
|
||||
cursor: "pointer",
|
||||
}}
|
||||
>
|
||||
Je refuse
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
|
||||
export function PastMeetingGallery({
|
||||
title,
|
||||
photos,
|
||||
}: {
|
||||
title: string;
|
||||
photos: { id: number; imageUrl: string; caption: string | null }[];
|
||||
}) {
|
||||
const [open, setOpen] = useState<number | null>(null);
|
||||
if (photos.length === 0) return null;
|
||||
const current = open == null ? null : photos.find((photo) => photo.id === open);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div style={{ display: "grid", gridTemplateColumns: "repeat(4, 1fr)", gap: 8, marginTop: 14 }}>
|
||||
{photos.slice(0, 4).map((photo, index) => (
|
||||
<button
|
||||
key={photo.id}
|
||||
type="button"
|
||||
onClick={() => setOpen(photo.id)}
|
||||
aria-label={`Ouvrir photo ${index + 1}`}
|
||||
style={{
|
||||
position: "relative",
|
||||
border: "none",
|
||||
padding: 0,
|
||||
aspectRatio: "1 / 1",
|
||||
borderRadius: 8,
|
||||
overflow: "hidden",
|
||||
cursor: "pointer",
|
||||
background: `center/cover no-repeat url(${photo.imageUrl})`,
|
||||
}}
|
||||
>
|
||||
{index === 3 && photos.length > 4 && (
|
||||
<span style={{ position: "absolute", inset: 0, background: "rgba(19,50,79,0.62)", color: "#fff", display: "flex", alignItems: "center", justifyContent: "center", fontWeight: 800 }}>
|
||||
+{photos.length - 3}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{current && (
|
||||
<div
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label={`Galerie ${title}`}
|
||||
onClick={() => setOpen(null)}
|
||||
style={{ position: "fixed", inset: 0, background: "rgba(19,50,79,0.72)", zIndex: 80, padding: 24, display: "flex", alignItems: "center", justifyContent: "center" }}
|
||||
>
|
||||
<div onClick={(event) => event.stopPropagation()} style={{ maxWidth: 980, width: "100%" }}>
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img src={current.imageUrl} alt={current.caption ?? title} style={{ width: "100%", maxHeight: "78vh", objectFit: "contain", background: "#fff", borderRadius: 12 }} />
|
||||
<div style={{ marginTop: 12, display: "flex", justifyContent: "space-between", gap: 12, alignItems: "center" }}>
|
||||
<div style={{ color: "#fff", fontWeight: 700 }}>{current.caption ?? title}</div>
|
||||
<button type="button" onClick={() => setOpen(null)} style={{ border: "none", background: "#fff", color: "#13324F", borderRadius: 999, padding: "9px 16px", fontWeight: 800, cursor: "pointer" }}>
|
||||
Fermer
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -24,6 +24,7 @@ export function SiteFooter() {
|
||||
<div className="footer-links">
|
||||
<Link href="/annuaire">Annuaire</Link>
|
||||
<Link href="/#promotions">Promotions</Link>
|
||||
<Link href="/association">Association</Link>
|
||||
<Link href="/login">Connexion</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -4,7 +4,7 @@ const NAV = [
|
||||
{ key: "accueil", label: "Accueil", href: "/" },
|
||||
{ key: "annuaire", label: "Annuaire", href: "/annuaire" },
|
||||
{ key: "promotions", label: "Promotions", href: "/#promotions" },
|
||||
{ key: "association", label: "L'association", href: "#" },
|
||||
{ key: "association", label: "L'association", href: "/association" },
|
||||
];
|
||||
|
||||
export function SiteHeader({ active, logo = false }: { active?: string; logo?: boolean }) {
|
||||
|
||||
Reference in new issue
Block a user