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

View File

@ -134,15 +134,15 @@ export function LoginDialog({ open, onOpenChange, onLoginSuccess }: LoginDialogP
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent
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 */}
<div className="flex items-start justify-between mb-2">
<DialogHeader className="flex-1">
<DialogTitle className="text-3xl font-bold bg-gradient-to-r from-rose-600 via-pink-600 to-rose-600 bg-clip-text text-transparent">
{/* Header with Close Button - Fixed */}
<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 pr-2">
<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"}
</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
? "Sign up to complete your booking"
: "Please log in to complete your booking"}
@ -158,9 +158,11 @@ export function LoginDialog({ open, onOpenChange, onLoginSuccess }: LoginDialogP
</button>
</div>
{/* Signup Form */}
{isSignup ? (
<form className="space-y-6 mt-4" onSubmit={handleSignup}>
{/* Scrollable Content */}
<div className="overflow-y-auto flex-1 px-6">
{/* Signup Form */}
{isSignup ? (
<form className="space-y-4 sm:space-y-5 py-4 sm:py-6" onSubmit={handleSignup}>
{error && (
<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>
@ -168,7 +170,7 @@ export function LoginDialog({ open, onOpenChange, onLoginSuccess }: LoginDialogP
)}
{/* 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'}`}>
First Name *
</label>
@ -178,13 +180,13 @@ export function LoginDialog({ open, onOpenChange, onLoginSuccess }: LoginDialogP
placeholder="John"
value={signupData.first_name}
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
/>
</div>
{/* 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'}`}>
Last Name *
</label>
@ -194,13 +196,13 @@ export function LoginDialog({ open, onOpenChange, onLoginSuccess }: LoginDialogP
placeholder="Doe"
value={signupData.last_name}
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
/>
</div>
{/* 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'}`}>
Email address *
</label>
@ -210,13 +212,13 @@ export function LoginDialog({ open, onOpenChange, onLoginSuccess }: LoginDialogP
placeholder="Email address"
value={signupData.email}
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
/>
</div>
{/* 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'}`}>
Phone Number (Optional)
</label>
@ -226,12 +228,12 @@ export function LoginDialog({ open, onOpenChange, onLoginSuccess }: LoginDialogP
placeholder="+1 (555) 123-4567"
value={signupData.phone_number || ""}
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>
{/* 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'}`}>
Password *
</label>
@ -242,7 +244,7 @@ export function LoginDialog({ open, onOpenChange, onLoginSuccess }: LoginDialogP
placeholder="Password (min 8 characters)"
value={signupData.password}
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
/>
<Button
@ -250,20 +252,20 @@ export function LoginDialog({ open, onOpenChange, onLoginSuccess }: LoginDialogP
variant="ghost"
size="icon"
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"}
>
{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>
</div>
</div>
{/* 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'}`}>
Confirm Password *
</label>
@ -274,7 +276,7 @@ export function LoginDialog({ open, onOpenChange, onLoginSuccess }: LoginDialogP
placeholder="Confirm password"
value={signupData.password2}
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
/>
<Button
@ -282,13 +284,13 @@ export function LoginDialog({ open, onOpenChange, onLoginSuccess }: LoginDialogP
variant="ghost"
size="icon"
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"}
>
{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>
</div>
@ -298,7 +300,7 @@ export function LoginDialog({ open, onOpenChange, onLoginSuccess }: LoginDialogP
<Button
type="submit"
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 ? (
<>
@ -311,7 +313,7 @@ export function LoginDialog({ open, onOpenChange, onLoginSuccess }: LoginDialogP
</Button>
{/* 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?{" "}
<button
type="button"
@ -324,7 +326,7 @@ export function LoginDialog({ open, onOpenChange, onLoginSuccess }: LoginDialogP
</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 && (
<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>
@ -332,7 +334,7 @@ export function LoginDialog({ open, onOpenChange, onLoginSuccess }: LoginDialogP
)}
{/* 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'}`}>
Email address
</label>
@ -342,13 +344,13 @@ export function LoginDialog({ open, onOpenChange, onLoginSuccess }: LoginDialogP
placeholder="Email address"
value={loginData.email}
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
/>
</div>
{/* 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'}`}>
Your password
</label>
@ -359,7 +361,7 @@ export function LoginDialog({ open, onOpenChange, onLoginSuccess }: LoginDialogP
placeholder="Your password"
value={loginData.password}
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
/>
<Button
@ -367,13 +369,13 @@ export function LoginDialog({ open, onOpenChange, onLoginSuccess }: LoginDialogP
variant="ghost"
size="icon"
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"}
>
{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>
</div>
@ -383,7 +385,7 @@ export function LoginDialog({ open, onOpenChange, onLoginSuccess }: LoginDialogP
<Button
type="submit"
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 ? (
<>
@ -396,7 +398,7 @@ export function LoginDialog({ open, onOpenChange, onLoginSuccess }: LoginDialogP
</Button>
{/* 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">
<input
type="checkbox"
@ -408,7 +410,7 @@ export function LoginDialog({ open, onOpenChange, onLoginSuccess }: LoginDialogP
</label>
<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)}
>
Forgot password?
@ -416,7 +418,7 @@ export function LoginDialog({ open, onOpenChange, onLoginSuccess }: LoginDialogP
</div>
{/* 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?{" "}
<button
type="button"
@ -428,6 +430,7 @@ export function LoginDialog({ open, onOpenChange, onLoginSuccess }: LoginDialogP
</p>
</form>
)}
</div>
</DialogContent>
</Dialog>
);