Files
anouma/components/TurnstileWidget.tsx
T

88 lines
2.7 KiB
TypeScript

"use client";
import { useEffect, useRef, useState } from "react";
import { loadTurnstile, type TurnstileWidgetId } from "@/lib/turnstile/client";
type TurnstileWidgetProps = {
siteKey: string;
action: string;
resetKey: number;
onTokenChange: (token: string | null) => void;
};
export function TurnstileWidget({ siteKey, action, resetKey, onTokenChange }: TurnstileWidgetProps) {
const containerRef = useRef<HTMLDivElement>(null);
const widgetIdRef = useRef<TurnstileWidgetId | null>(null);
const onTokenChangeRef = useRef(onTokenChange);
const [loadError, setLoadError] = useState(false);
const [verificationStatus, setVerificationStatus] = useState<"idle" | "ready" | "expired">("idle");
useEffect(() => {
onTokenChangeRef.current = onTokenChange;
}, [onTokenChange]);
useEffect(() => {
if (resetKey === 0) return;
if (widgetIdRef.current !== null && window.turnstile) {
window.turnstile.reset(widgetIdRef.current);
}
onTokenChangeRef.current(null);
}, [resetKey]);
useEffect(() => {
if (!siteKey || !containerRef.current || widgetIdRef.current !== null) return;
let cancelled = false;
loadTurnstile()
.then((turnstile) => {
if (cancelled || !containerRef.current || widgetIdRef.current !== null) return;
try {
widgetIdRef.current = turnstile.render(containerRef.current, {
sitekey: siteKey,
action,
callback: (token) => {
setVerificationStatus("ready");
onTokenChangeRef.current(token);
},
"expired-callback": () => {
setVerificationStatus("expired");
onTokenChangeRef.current(null);
},
"error-callback": () => {
onTokenChangeRef.current(null);
setLoadError(true);
},
});
} catch {
setLoadError(true);
onTokenChangeRef.current(null);
}
})
.catch(() => {
if (cancelled) return;
setLoadError(true);
onTokenChangeRef.current(null);
});
return () => {
cancelled = true;
};
}, [action, siteKey]);
return (
<div className="space-y-2" aria-live="polite">
<div ref={containerRef} data-turnstile-container="true" />
{!siteKey || loadError ? (
<p className="text-sm text-red-700" role="alert">
Die Sicherheitsprüfung ist momentan nicht verfügbar. Bitte lade die Seite neu und versuche es erneut.
</p>
) : verificationStatus === "expired" ? (
<p className="text-sm text-red-700" role="alert">
Die Sicherheitsprüfung ist abgelaufen. Bitte bestätige sie erneut.
</p>
) : null}
</div>
);
}