finance-calculator/components/captcha-dialog.tsx
2025-06-14 21:34:28 +08:00

182 lines
5.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client"
import { useState, useEffect } from "react"
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogPortal,
DialogOverlay,
} from "@/components/ui/dialog"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Alert, AlertDescription } from "@/components/ui/alert"
import { Shield, RefreshCw, AlertCircle } from "lucide-react"
import * as DialogPrimitive from "@radix-ui/react-dialog"
interface CaptchaDialogProps {
open: boolean
onOpenChange: (open: boolean) => void
onVerify: () => void
onCancel: () => void
}
export function CaptchaDialog({
open,
onOpenChange,
onVerify,
onCancel
}: CaptchaDialogProps) {
const [num1, setNum1] = useState(0)
const [num2, setNum2] = useState(0)
const [userAnswer, setUserAnswer] = useState("")
const [error, setError] = useState("")
const generateCaptcha = () => {
const newNum1 = Math.floor(Math.random() * 10) + 1
const newNum2 = Math.floor(Math.random() * 10) + 1
setNum1(newNum1)
setNum2(newNum2)
setUserAnswer("")
setError("")
}
useEffect(() => {
if (open) {
generateCaptcha()
}
}, [open])
const handleVerify = () => {
const correctAnswer = num1 + num2
const inputAnswer = parseInt(userAnswer)
if (isNaN(inputAnswer) || inputAnswer !== correctAnswer) {
setError("验证码错误,请重新计算")
generateCaptcha()
return
}
setError("")
onVerify()
onOpenChange(false)
}
const handleCancel = () => {
setUserAnswer("")
setError("")
onCancel()
onOpenChange(false)
}
const handleKeyPress = (e: React.KeyboardEvent) => {
if (e.key === 'Enter') {
handleVerify()
}
}
// 防止第一次打开时的遮罩层问题
const handleOpenChange = (newOpen: boolean) => {
if (!newOpen) {
setUserAnswer("")
setError("")
onCancel()
}
onOpenChange(newOpen)
}
return (
<Dialog open={open} onOpenChange={handleOpenChange}>
<DialogPortal>
<DialogOverlay className="fixed inset-0 z-[100] bg-black/80" />
<DialogPrimitive.Content
className="fixed left-[50%] top-[50%] z-[101] grid w-full max-w-md translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg focus:outline-none"
onPointerDownOutside={(e) => e.preventDefault()}
>
<DialogHeader>
<DialogTitle className="flex items-center gap-2 text-foreground">
<Shield className="h-5 w-5 text-blue-500 dark:text-blue-400" />
</DialogTitle>
<DialogDescription>
使
</DialogDescription>
</DialogHeader>
<div className="space-y-4">
<Alert className="border-blue-200 bg-blue-50 dark:border-blue-800 dark:bg-blue-950/50">
<AlertCircle className="h-4 w-4" />
<AlertDescription>
<strong></strong>
</AlertDescription>
</Alert>
<div className="space-y-3">
<div className="flex items-center justify-center p-6 bg-muted/50 rounded-lg border">
<div className="text-center">
<div className="text-2xl font-bold text-foreground mb-2">
{num1} + {num2} = ?
</div>
<div className="flex items-center justify-center gap-2">
<Button
variant="ghost"
size="sm"
onClick={generateCaptcha}
className="text-muted-foreground hover:text-foreground"
>
<RefreshCw className="h-4 w-4 mr-1" />
</Button>
</div>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="captcha-answer" className="text-foreground">
</Label>
<Input
id="captcha-answer"
type="number"
placeholder="输入计算结果"
value={userAnswer}
onChange={(e) => setUserAnswer(e.target.value)}
onKeyDown={handleKeyPress}
className="text-center text-lg font-medium"
autoFocus
/>
</div>
{error && (
<Alert className="border-red-200 bg-red-50 text-red-800 dark:border-red-800 dark:bg-red-950 dark:text-red-200">
<AlertCircle className="h-4 w-4" />
<AlertDescription>{error}</AlertDescription>
</Alert>
)}
</div>
</div>
<DialogFooter className="flex justify-between">
<Button
variant="outline"
onClick={handleCancel}
>
</Button>
<Button
onClick={handleVerify}
disabled={!userAnswer.trim()}
className="bg-blue-600 hover:bg-blue-700 dark:bg-blue-600 dark:hover:bg-blue-700"
>
</Button>
</DialogFooter>
</DialogPrimitive.Content>
</DialogPortal>
</Dialog>
)
}