mirror of
https://github.com/beilunyang/moemail.git
synced 2026-05-11 10:00:25 +08:00
105 lines
2.6 KiB
TypeScript
105 lines
2.6 KiB
TypeScript
"use client"
|
|
|
|
import { useEffect, useRef } from "react"
|
|
import { cn } from "@/lib/utils"
|
|
|
|
interface TurnstileProps {
|
|
siteKey: string
|
|
onVerify: (token: string) => void
|
|
onExpire?: () => void
|
|
resetSignal?: number
|
|
className?: string
|
|
}
|
|
|
|
export function Turnstile({
|
|
siteKey,
|
|
onVerify,
|
|
onExpire,
|
|
resetSignal,
|
|
className,
|
|
}: TurnstileProps) {
|
|
const containerRef = useRef<HTMLDivElement | null>(null)
|
|
const widgetIdRef = useRef<string>(null)
|
|
|
|
useEffect(() => {
|
|
if (!siteKey) return
|
|
|
|
const renderWidget = () => {
|
|
if (!containerRef.current || !window.turnstile) return
|
|
|
|
if (widgetIdRef.current) {
|
|
window.turnstile.reset(widgetIdRef.current)
|
|
return
|
|
}
|
|
|
|
widgetIdRef.current = window.turnstile.render(containerRef.current, {
|
|
sitekey: siteKey,
|
|
theme: "auto",
|
|
callback: (token: string) => onVerify(token),
|
|
"error-callback": () => onVerify(""),
|
|
"expired-callback": () => {
|
|
onVerify("")
|
|
onExpire?.()
|
|
},
|
|
})
|
|
}
|
|
|
|
const existingScript = document.querySelector<HTMLScriptElement>('script[data-turnstile="true"]')
|
|
|
|
if (window.turnstile) {
|
|
renderWidget()
|
|
} else if (existingScript) {
|
|
const handleExistingScriptLoad = () => renderWidget()
|
|
|
|
if (existingScript.dataset.loaded === "true") {
|
|
renderWidget()
|
|
} else {
|
|
existingScript.addEventListener("load", handleExistingScriptLoad)
|
|
}
|
|
|
|
return () => {
|
|
existingScript.removeEventListener("load", handleExistingScriptLoad)
|
|
}
|
|
} else {
|
|
const script = document.createElement("script")
|
|
script.src = "https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit"
|
|
script.async = true
|
|
script.defer = true
|
|
script.dataset.turnstile = "true"
|
|
const handleScriptLoad = () => {
|
|
script.dataset.loaded = "true"
|
|
renderWidget()
|
|
}
|
|
script.addEventListener("load", handleScriptLoad)
|
|
document.head.appendChild(script)
|
|
|
|
return () => {
|
|
script.removeEventListener("load", handleScriptLoad)
|
|
}
|
|
}
|
|
|
|
return () => {
|
|
if (widgetIdRef.current && window.turnstile) {
|
|
window.turnstile.remove(widgetIdRef.current)
|
|
widgetIdRef.current = null
|
|
}
|
|
}
|
|
}, [siteKey, onExpire, onVerify])
|
|
|
|
useEffect(() => {
|
|
if (resetSignal === undefined) return
|
|
if (widgetIdRef.current && window.turnstile) {
|
|
window.turnstile.reset(widgetIdRef.current)
|
|
}
|
|
onVerify("")
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [resetSignal])
|
|
|
|
return (
|
|
<div
|
|
ref={containerRef}
|
|
className={cn("flex justify-center", className)}
|
|
/>
|
|
)
|
|
}
|