122 lines
No EOL
3.6 KiB
TypeScript
122 lines
No EOL
3.6 KiB
TypeScript
"use client";
|
|
import { useRef, useState } from "react";
|
|
import type { FormHTMLAttributes, PropsWithChildren } from "react";
|
|
|
|
type Props = PropsWithChildren<
|
|
FormHTMLAttributes<HTMLFormElement> & {
|
|
confirmTitle: string;
|
|
confirmMessage: string;
|
|
confirmCta?: string;
|
|
cancelCta?: string;
|
|
disabled?: boolean;
|
|
submitLabel?: string; // when you want to render a default submit button
|
|
buttonClassName?: string;
|
|
// Nouveau: support pour les actions async (API calls)
|
|
action?: string | ((formData: FormData) => Promise<void>);
|
|
}
|
|
>;
|
|
|
|
export default function ConfirmableForm({
|
|
children,
|
|
confirmTitle,
|
|
confirmMessage,
|
|
confirmCta = "Confirmer",
|
|
cancelCta = "Annuler",
|
|
disabled,
|
|
submitLabel,
|
|
buttonClassName,
|
|
action,
|
|
...formProps
|
|
}: Props) {
|
|
const formRef = useRef<HTMLFormElement>(null);
|
|
const [open, setOpen] = useState(false);
|
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
|
|
function onSubmitIntercept(e: React.FormEvent<HTMLFormElement>) {
|
|
if (disabled || isSubmitting) return; // disabled => laisse le navigateur gérer
|
|
e.preventDefault();
|
|
setOpen(true);
|
|
}
|
|
|
|
async function doSubmit() {
|
|
if (!formRef.current || isSubmitting) return;
|
|
|
|
setIsSubmitting(true);
|
|
setOpen(false);
|
|
|
|
try {
|
|
if (typeof action === "function") {
|
|
// Action async personnalisée
|
|
const formData = new FormData(formRef.current);
|
|
await action(formData);
|
|
} else if (typeof action === "string") {
|
|
// URL d'action classique
|
|
formRef.current.action = action;
|
|
formRef.current.submit();
|
|
} else {
|
|
// Soumission classique
|
|
formRef.current.submit();
|
|
}
|
|
} catch (error) {
|
|
console.error("Erreur lors de la soumission:", error);
|
|
// L'erreur est gérée par le composant parent via l'action
|
|
} finally {
|
|
setIsSubmitting(false);
|
|
}
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<form ref={formRef} onSubmit={onSubmitIntercept} {...formProps}>
|
|
{children}
|
|
{submitLabel && (
|
|
<button
|
|
type="submit"
|
|
disabled={disabled || isSubmitting}
|
|
className={
|
|
buttonClassName ||
|
|
"px-3 py-1 rounded bg-slate-900 text-white text-xs hover:bg-slate-700 disabled:opacity-50"
|
|
}
|
|
>
|
|
{isSubmitting ? "..." : submitLabel}
|
|
</button>
|
|
)}
|
|
</form>
|
|
|
|
{open && (
|
|
<div className="fixed inset-0 z-[100] grid place-items-center">
|
|
{/* backdrop */}
|
|
<div
|
|
className="absolute inset-0 bg-black/40"
|
|
onClick={() => setOpen(false)}
|
|
aria-hidden
|
|
/>
|
|
|
|
{/* modal */}
|
|
<div className="relative z-[101] w-[min(92vw,480px)] rounded-2xl border bg-white p-5 shadow-xl">
|
|
<h3 className="text-base font-semibold mb-1">{confirmTitle}</h3>
|
|
<p className="text-sm text-slate-600">{confirmMessage}</p>
|
|
<div className="mt-4 flex justify-end gap-2">
|
|
<button
|
|
type="button"
|
|
onClick={() => setOpen(false)}
|
|
disabled={isSubmitting}
|
|
className="px-3 py-1.5 rounded-lg border text-sm disabled:opacity-50"
|
|
>
|
|
{cancelCta}
|
|
</button>
|
|
<button
|
|
type="button"
|
|
onClick={doSubmit}
|
|
disabled={isSubmitting}
|
|
className="px-3 py-1.5 rounded-lg bg-rose-600 text-white text-sm hover:bg-rose-700 disabled:opacity-50"
|
|
>
|
|
{isSubmitting ? "..." : confirmCta}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</>
|
|
);
|
|
} |