Refactor login and OTP verification flow in Login component. Update success handling to switch to login step instead of redirecting, and enhance UI in LoginDialog for better responsiveness and accessibility. Adjust styling for form elements and improve layout consistency.

This commit is contained in:
iamkiddy 2025-11-24 16:21:21 +00:00
parent c7871cfb46
commit eec3976f94
2 changed files with 51 additions and 47 deletions

View File

@ -273,12 +273,13 @@ export default function Login() {
try { try {
const result = await verifyOtp(otpToVerify); const result = await verifyOtp(otpToVerify);
// If verification is successful, redirect to login page // If verification is successful, switch to login step
toast.success("Email verified successfully! Redirecting to login..."); toast.success("Email verified successfully! You can now login.");
// Redirect to login page with email pre-filled // Switch to login step and pre-fill email
setTimeout(() => { setStep("login");
router.push(`/login?email=${encodeURIComponent(emailToVerify)}`); setLoginData(prev => ({ ...prev, email: emailToVerify }));
}, 1000); setOtpData({ email: "", otp: "" });
setRegisteredEmail("");
} catch (error) { } catch (error) {
const errorMessage = error instanceof Error ? error.message : "OTP verification failed. Please try again."; const errorMessage = error instanceof Error ? error.message : "OTP verification failed. Please try again.";
toast.error(errorMessage); toast.error(errorMessage);

View File

@ -134,15 +134,15 @@ export function LoginDialog({ open, onOpenChange, onLoginSuccess }: LoginDialogP
<Dialog open={open} onOpenChange={onOpenChange}> <Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent <DialogContent
showCloseButton={false} showCloseButton={false}
className={`sm:max-w-md ${isDark ? 'bg-gray-800 border-gray-700' : 'bg-white border-gray-200'}`} className={`max-w-md max-h-[90vh] overflow-hidden flex flex-col p-0 ${isDark ? 'bg-gray-800 border-gray-700' : 'bg-white border-gray-200'}`}
> >
{/* Header with Close Button */} {/* Header with Close Button - Fixed */}
<div className="flex items-start justify-between mb-2"> <div className="flex items-start justify-between p-6 pb-4 flex-shrink-0 border-b border-gray-200 dark:border-gray-700">
<DialogHeader className="flex-1"> <DialogHeader className="flex-1 pr-2">
<DialogTitle className="text-3xl font-bold bg-gradient-to-r from-rose-600 via-pink-600 to-rose-600 bg-clip-text text-transparent"> <DialogTitle className="text-2xl sm:text-3xl font-bold bg-gradient-to-r from-rose-600 via-pink-600 to-rose-600 bg-clip-text text-transparent">
{isSignup ? "Create an account" : "Welcome back"} {isSignup ? "Create an account" : "Welcome back"}
</DialogTitle> </DialogTitle>
<DialogDescription className={isDark ? 'text-gray-400' : 'text-gray-600'}> <DialogDescription className={`text-sm sm:text-base mt-1 ${isDark ? 'text-gray-400' : 'text-gray-600'}`}>
{isSignup {isSignup
? "Sign up to complete your booking" ? "Sign up to complete your booking"
: "Please log in to complete your booking"} : "Please log in to complete your booking"}
@ -158,9 +158,11 @@ export function LoginDialog({ open, onOpenChange, onLoginSuccess }: LoginDialogP
</button> </button>
</div> </div>
{/* Signup Form */} {/* Scrollable Content */}
{isSignup ? ( <div className="overflow-y-auto flex-1 px-6">
<form className="space-y-6 mt-4" onSubmit={handleSignup}> {/* Signup Form */}
{isSignup ? (
<form className="space-y-4 sm:space-y-5 py-4 sm:py-6" onSubmit={handleSignup}>
{error && ( {error && (
<div className={`p-3 rounded-lg border ${isDark ? 'bg-red-900/20 border-red-800' : 'bg-red-50 border-red-200'}`}> <div className={`p-3 rounded-lg border ${isDark ? 'bg-red-900/20 border-red-800' : 'bg-red-50 border-red-200'}`}>
<p className={`text-sm ${isDark ? 'text-red-200' : 'text-red-800'}`}>{error}</p> <p className={`text-sm ${isDark ? 'text-red-200' : 'text-red-800'}`}>{error}</p>
@ -168,7 +170,7 @@ export function LoginDialog({ open, onOpenChange, onLoginSuccess }: LoginDialogP
)} )}
{/* First Name Field */} {/* First Name Field */}
<div className="space-y-2"> <div className="space-y-1.5 sm:space-y-2">
<label htmlFor="signup-firstName" className={`text-sm font-medium ${isDark ? 'text-gray-300' : 'text-black'}`}> <label htmlFor="signup-firstName" className={`text-sm font-medium ${isDark ? 'text-gray-300' : 'text-black'}`}>
First Name * First Name *
</label> </label>
@ -178,13 +180,13 @@ export function LoginDialog({ open, onOpenChange, onLoginSuccess }: LoginDialogP
placeholder="John" placeholder="John"
value={signupData.first_name} value={signupData.first_name}
onChange={(e) => setSignupData({ ...signupData, first_name: e.target.value })} onChange={(e) => setSignupData({ ...signupData, first_name: e.target.value })}
className={`h-12 ${isDark ? 'bg-gray-700 border-gray-600 text-white placeholder:text-gray-400' : 'bg-white border-gray-300 text-gray-900'}`} 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 required
/> />
</div> </div>
{/* Last Name Field */} {/* Last Name Field */}
<div className="space-y-2"> <div className="space-y-1.5 sm:space-y-2">
<label htmlFor="signup-lastName" className={`text-sm font-medium ${isDark ? 'text-gray-300' : 'text-black'}`}> <label htmlFor="signup-lastName" className={`text-sm font-medium ${isDark ? 'text-gray-300' : 'text-black'}`}>
Last Name * Last Name *
</label> </label>
@ -194,13 +196,13 @@ export function LoginDialog({ open, onOpenChange, onLoginSuccess }: LoginDialogP
placeholder="Doe" placeholder="Doe"
value={signupData.last_name} value={signupData.last_name}
onChange={(e) => setSignupData({ ...signupData, last_name: e.target.value })} onChange={(e) => setSignupData({ ...signupData, last_name: e.target.value })}
className={`h-12 ${isDark ? 'bg-gray-700 border-gray-600 text-white placeholder:text-gray-400' : 'bg-white border-gray-300 text-gray-900'}`} 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 required
/> />
</div> </div>
{/* Email Field */} {/* Email Field */}
<div className="space-y-2"> <div className="space-y-1.5 sm:space-y-2">
<label htmlFor="signup-email" className={`text-sm font-medium ${isDark ? 'text-gray-300' : 'text-black'}`}> <label htmlFor="signup-email" className={`text-sm font-medium ${isDark ? 'text-gray-300' : 'text-black'}`}>
Email address * Email address *
</label> </label>
@ -210,13 +212,13 @@ export function LoginDialog({ open, onOpenChange, onLoginSuccess }: LoginDialogP
placeholder="Email address" placeholder="Email address"
value={signupData.email} value={signupData.email}
onChange={(e) => setSignupData({ ...signupData, email: e.target.value })} onChange={(e) => setSignupData({ ...signupData, email: e.target.value })}
className={`h-12 ${isDark ? 'bg-gray-700 border-gray-600 text-white placeholder:text-gray-400' : 'bg-white border-gray-300 text-gray-900'}`} 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 required
/> />
</div> </div>
{/* Phone Field */} {/* Phone Field */}
<div className="space-y-2"> <div className="space-y-1.5 sm:space-y-2">
<label htmlFor="signup-phone" className={`text-sm font-medium ${isDark ? 'text-gray-300' : 'text-black'}`}> <label htmlFor="signup-phone" className={`text-sm font-medium ${isDark ? 'text-gray-300' : 'text-black'}`}>
Phone Number (Optional) Phone Number (Optional)
</label> </label>
@ -226,12 +228,12 @@ export function LoginDialog({ open, onOpenChange, onLoginSuccess }: LoginDialogP
placeholder="+1 (555) 123-4567" placeholder="+1 (555) 123-4567"
value={signupData.phone_number || ""} value={signupData.phone_number || ""}
onChange={(e) => setSignupData({ ...signupData, phone_number: e.target.value })} onChange={(e) => setSignupData({ ...signupData, phone_number: e.target.value })}
className={`h-12 ${isDark ? 'bg-gray-700 border-gray-600 text-white placeholder:text-gray-400' : 'bg-white border-gray-300 text-gray-900'}`} 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'}`}
/> />
</div> </div>
{/* Password Field */} {/* Password Field */}
<div className="space-y-2"> <div className="space-y-1.5 sm:space-y-2">
<label htmlFor="signup-password" className={`text-sm font-medium ${isDark ? 'text-gray-300' : 'text-black'}`}> <label htmlFor="signup-password" className={`text-sm font-medium ${isDark ? 'text-gray-300' : 'text-black'}`}>
Password * Password *
</label> </label>
@ -242,7 +244,7 @@ export function LoginDialog({ open, onOpenChange, onLoginSuccess }: LoginDialogP
placeholder="Password (min 8 characters)" placeholder="Password (min 8 characters)"
value={signupData.password} value={signupData.password}
onChange={(e) => setSignupData({ ...signupData, password: e.target.value })} onChange={(e) => setSignupData({ ...signupData, password: e.target.value })}
className={`h-12 pr-12 ${isDark ? 'bg-gray-700 border-gray-600 text-white placeholder:text-gray-400' : 'bg-white border-gray-300 text-gray-900'}`} 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 required
/> />
<Button <Button
@ -250,20 +252,20 @@ export function LoginDialog({ open, onOpenChange, onLoginSuccess }: LoginDialogP
variant="ghost" variant="ghost"
size="icon" size="icon"
onClick={() => setShowPassword(!showPassword)} onClick={() => setShowPassword(!showPassword)}
className={`absolute right-4 top-1/2 -translate-y-1/2 h-auto w-auto p-0 ${isDark ? 'text-gray-400 hover:text-gray-300' : 'text-gray-500 hover:text-gray-700'}`} className={`absolute right-3 sm:right-4 top-1/2 -translate-y-1/2 h-auto w-auto p-0 ${isDark ? 'text-gray-400 hover:text-gray-300' : 'text-gray-500 hover:text-gray-700'}`}
aria-label={showPassword ? "Hide password" : "Show password"} aria-label={showPassword ? "Hide password" : "Show password"}
> >
{showPassword ? ( {showPassword ? (
<EyeOff className="w-5 h-5" /> <EyeOff className="w-4 h-4 sm:w-5 sm:h-5" />
) : ( ) : (
<Eye className="w-5 h-5" /> <Eye className="w-4 h-4 sm:w-5 sm:h-5" />
)} )}
</Button> </Button>
</div> </div>
</div> </div>
{/* Confirm Password Field */} {/* Confirm Password Field */}
<div className="space-y-2"> <div className="space-y-1.5 sm:space-y-2">
<label htmlFor="signup-password2" className={`text-sm font-medium ${isDark ? 'text-gray-300' : 'text-black'}`}> <label htmlFor="signup-password2" className={`text-sm font-medium ${isDark ? 'text-gray-300' : 'text-black'}`}>
Confirm Password * Confirm Password *
</label> </label>
@ -274,7 +276,7 @@ export function LoginDialog({ open, onOpenChange, onLoginSuccess }: LoginDialogP
placeholder="Confirm password" placeholder="Confirm password"
value={signupData.password2} value={signupData.password2}
onChange={(e) => setSignupData({ ...signupData, password2: e.target.value })} onChange={(e) => setSignupData({ ...signupData, password2: e.target.value })}
className={`h-12 pr-12 ${isDark ? 'bg-gray-700 border-gray-600 text-white placeholder:text-gray-400' : 'bg-white border-gray-300 text-gray-900'}`} 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 required
/> />
<Button <Button
@ -282,13 +284,13 @@ export function LoginDialog({ open, onOpenChange, onLoginSuccess }: LoginDialogP
variant="ghost" variant="ghost"
size="icon" size="icon"
onClick={() => setShowPassword2(!showPassword2)} onClick={() => setShowPassword2(!showPassword2)}
className={`absolute right-4 top-1/2 -translate-y-1/2 h-auto w-auto p-0 ${isDark ? 'text-gray-400 hover:text-gray-300' : 'text-gray-500 hover:text-gray-700'}`} className={`absolute right-3 sm:right-4 top-1/2 -translate-y-1/2 h-auto w-auto p-0 ${isDark ? 'text-gray-400 hover:text-gray-300' : 'text-gray-500 hover:text-gray-700'}`}
aria-label={showPassword2 ? "Hide password" : "Show password"} aria-label={showPassword2 ? "Hide password" : "Show password"}
> >
{showPassword2 ? ( {showPassword2 ? (
<EyeOff className="w-5 h-5" /> <EyeOff className="w-4 h-4 sm:w-5 sm:h-5" />
) : ( ) : (
<Eye className="w-5 h-5" /> <Eye className="w-4 h-4 sm:w-5 sm:h-5" />
)} )}
</Button> </Button>
</div> </div>
@ -298,7 +300,7 @@ export function LoginDialog({ open, onOpenChange, onLoginSuccess }: LoginDialogP
<Button <Button
type="submit" type="submit"
disabled={registerMutation.isPending} disabled={registerMutation.isPending}
className="w-full h-12 text-base font-semibold bg-gradient-to-r from-rose-500 to-pink-600 hover:from-rose-600 hover:to-pink-700 text-white shadow-lg hover:shadow-xl transition-all disabled:opacity-50 disabled:cursor-not-allowed" className="w-full h-11 sm:h-12 text-sm sm:text-base font-semibold bg-gradient-to-r from-rose-500 to-pink-600 hover:from-rose-600 hover:to-pink-700 text-white shadow-lg hover:shadow-xl transition-all disabled:opacity-50 disabled:cursor-not-allowed mt-4 sm:mt-6"
> >
{registerMutation.isPending ? ( {registerMutation.isPending ? (
<> <>
@ -311,7 +313,7 @@ export function LoginDialog({ open, onOpenChange, onLoginSuccess }: LoginDialogP
</Button> </Button>
{/* Switch to Login */} {/* Switch to Login */}
<p className={`text-sm text-center ${isDark ? 'text-gray-400' : 'text-gray-600'}`}> <p className={`text-xs sm:text-sm text-center pt-2 ${isDark ? 'text-gray-400' : 'text-gray-600'}`}>
Already have an account?{" "} Already have an account?{" "}
<button <button
type="button" type="button"
@ -324,7 +326,7 @@ export function LoginDialog({ open, onOpenChange, onLoginSuccess }: LoginDialogP
</form> </form>
) : ( ) : (
/* Login Form */ /* Login Form */
<form className="space-y-6 mt-4" onSubmit={handleLogin}> <form className="space-y-4 sm:space-y-5 py-4 sm:py-6" onSubmit={handleLogin}>
{error && ( {error && (
<div className={`p-3 rounded-lg border ${isDark ? 'bg-red-900/20 border-red-800' : 'bg-red-50 border-red-200'}`}> <div className={`p-3 rounded-lg border ${isDark ? 'bg-red-900/20 border-red-800' : 'bg-red-50 border-red-200'}`}>
<p className={`text-sm ${isDark ? 'text-red-200' : 'text-red-800'}`}>{error}</p> <p className={`text-sm ${isDark ? 'text-red-200' : 'text-red-800'}`}>{error}</p>
@ -332,7 +334,7 @@ export function LoginDialog({ open, onOpenChange, onLoginSuccess }: LoginDialogP
)} )}
{/* Email Field */} {/* Email Field */}
<div className="space-y-2"> <div className="space-y-1.5 sm:space-y-2">
<label htmlFor="login-email" className={`text-sm font-medium ${isDark ? 'text-gray-300' : 'text-black'}`}> <label htmlFor="login-email" className={`text-sm font-medium ${isDark ? 'text-gray-300' : 'text-black'}`}>
Email address Email address
</label> </label>
@ -342,13 +344,13 @@ export function LoginDialog({ open, onOpenChange, onLoginSuccess }: LoginDialogP
placeholder="Email address" placeholder="Email address"
value={loginData.email} value={loginData.email}
onChange={(e) => setLoginData({ ...loginData, email: e.target.value })} onChange={(e) => setLoginData({ ...loginData, email: e.target.value })}
className={`h-12 ${isDark ? 'bg-gray-700 border-gray-600 text-white placeholder:text-gray-400' : 'bg-white border-gray-300 text-gray-900'}`} 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 required
/> />
</div> </div>
{/* Password Field */} {/* Password Field */}
<div className="space-y-2"> <div className="space-y-1.5 sm:space-y-2">
<label htmlFor="login-password" className={`text-sm font-medium ${isDark ? 'text-gray-300' : 'text-black'}`}> <label htmlFor="login-password" className={`text-sm font-medium ${isDark ? 'text-gray-300' : 'text-black'}`}>
Your password Your password
</label> </label>
@ -359,7 +361,7 @@ export function LoginDialog({ open, onOpenChange, onLoginSuccess }: LoginDialogP
placeholder="Your password" placeholder="Your password"
value={loginData.password} value={loginData.password}
onChange={(e) => setLoginData({ ...loginData, password: e.target.value })} onChange={(e) => setLoginData({ ...loginData, password: e.target.value })}
className={`h-12 pr-12 ${isDark ? 'bg-gray-700 border-gray-600 text-white placeholder:text-gray-400' : 'bg-white border-gray-300 text-gray-900'}`} 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 required
/> />
<Button <Button
@ -367,13 +369,13 @@ export function LoginDialog({ open, onOpenChange, onLoginSuccess }: LoginDialogP
variant="ghost" variant="ghost"
size="icon" size="icon"
onClick={() => setShowPassword(!showPassword)} onClick={() => setShowPassword(!showPassword)}
className={`absolute right-4 top-1/2 -translate-y-1/2 h-auto w-auto p-0 ${isDark ? 'text-gray-400 hover:text-gray-300' : 'text-gray-500 hover:text-gray-700'}`} className={`absolute right-3 sm:right-4 top-1/2 -translate-y-1/2 h-auto w-auto p-0 ${isDark ? 'text-gray-400 hover:text-gray-300' : 'text-gray-500 hover:text-gray-700'}`}
aria-label={showPassword ? "Hide password" : "Show password"} aria-label={showPassword ? "Hide password" : "Show password"}
> >
{showPassword ? ( {showPassword ? (
<EyeOff className="w-5 h-5" /> <EyeOff className="w-4 h-4 sm:w-5 sm:h-5" />
) : ( ) : (
<Eye className="w-5 h-5" /> <Eye className="w-4 h-4 sm:w-5 sm:h-5" />
)} )}
</Button> </Button>
</div> </div>
@ -383,7 +385,7 @@ export function LoginDialog({ open, onOpenChange, onLoginSuccess }: LoginDialogP
<Button <Button
type="submit" type="submit"
disabled={loginMutation.isPending} disabled={loginMutation.isPending}
className="w-full h-12 text-base font-semibold bg-gradient-to-r from-rose-500 to-pink-600 hover:from-rose-600 hover:to-pink-700 text-white shadow-lg hover:shadow-xl transition-all disabled:opacity-50 disabled:cursor-not-allowed" className="w-full h-11 sm:h-12 text-sm sm:text-base font-semibold bg-gradient-to-r from-rose-500 to-pink-600 hover:from-rose-600 hover:to-pink-700 text-white shadow-lg hover:shadow-xl transition-all disabled:opacity-50 disabled:cursor-not-allowed mt-4 sm:mt-6"
> >
{loginMutation.isPending ? ( {loginMutation.isPending ? (
<> <>
@ -396,7 +398,7 @@ export function LoginDialog({ open, onOpenChange, onLoginSuccess }: LoginDialogP
</Button> </Button>
{/* Remember Me & Forgot Password */} {/* Remember Me & Forgot Password */}
<div className="flex items-center justify-between text-sm"> <div className="flex items-center justify-between text-xs sm:text-sm">
<label className="flex items-center gap-2 cursor-pointer"> <label className="flex items-center gap-2 cursor-pointer">
<input <input
type="checkbox" type="checkbox"
@ -408,7 +410,7 @@ export function LoginDialog({ open, onOpenChange, onLoginSuccess }: LoginDialogP
</label> </label>
<button <button
type="button" type="button"
className={`font-medium ${isDark ? 'text-blue-400 hover:text-blue-300' : 'text-blue-600 hover:text-blue-700'}`} className={`font-medium text-xs sm:text-sm ${isDark ? 'text-blue-400 hover:text-blue-300' : 'text-blue-600 hover:text-blue-700'}`}
onClick={() => onOpenChange(false)} onClick={() => onOpenChange(false)}
> >
Forgot password? Forgot password?
@ -416,7 +418,7 @@ export function LoginDialog({ open, onOpenChange, onLoginSuccess }: LoginDialogP
</div> </div>
{/* Sign Up Prompt */} {/* Sign Up Prompt */}
<p className={`text-sm text-center ${isDark ? 'text-gray-400' : 'text-gray-600'}`}> <p className={`text-xs sm:text-sm text-center pt-2 ${isDark ? 'text-gray-400' : 'text-gray-600'}`}>
New to Attune Heart Therapy?{" "} New to Attune Heart Therapy?{" "}
<button <button
type="button" type="button"
@ -428,6 +430,7 @@ export function LoginDialog({ open, onOpenChange, onLoginSuccess }: LoginDialogP
</p> </p>
</form> </form>
)} )}
</div>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
); );