Files
Calbook/components/booking/cancel-form.tsx

144 lines
5.5 KiB
TypeScript

"use client";
import { useState } from "react";
import { CheckCircle2, XCircle } from "lucide-react";
import { PublicFooter } from "@/components/layout/public-footer";
export function CancelForm({
initialToken = "",
companyName = "CalBook",
footerPrivacyLabel = "Datenschutz",
footerPrivacyUrl = "/datenschutz",
footerImprintLabel = "Impressum",
footerImprintUrl = "/impressum",
footerCopyrightText = "© {{year}} {{companyName}}"
}: {
initialToken?: string;
companyName?: string;
footerPrivacyLabel?: string;
footerPrivacyUrl?: string;
footerImprintLabel?: string;
footerImprintUrl?: string;
footerCopyrightText?: string;
}) {
const [token, setToken] = useState(initialToken);
const [status, setStatus] = useState<"idle" | "loading" | "success" | "error">("idle");
const [errorMsg, setErrorMsg] = useState("");
async function onCancel() {
if (!token.trim()) {
setStatus("error");
setErrorMsg("Bitte Token aus der E-Mail eingeben.");
return;
}
setStatus("loading");
setErrorMsg("");
try {
const res = await fetch("/api/public/stornieren", {
method: "POST",
headers: {
"content-type": "application/json"
},
body: JSON.stringify({
token: token.trim()
})
});
const data = await res.json();
if (!res.ok) {
setStatus("error");
setErrorMsg(data?.message ?? "Stornierung fehlgeschlagen.");
return;
}
setStatus("success");
} catch {
setStatus("error");
setErrorMsg("Netzwerkfehler bei der Stornierung.");
}
}
return (
<div className="min-h-screen bg-slate-50 flex flex-col font-sans">
<div className="flex-1 flex flex-col items-center justify-center p-4">
<div className="mb-6 flex items-center gap-2">
<div className="h-8 w-8 rounded-lg flex items-center justify-center" style={{ backgroundColor: "var(--accent)" }}>
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
</div>
<h1 className="text-lg font-black text-slate-900">{companyName}</h1>
</div>
<div className="bg-white rounded-2xl shadow-xl w-full max-w-md p-8 text-center ring-1 ring-slate-100">
{status === "loading" ? (
<div className="flex flex-col items-center">
<div className="w-12 h-12 border-4 border-indigo-200 border-t-indigo-600 rounded-full animate-spin mb-4" />
<h2 className="text-xl font-bold text-slate-900">Termin wird storniert...</h2>
<p className="text-slate-500 mt-2 text-sm">
Bitte warten, wir stornieren deinen Termin im System.
</p>
</div>
) : null}
{status === "success" ? (
<div className="flex flex-col items-center">
<CheckCircle2 className="w-16 h-16 text-emerald-500 mb-4" />
<h2 className="text-2xl font-black text-slate-900 tracking-tight">Erfolgreich storniert!</h2>
<p className="text-slate-600 mt-3 text-sm leading-relaxed">
Dein Termin wurde erfolgreich abgesagt und aus unserem Kalender entfernt.
</p>
</div>
) : null}
{status === "error" ? (
<div className="flex flex-col items-center">
<XCircle className="w-16 h-16 text-red-500 mb-4" />
<h2 className="text-2xl font-black text-slate-900 tracking-tight">Ein Fehler ist aufgetreten</h2>
<p className="text-slate-600 mt-3 text-sm leading-relaxed">{errorMsg}</p>
<button
type="button"
onClick={() => setStatus("idle")}
className="mt-6 h-10 px-5 rounded-xl border border-slate-200 bg-slate-50 text-sm font-bold text-slate-700 hover:bg-slate-100"
>
Erneut versuchen
</button>
</div>
) : null}
{status === "idle" ? (
<div className="space-y-4 text-left">
<h2 className="text-2xl font-black text-slate-900 tracking-tight text-center">Termin stornieren</h2>
<p className="text-slate-600 text-sm text-center">
Bitte den Stornierungs-Token aus deiner E-Mail eingeben.
</p>
<input
value={token}
onChange={(event) => setToken(event.target.value)}
placeholder="Token"
className="w-full h-11 px-4 bg-slate-50 border border-slate-200 rounded-xl text-sm focus:outline-none focus:border-indigo-600 focus:ring-1 focus:ring-indigo-600 transition-all font-medium text-slate-900 placeholder:text-slate-400"
/>
<button
type="button"
onClick={() => void onCancel()}
className="w-full h-11 bg-slate-900 text-white rounded-xl flex items-center justify-center font-bold hover:bg-slate-800 transition-all"
>
Jetzt stornieren
</button>
</div>
) : null}
</div>
</div>
<PublicFooter
companyName={companyName}
privacyLabel={footerPrivacyLabel}
privacyHref={footerPrivacyUrl}
imprintLabel={footerImprintLabel}
imprintHref={footerImprintUrl}
copyrightTemplate={footerCopyrightText}
/>
</div>
);
}