Files
anouma/components/TurnstileWidget.tsx
T

104 lines
2.9 KiB
TypeScript

"use client";
import Script from "next/script";
import { useCallback, useEffect, useRef, useState } from "react";
type TurnstileWidgetId = string;
type TurnstileApi = {
render: (
container: HTMLElement,
options: {
sitekey: string;
action: string;
callback: (token: string) => void;
"expired-callback": () => void;
"error-callback": () => void;
},
) => TurnstileWidgetId;
reset: (widgetId: TurnstileWidgetId) => void;
};
declare global {
interface Window {
turnstile?: TurnstileApi;
}
}
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]);
const render = useCallback(() => {
if (!siteKey || !containerRef.current || widgetIdRef.current !== null) return;
if (!window.turnstile) {
setLoadError(true);
return;
}
try {
widgetIdRef.current = window.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);
}
}, [action, siteKey]);
return (
<div className="space-y-2" aria-live="polite">
<Script
src="https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit"
strategy="afterInteractive"
onReady={render}
onError={() => setLoadError(true)}
/>
<div ref={containerRef} />
{!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>
);
}