diff --git a/app/(admin)/_components/header.tsx b/app/(admin)/_components/header.tsx index a8b6e5f..0475a10 100644 --- a/app/(admin)/_components/header.tsx +++ b/app/(admin)/_components/header.tsx @@ -99,19 +99,7 @@ export function Header() { Book Appointment - - - Documentation - + {/* Right Side Actions */} diff --git a/components/ForgotPasswordDialog.tsx b/components/ForgotPasswordDialog.tsx new file mode 100644 index 0000000..db59f4c --- /dev/null +++ b/components/ForgotPasswordDialog.tsx @@ -0,0 +1,459 @@ +"use client"; + +import { useState } from "react"; +import { Button } from "@/components/ui/button"; +import { useAppTheme } from "@/components/ThemeProvider"; +import { Input } from "@/components/ui/input"; +import { + InputOTP, + InputOTPGroup, + InputOTPSlot, +} from "@/components/ui/input-otp"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { Eye, EyeOff, Loader2, X, CheckCircle2 } from "lucide-react"; +import { useAuth } from "@/hooks/useAuth"; +import { + forgotPasswordSchema, + verifyPasswordResetOtpSchema, + resetPasswordSchema, + type ForgotPasswordInput, + type VerifyPasswordResetOtpInput, + type ResetPasswordInput +} from "@/lib/schema/auth"; +import { toast } from "sonner"; + +interface ForgotPasswordDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; + onSuccess?: () => void; +} + +type Step = "request" | "verify" | "reset"; + +export function ForgotPasswordDialog({ open, onOpenChange, onSuccess }: ForgotPasswordDialogProps) { + const { theme } = useAppTheme(); + const isDark = theme === "dark"; + const { + forgotPasswordMutation, + verifyPasswordResetOtpMutation, + resetPasswordMutation, + resendOtpMutation + } = useAuth(); + const [step, setStep] = useState("request"); + const [email, setEmail] = useState(""); + const [otpData, setOtpData] = useState({ + email: "", + otp: "", + }); + const [resetData, setResetData] = useState({ + email: "", + otp: "", + new_password: "", + confirm_password: "", + }); + const [showPassword, setShowPassword] = useState(false); + const [showPassword2, setShowPassword2] = useState(false); + + const handleRequestOtp = async (e: React.FormEvent) => { + e.preventDefault(); + + const validation = forgotPasswordSchema.safeParse({ email }); + if (!validation.success) { + const firstError = validation.error.issues[0]; + toast.error(firstError.message); + return; + } + + try { + await forgotPasswordMutation.mutateAsync({ email }); + setOtpData({ email, otp: "" }); + setResetData({ email, otp: "", new_password: "", confirm_password: "" }); + setStep("verify"); + toast.success("Password reset OTP sent! Please check your email."); + } catch (err) { + const errorMessage = err instanceof Error ? err.message : "Failed to send OTP. Please try again."; + toast.error(errorMessage); + } + }; + + const handleVerifyOtp = async (e: React.FormEvent) => { + e.preventDefault(); + + const emailToVerify = email || otpData.email; + if (!emailToVerify) { + toast.error("Email is required"); + return; + } + + const validation = verifyPasswordResetOtpSchema.safeParse({ + email: emailToVerify, + otp: otpData.otp, + }); + + if (!validation.success) { + const firstError = validation.error.issues[0]; + toast.error(firstError.message); + return; + } + + try { + await verifyPasswordResetOtpMutation.mutateAsync({ + email: emailToVerify, + otp: otpData.otp, + }); + setResetData({ + email: emailToVerify, + otp: otpData.otp, + new_password: "", + confirm_password: "" + }); + setStep("reset"); + toast.success("OTP verified! Please set your new password."); + } catch (err) { + const errorMessage = err instanceof Error ? err.message : "OTP verification failed. Please try again."; + toast.error(errorMessage); + } + }; + + const handleResetPassword = async (e: React.FormEvent) => { + e.preventDefault(); + + const validation = resetPasswordSchema.safeParse(resetData); + if (!validation.success) { + const firstError = validation.error.issues[0]; + toast.error(firstError.message); + return; + } + + try { + await resetPasswordMutation.mutateAsync(resetData); + toast.success("Password reset successful! Please log in with your new password."); + handleDialogChange(false); + if (onSuccess) { + onSuccess(); + } + } catch (err) { + const errorMessage = err instanceof Error ? err.message : "Password reset failed. Please try again."; + toast.error(errorMessage); + } + }; + + const handleResendOtp = async () => { + const emailToResend = email || otpData.email; + if (!emailToResend) { + toast.error("Email is required"); + return; + } + + try { + await resendOtpMutation.mutateAsync({ + email: emailToResend, + context: "password_reset" + }); + toast.success("OTP resent successfully! Please check your email."); + } catch (err) { + const errorMessage = err instanceof Error ? err.message : "Failed to resend OTP"; + toast.error(errorMessage); + } + }; + + const handleOtpChange = (field: keyof VerifyPasswordResetOtpInput, value: string) => { + setOtpData((prev) => ({ ...prev, [field]: value })); + }; + + // Reset step when dialog closes + const handleDialogChange = (isOpen: boolean) => { + if (!isOpen) { + setStep("request"); + setEmail(""); + setOtpData({ email: "", otp: "" }); + setResetData({ email: "", otp: "", new_password: "", confirm_password: "" }); + } + onOpenChange(isOpen); + }; + + return ( + + + {/* Header with Close Button - Fixed */} +
+ + + {step === "request" && "Reset Password"} + {step === "verify" && "Verify OTP"} + {step === "reset" && "Set New Password"} + + + {step === "request" && "Enter your email to receive a password reset code"} + {step === "verify" && "Enter the verification code sent to your email"} + {step === "reset" && "Enter your new password"} + + + {/* Close Button */} + +
+ + {/* Scrollable Content */} +
+ {/* Request OTP Form */} + {step === "request" && ( +
+
+ + setEmail(e.target.value)} + className={`h-11 sm:h-12 text-sm sm:text-base ${isDark ? 'bg-gray-700 border-gray-600 text-white placeholder:text-gray-400' : 'bg-white border-gray-300 text-gray-900'}`} + required + /> +
+ + +
+ )} + + {/* Verify OTP Form */} + {step === "verify" && ( +
+
+
+ +
+

+ Check your email +

+

+ We've sent a 6-digit verification code to {email || otpData.email || "your email address"}. +

+
+
+
+ + {/* Email Field (if not set) */} + {!email && ( +
+ + handleOtpChange("email", e.target.value)} + className={`h-11 sm:h-12 text-sm sm:text-base ${isDark ? 'bg-gray-700 border-gray-600 text-white placeholder:text-gray-400' : 'bg-white border-gray-300 text-gray-900'}`} + required + /> +
+ )} + + {/* OTP Field */} +
+ +
+ handleOtpChange("otp", value)} + > + + + + + + + + + +
+
+ + {/* Resend OTP */} +
+ +
+ + {/* Submit Button */} + + + {/* Back to request */} +
+ +
+
+ )} + + {/* Reset Password Form */} + {step === "reset" && ( +
+ {/* New Password Field */} +
+ +
+ setResetData({ ...resetData, new_password: e.target.value })} + className={`h-11 sm:h-12 pr-12 text-sm sm:text-base ${isDark ? 'bg-gray-700 border-gray-600 text-white placeholder:text-gray-400' : 'bg-white border-gray-300 text-gray-900'}`} + required + /> + +
+
+ + {/* Confirm Password Field */} +
+ +
+ setResetData({ ...resetData, confirm_password: e.target.value })} + className={`h-11 sm:h-12 pr-12 text-sm sm:text-base ${isDark ? 'bg-gray-700 border-gray-600 text-white placeholder:text-gray-400' : 'bg-white border-gray-300 text-gray-900'}`} + required + /> + +
+
+ + {/* Submit Button */} + + + {/* Back to verify */} +
+ +
+
+ )} +
+
+
+ ); +} + diff --git a/components/LoginDialog.tsx b/components/LoginDialog.tsx index ab1ddf0..85ef759 100644 --- a/components/LoginDialog.tsx +++ b/components/LoginDialog.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState } from "react"; +import { useState, useEffect } from "react"; import { Button } from "@/components/ui/button"; import { useAppTheme } from "@/components/ThemeProvider"; import { Input } from "@/components/ui/input"; @@ -13,43 +13,41 @@ import { } from "@/components/ui/dialog"; import { Eye, EyeOff, Loader2, X } from "lucide-react"; import { useAuth } from "@/hooks/useAuth"; -import { loginSchema, registerSchema, type LoginInput, type RegisterInput } from "@/lib/schema/auth"; +import { loginSchema, type LoginInput } from "@/lib/schema/auth"; import { toast } from "sonner"; import { useRouter } from "next/navigation"; +import { ForgotPasswordDialog } from "./ForgotPasswordDialog"; interface LoginDialogProps { open: boolean; onOpenChange: (open: boolean) => void; onLoginSuccess: () => void; + prefillEmail?: string; + onSwitchToSignup?: () => void; } // Login Dialog component -export function LoginDialog({ open, onOpenChange, onLoginSuccess }: LoginDialogProps) { +export function LoginDialog({ open, onOpenChange, onLoginSuccess, prefillEmail, onSwitchToSignup }: LoginDialogProps) { const { theme } = useAppTheme(); const isDark = theme === "dark"; const router = useRouter(); - const { login, register, loginMutation, registerMutation } = useAuth(); - const [isSignup, setIsSignup] = useState(false); + const { login, loginMutation } = useAuth(); const [loginData, setLoginData] = useState({ email: "", password: "", }); - const [signupData, setSignupData] = useState({ - first_name: "", - last_name: "", - email: "", - phone_number: "", - password: "", - password2: "", - }); const [showPassword, setShowPassword] = useState(false); - const [showPassword2, setShowPassword2] = useState(false); - const [rememberMe, setRememberMe] = useState(false); - const [error, setError] = useState(null); + const [forgotPasswordDialogOpen, setForgotPasswordDialogOpen] = useState(false); + + // Pre-fill email if provided + useEffect(() => { + if (prefillEmail && open) { + setLoginData(prev => ({ ...prev, email: prefillEmail })); + } + }, [prefillEmail, open]); const handleLogin = async (e: React.FormEvent) => { e.preventDefault(); - setError(null); // Validate form const validation = loginSchema.safeParse(loginData); @@ -64,9 +62,13 @@ export function LoginDialog({ open, onOpenChange, onLoginSuccess }: LoginDialogP if (result.tokens && result.user) { toast.success("Login successful!"); - setShowPassword(false); - onOpenChange(false); - onLoginSuccess(); + setShowPassword(false); + onOpenChange(false); + // Reset form + setLoginData({ email: "", password: "" }); + // Redirect to user dashboard + router.push("/user/dashboard"); + onLoginSuccess(); } } catch (err) { const errorMessage = err instanceof Error ? err.message : "Login failed. Please try again."; @@ -74,62 +76,16 @@ export function LoginDialog({ open, onOpenChange, onLoginSuccess }: LoginDialogP } }; - const handleSignup = async (e: React.FormEvent) => { - e.preventDefault(); - setError(null); - - // Validate form - const validation = registerSchema.safeParse(signupData); - if (!validation.success) { - const firstError = validation.error.issues[0]; - toast.error(firstError.message); - return; + // Reset form when dialog closes + const handleDialogChange = (isOpen: boolean) => { + if (!isOpen) { + setLoginData({ email: "", password: "" }); } - - try { - const result = await register(signupData); - - if (result.message) { - toast.success("Registration successful! Please check your email for OTP verification."); - // Switch to login after successful registration - setIsSignup(false); - setLoginData({ email: signupData.email, password: "" }); - setSignupData({ - first_name: "", - last_name: "", - email: "", - phone_number: "", - password: "", - password2: "", - }); - } - } catch (err) { - const errorMessage = err instanceof Error ? err.message : "Signup failed. Please try again."; - toast.error(errorMessage); - } - }; - - const handleSwitchToSignup = () => { - setIsSignup(true); - setError(null); - setLoginData({ email: "", password: "" }); - }; - - const handleSwitchToLogin = () => { - setIsSignup(false); - setError(null); - setSignupData({ - first_name: "", - last_name: "", - email: "", - phone_number: "", - password: "", - password2: "", - }); + onOpenChange(isOpen); }; return ( - + - {isSignup ? "Create an account" : "Welcome back"} + Welcome back - {isSignup - ? "Sign up to complete your booking" - : "Please log in to complete your booking"} + Please log in to complete your booking {/* Close Button */} - + {/* Scrollable Content */}
- {/* Signup Form */} - {isSignup ? ( -
- - {/* First Name Field */} -
- - setSignupData({ ...signupData, first_name: e.target.value })} - className={`h-11 sm:h-12 text-sm sm:text-base ${isDark ? 'bg-gray-700 border-gray-600 text-white placeholder:text-gray-400' : 'bg-white border-gray-300 text-gray-900'}`} - required - /> -
- - {/* Last Name Field */} -
- - setSignupData({ ...signupData, last_name: e.target.value })} - className={`h-11 sm:h-12 text-sm sm:text-base ${isDark ? 'bg-gray-700 border-gray-600 text-white placeholder:text-gray-400' : 'bg-white border-gray-300 text-gray-900'}`} - required - /> -
- + {/* Login Form */} + {/* Email Field */}
-
- {/* Phone Field */} -
- - setSignupData({ ...signupData, phone_number: e.target.value })} - className={`h-11 sm:h-12 text-sm sm:text-base ${isDark ? 'bg-gray-700 border-gray-600 text-white placeholder:text-gray-400' : 'bg-white border-gray-300 text-gray-900'}`} - /> -
- {/* Password Field */}
-
- {/* Confirm Password Field */} -
- -
- setSignupData({ ...signupData, password2: e.target.value })} - className={`h-11 sm:h-12 pr-12 text-sm sm:text-base ${isDark ? 'bg-gray-700 border-gray-600 text-white placeholder:text-gray-400' : 'bg-white border-gray-300 text-gray-900'}`} - required - /> - -
-
- {/* Submit Button */} - {/* Switch to Login */} -

- Already have an account?{" "} - -

-
- ) : ( - /* Login Form */ -
- - {/* Email Field */} -
- - setLoginData({ ...loginData, email: e.target.value })} - className={`h-11 sm:h-12 text-sm sm:text-base ${isDark ? 'bg-gray-700 border-gray-600 text-white placeholder:text-gray-400' : 'bg-white border-gray-300 text-gray-900'}`} - required - /> -
- - {/* Password Field */} -
- -
- setLoginData({ ...loginData, password: e.target.value })} - className={`h-11 sm:h-12 pr-12 text-sm sm:text-base ${isDark ? 'bg-gray-700 border-gray-600 text-white placeholder:text-gray-400' : 'bg-white border-gray-300 text-gray-900'}`} - required - /> + {/* Forgot Password */} +
-
- {/* Submit Button */} - - - {/* Remember Me & Forgot Password */} -
- - -
- - {/* Sign Up Prompt */} -

- New to Attune Heart Therapy?{" "} - -

- - )} + {/* Sign Up Prompt */} +

+ New to Attune Heart Therapy?{" "} + +

+
+ + {/* Forgot Password Dialog */} +
); } - diff --git a/components/Navbar.tsx b/components/Navbar.tsx index 368b1a5..04a8a73 100644 --- a/components/Navbar.tsx +++ b/components/Navbar.tsx @@ -6,6 +6,7 @@ import { Heart, Menu, X, LogOut } from "lucide-react"; import { ThemeToggle } from "@/components/ThemeToggle"; import { useEffect, useState } from "react"; import { LoginDialog } from "@/components/LoginDialog"; +import { SignupDialog } from "@/components/SignupDialog"; import { useRouter, usePathname } from "next/navigation"; import Link from "next/link"; import { useAppTheme } from "@/components/ThemeProvider"; @@ -16,6 +17,8 @@ export function Navbar() { const { theme } = useAppTheme(); const isDark = theme === "dark"; const [loginDialogOpen, setLoginDialogOpen] = useState(false); + const [signupDialogOpen, setSignupDialogOpen] = useState(false); + const [prefillEmail, setPrefillEmail] = useState(undefined); const [mobileMenuOpen, setMobileMenuOpen] = useState(false); const router = useRouter(); const pathname = usePathname(); @@ -258,6 +261,28 @@ export function Navbar() { open={loginDialogOpen} onOpenChange={setLoginDialogOpen} onLoginSuccess={handleLoginSuccess} + prefillEmail={prefillEmail} + onSwitchToSignup={() => { + setLoginDialogOpen(false); + // Small delay to ensure dialog closes before opening signup + setTimeout(() => { + setSignupDialogOpen(true); + }, 100); + }} + /> + + {/* Signup Dialog */} + { + setSignupDialogOpen(false); + setPrefillEmail(email); + // Small delay to ensure dialog closes before opening login + setTimeout(() => { + setLoginDialogOpen(true); + }, 100); + }} /> ); diff --git a/components/SignupDialog.tsx b/components/SignupDialog.tsx new file mode 100644 index 0000000..8f7ca9a --- /dev/null +++ b/components/SignupDialog.tsx @@ -0,0 +1,479 @@ +"use client"; + +import { useState } from "react"; +import { Button } from "@/components/ui/button"; +import { useAppTheme } from "@/components/ThemeProvider"; +import { Input } from "@/components/ui/input"; +import { + InputOTP, + InputOTPGroup, + InputOTPSlot, +} from "@/components/ui/input-otp"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { Eye, EyeOff, Loader2, X, CheckCircle2 } from "lucide-react"; +import { useAuth } from "@/hooks/useAuth"; +import { registerSchema, verifyOtpSchema, type RegisterInput, type VerifyOtpInput } from "@/lib/schema/auth"; +import { toast } from "sonner"; + +interface SignupDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; + onSignupSuccess?: () => void; + onSwitchToLogin?: (email?: string) => void; +} + +type Step = "signup" | "verify"; + +export function SignupDialog({ open, onOpenChange, onSignupSuccess, onSwitchToLogin }: SignupDialogProps) { + const { theme } = useAppTheme(); + const isDark = theme === "dark"; + const { register, verifyOtp, registerMutation, verifyOtpMutation, resendOtpMutation } = useAuth(); + const [step, setStep] = useState("signup"); + const [registeredEmail, setRegisteredEmail] = useState(""); + const [signupData, setSignupData] = useState({ + first_name: "", + last_name: "", + email: "", + phone_number: "", + password: "", + password2: "", + }); + const [otpData, setOtpData] = useState({ + email: "", + otp: "", + }); + const [showPassword, setShowPassword] = useState(false); + const [showPassword2, setShowPassword2] = useState(false); + + const handleSignup = async (e: React.FormEvent) => { + e.preventDefault(); + + // Validate form + const validation = registerSchema.safeParse(signupData); + if (!validation.success) { + const firstError = validation.error.issues[0]; + toast.error(firstError.message); + return; + } + + try { + const result = await register(signupData); + + // Always switch to OTP verification step after successful registration + const email = signupData.email; + setRegisteredEmail(email); + setOtpData({ email: email, otp: "" }); + + // Clear signup form + setSignupData({ + first_name: "", + last_name: "", + email: "", + phone_number: "", + password: "", + password2: "", + }); + + // Switch to verify step + setStep("verify"); + toast.success("Registration successful! Please check your email for OTP verification."); + } catch (err) { + const errorMessage = err instanceof Error ? err.message : "Signup failed. Please try again."; + toast.error(errorMessage); + } + }; + + const handleVerifyOtp = async (e: React.FormEvent) => { + e.preventDefault(); + + const emailToVerify = registeredEmail || otpData.email; + if (!emailToVerify) { + toast.error("Email is required"); + return; + } + + const validation = verifyOtpSchema.safeParse({ + email: emailToVerify, + otp: otpData.otp, + }); + + if (!validation.success) { + const firstError = validation.error.issues[0]; + toast.error(firstError.message); + return; + } + + try { + const result = await verifyOtp({ + email: emailToVerify, + otp: otpData.otp, + }); + + if (result.message) { + toast.success("Email verified successfully! Please log in."); + // Close signup dialog and open login dialog with email + const emailToPass = emailToVerify; + onOpenChange(false); + // Call onSwitchToLogin with email to open login dialog with pre-filled email + if (onSwitchToLogin) { + onSwitchToLogin(emailToPass); + } + } + } catch (err) { + const errorMessage = err instanceof Error ? err.message : "OTP verification failed. Please try again."; + toast.error(errorMessage); + } + }; + + const handleResendOtp = async () => { + const emailToResend = registeredEmail || otpData.email; + if (!emailToResend) { + toast.error("Email is required"); + return; + } + + try { + await resendOtpMutation.mutateAsync({ email: emailToResend, context: "registration" }); + toast.success("OTP resent successfully! Please check your email."); + } catch (err) { + const errorMessage = err instanceof Error ? err.message : "Failed to resend OTP"; + toast.error(errorMessage); + } + }; + + const handleOtpChange = (field: keyof VerifyOtpInput, value: string) => { + setOtpData((prev) => ({ ...prev, [field]: value })); + }; + + // Reset step when dialog closes + const handleDialogChange = (isOpen: boolean) => { + if (!isOpen) { + setStep("signup"); + setRegisteredEmail(""); + setOtpData({ email: "", otp: "" }); + setSignupData({ + first_name: "", + last_name: "", + email: "", + phone_number: "", + password: "", + password2: "", + }); + } + onOpenChange(isOpen); + }; + + return ( + + + {/* Header with Close Button - Fixed */} +
+ + + {step === "signup" && "Create an account"} + {step === "verify" && "Verify your email"} + + + {step === "signup" && "Sign up to complete your booking"} + {step === "verify" && "Enter the verification code sent to your email"} + + + {/* Close Button */} + +
+ + {/* Scrollable Content */} +
+ {/* Signup Form */} + {step === "signup" && ( +
+ {/* First Name Field */} +
+ + setSignupData({ ...signupData, first_name: e.target.value })} + className={`h-11 sm:h-12 text-sm sm:text-base ${isDark ? 'bg-gray-700 border-gray-600 text-white placeholder:text-gray-400' : 'bg-white border-gray-300 text-gray-900'}`} + required + /> +
+ + {/* Last Name Field */} +
+ + setSignupData({ ...signupData, last_name: e.target.value })} + className={`h-11 sm:h-12 text-sm sm:text-base ${isDark ? 'bg-gray-700 border-gray-600 text-white placeholder:text-gray-400' : 'bg-white border-gray-300 text-gray-900'}`} + required + /> +
+ + {/* Email Field */} +
+ + setSignupData({ ...signupData, email: e.target.value })} + className={`h-11 sm:h-12 text-sm sm:text-base ${isDark ? 'bg-gray-700 border-gray-600 text-white placeholder:text-gray-400' : 'bg-white border-gray-300 text-gray-900'}`} + required + /> +
+ + {/* Phone Field */} +
+ + setSignupData({ ...signupData, phone_number: e.target.value })} + className={`h-11 sm:h-12 text-sm sm:text-base ${isDark ? 'bg-gray-700 border-gray-600 text-white placeholder:text-gray-400' : 'bg-white border-gray-300 text-gray-900'}`} + /> +
+ + {/* Password Field */} +
+ +
+ setSignupData({ ...signupData, password: e.target.value })} + className={`h-11 sm:h-12 pr-12 text-sm sm:text-base ${isDark ? 'bg-gray-700 border-gray-600 text-white placeholder:text-gray-400' : 'bg-white border-gray-300 text-gray-900'}`} + required + /> + +
+
+ + {/* Confirm Password Field */} +
+ +
+ setSignupData({ ...signupData, password2: e.target.value })} + className={`h-11 sm:h-12 pr-12 text-sm sm:text-base ${isDark ? 'bg-gray-700 border-gray-600 text-white placeholder:text-gray-400' : 'bg-white border-gray-300 text-gray-900'}`} + required + /> + +
+
+ + {/* Submit Button */} + + + {/* Switch to Login */} +

+ Already have an account?{" "} + +

+
+ )} + + {/* OTP Verification Form */} + {step === "verify" && ( +
+
+
+ +
+

+ Check your email +

+

+ We've sent a 6-digit verification code to {registeredEmail || otpData.email || "your email address"}. +

+
+
+
+ + {/* Email Field (if not set) */} + {!registeredEmail && ( +
+ + handleOtpChange("email", e.target.value)} + className={`h-11 sm:h-12 text-sm sm:text-base ${isDark ? 'bg-gray-700 border-gray-600 text-white placeholder:text-gray-400' : 'bg-white border-gray-300 text-gray-900'}`} + required + /> +
+ )} + + {/* OTP Field */} +
+ +
+ handleOtpChange("otp", value)} + > + + + + + + + + + +
+
+ + {/* Resend OTP */} +
+ +
+ + {/* Submit Button */} + + + {/* Back to signup */} +
+ +
+
+ )} +
+
+
+ ); +} +