144 lines
5.5 KiB
TypeScript
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>
|
|
);
|
|
}
|