diff options
| author | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-06-27 01:16:20 +0000 |
|---|---|---|
| committer | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-06-27 01:16:20 +0000 |
| commit | e9897d416b3e7327bbd4d4aef887eee37751ae82 (patch) | |
| tree | bd20ce6eadf9b21755bd7425492d2d31c7700a0e /components/auth/simple-reauth-modal.tsx | |
| parent | 3bf1952c1dad9d479bb8b22031b06a7434d37c37 (diff) | |
(대표님) 20250627 오전 10시 작업사항
Diffstat (limited to 'components/auth/simple-reauth-modal.tsx')
| -rw-r--r-- | components/auth/simple-reauth-modal.tsx | 193 |
1 files changed, 193 insertions, 0 deletions
diff --git a/components/auth/simple-reauth-modal.tsx b/components/auth/simple-reauth-modal.tsx new file mode 100644 index 00000000..f00674e3 --- /dev/null +++ b/components/auth/simple-reauth-modal.tsx @@ -0,0 +1,193 @@ +// components/auth/simple-reauth-modal.tsx +"use client" + +import * as React from "react" +import { zodResolver } from "@hookform/resolvers/zod" +import { useForm } from "react-hook-form" +import { z } from "zod" +import { verifyExternalCredentials } from "@/lib/users/auth/verifyCredentails" + +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog" +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form" +import { Input } from "@/components/ui/input" +import { Button } from "@/components/ui/button" +import { toast } from "@/hooks/use-toast" +import { Shield, AlertCircle } from "lucide-react" + +const reAuthSchema = z.object({ + password: z.string().min(1, "Password is required"), +}) + +type ReAuthFormValues = z.infer<typeof reAuthSchema> + +interface SimpleReAuthModalProps { + isOpen: boolean + onSuccess: () => void + userEmail: string +} + +export function SimpleReAuthModal({ + isOpen, + onSuccess, + userEmail +}: SimpleReAuthModalProps) { + const [isLoading, setIsLoading] = React.useState(false) + const [attemptCount, setAttemptCount] = React.useState(0) + + const form = useForm<ReAuthFormValues>({ + resolver: zodResolver(reAuthSchema), + defaultValues: { + password: "", + }, + }) + + async function onSubmit(data: ReAuthFormValues) { + setIsLoading(true) + + try { + // 직접 인증 함수 호출 (API 호출 없이) + const authResult = await verifyExternalCredentials( + userEmail, + data.password + ) + + if (!authResult.success || !authResult.user) { + setAttemptCount(prev => prev + 1) + + if (attemptCount >= 2) { + toast({ + title: "Too many failed attempts", + description: "Please wait a moment before trying again.", + variant: "destructive", + }) + setTimeout(() => setAttemptCount(0), 30000) + return + } + + toast({ + title: "Authentication failed", + description: `Invalid password. ${2 - attemptCount} attempts remaining.`, + variant: "destructive", + }) + + form.setError("password", { + type: "manual", + message: "Invalid password" + }) + } else { + // 인증 성공 + setAttemptCount(0) + onSuccess() + form.reset() + + toast({ + title: "Authentication successful", + description: "You can now access account settings.", + }) + } + } catch (error) { + console.error("Re-authentication error:", error) + toast({ + title: "Error", + description: "An unexpected error occurred. Please try again.", + variant: "destructive", + }) + } finally { + setIsLoading(false) + } + } + + React.useEffect(() => { + if (!isOpen) { + form.reset() + setAttemptCount(0) + } + }, [isOpen, form]) + + return ( + <Dialog open={isOpen} onOpenChange={() => {}}> + <DialogContent className="sm:max-w-[400px]"> + <DialogHeader> + <DialogTitle className="flex items-center gap-2"> + <Shield className="h-5 w-5 text-amber-600" /> + Verify Your Password + </DialogTitle> + <DialogDescription> + Please enter your password to access account settings. + </DialogDescription> + </DialogHeader> + + <Form {...form}> + <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4"> + <div className="rounded-lg bg-blue-50 border border-blue-200 p-3"> + <p className="text-sm text-blue-800"> + <strong>Email:</strong> {userEmail} + </p> + </div> + + {attemptCount >= 2 && ( + <div className="rounded-lg bg-red-50 border border-red-200 p-3"> + <div className="flex items-center gap-2"> + <AlertCircle className="h-4 w-4 text-red-500" /> + <p className="text-sm text-red-800"> + Too many failed attempts. Please wait 30 seconds. + </p> + </div> + </div> + )} + + <FormField + control={form.control} + name="password" + render={({ field }) => ( + <FormItem> + <FormLabel>Password</FormLabel> + <FormControl> + <Input + type="password" + placeholder="Enter your password" + disabled={attemptCount >= 3 || isLoading} + {...field} + autoFocus + /> + </FormControl> + <FormMessage /> + </FormItem> + )} + /> + + <Button + type="submit" + className="w-full" + disabled={isLoading || attemptCount >= 3} + > + {isLoading ? ( + <> + <div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white mr-2"></div> + Verifying... + </> + ) : attemptCount >= 3 ? ( + "Please wait..." + ) : ( + "Verify" + )} + </Button> + </form> + </Form> + </DialogContent> + </Dialog> + ) +}
\ No newline at end of file |
