feat/authentication #21

Merged
Hammond merged 12 commits from feat/authentication into master 2025-11-24 22:09:51 +00:00
2 changed files with 51 additions and 47 deletions
Showing only changes of commit eec3976f94 - Show all commits

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>
);