login screen fix

This commit is contained in:
suryac 2026-11-12 10:54:09 +05:30
parent 144f70cd34
commit dc0b46fbd9
3 changed files with 168 additions and 130 deletions

View File

@ -0,0 +1,63 @@
import type { ReactNode } from 'react';
import { Card } from '../reusable';
import logo from '../../assets/logo.png';
/** Shared auth-page chrome: curved green background, brand header and a card. */
export function AuthShell({
title,
subtitle,
children,
}: {
title: string;
subtitle?: string;
children: ReactNode;
}) {
return (
<div className="relative min-h-screen overflow-hidden flex flex-col items-center justify-center gap-6 p-4 bg-app">
{/* Decorative curves */}
<svg
aria-hidden
className="pointer-events-none absolute top-0 left-0 w-[70%] max-w-[520px] h-auto"
viewBox="0 0 400 220"
fill="none"
>
<defs>
<linearGradient id="wave-top" x1="0" y1="0" x2="1" y2="1">
<stop offset="0" stopColor="#7fd49a" stopOpacity="0.85" />
<stop offset="1" stopColor="#d9f3e2" stopOpacity="0.2" />
</linearGradient>
</defs>
<path d="M0 0H400C340 40 300 120 190 150C110 172 50 150 0 210Z" fill="url(#wave-top)" />
<path d="M0 0H250C200 30 150 70 90 90C50 103 20 100 0 130Z" fill="#9adcae" fillOpacity="0.55" />
</svg>
<svg
aria-hidden
className="pointer-events-none absolute bottom-0 left-0 w-full h-auto"
viewBox="0 0 400 180"
preserveAspectRatio="none"
fill="none"
>
<defs>
<linearGradient id="wave-bottom" x1="0" y1="0" x2="1" y2="0">
<stop offset="0" stopColor="#d9f3e2" stopOpacity="0.4" />
<stop offset="1" stopColor="#6cc88b" stopOpacity="0.8" />
</linearGradient>
</defs>
<path d="M0 80C80 30 160 40 240 80C310 115 360 90 400 50V180H0Z" fill="url(#wave-bottom)" />
<path d="M0 130C90 100 170 110 250 135C320 157 370 140 400 115V180H0Z" fill="#4fb374" fillOpacity="0.55" />
</svg>
<div className="relative flex flex-col gap-2 items-center text-center">
<img src={logo} alt="Krishna Sales Logo" className="h-20 w-20 rounded-full object-contain shadow-md" />
<h1 className="m-0 text-3xl font-extrabold text-emerald-900 tracking-[-0.02em]">Krishna Sales</h1>
</div>
<Card className="relative w-full max-w-[420px] rounded-3xl shadow-xl shadow-emerald-900/10 border-white/60 bg-white/85 backdrop-blur p-6 sm:p-8">
<div className="mb-5">
<h2 className="m-0 text-2xl font-extrabold text-emerald-950">{title}</h2>
{subtitle && <p className="text-sm text-slate-600 mt-1 leading-relaxed">{subtitle}</p>}
</div>
{children}
</Card>
</div>
);
}

View File

@ -1,7 +1,7 @@
import { useState, useEffect, type FormEvent } from 'react'; import { useState, useEffect, useRef, type FormEvent } from 'react';
import { useNavigate, Link } from 'react-router-dom'; import { useNavigate, Link } from 'react-router-dom';
import { Eye, EyeOff, ArrowLeft, Mail, KeyRound, CheckCircle2, AlertCircle } from 'lucide-react'; import { Eye, EyeOff, ArrowLeft, Mail, Lock, KeyRound, CheckCircle2, AlertCircle } from 'lucide-react';
import { Card } from '../components/reusable'; import { AuthShell } from '../components/auth/AuthShell';
import { Button } from '../components/buttons'; import { Button } from '../components/buttons';
import { PasswordRulesChecklist } from '../components/auth/PasswordRulesChecklist'; import { PasswordRulesChecklist } from '../components/auth/PasswordRulesChecklist';
import { forgotPassword, verifyResetCode, resetPassword } from '../api/auth'; import { forgotPassword, verifyResetCode, resetPassword } from '../api/auth';
@ -11,7 +11,6 @@ import {
passwordTooLong, passwordTooLong,
PASSWORD_MAX_CHARS, PASSWORD_MAX_CHARS,
} from '../utils/passwordPolicy'; } from '../utils/passwordPolicy';
import logo from '../assets/logo.png';
const CODE_LENGTH = 6; const CODE_LENGTH = 6;
const RESEND_COOLDOWN = 60; // seconds const RESEND_COOLDOWN = 60; // seconds
@ -34,6 +33,13 @@ export function ForgotPasswordPage() {
const [message, setMessage] = useState(''); const [message, setMessage] = useState('');
const policy = usePasswordPolicy(step === 'password'); const policy = usePasswordPolicy(step === 'password');
const codeInputRef = useRef<HTMLInputElement>(null);
// The email and code inputs share a tree position, so React reuses the DOM node
// and autoFocus doesn't fire on step change — focus the code input explicitly.
useEffect(() => {
if (step === 'code') codeInputRef.current?.focus();
}, [step]);
// Cooldown countdown // Cooldown countdown
useEffect(() => { useEffect(() => {
@ -137,31 +143,23 @@ export function ForgotPasswordPage() {
} }
}; };
return ( const title = done
<div className="min-h-screen flex items-center justify-center p-4 bg-emerald-50"> ? 'Password Reset Complete'
<Card className="w-full max-w-[420px] shadow-lg border-emerald-100 bg-white p-6 sm:p-7"> : step === 'email'
<div className="flex flex-col gap-2 items-center mb-6 text-center"> ? 'Forgot Password'
<img src={logo} alt="Krishna Sales Logo" className="h-14 w-auto object-contain" /> : step === 'code'
<h1 className="text-xl font-extrabold text-emerald-950 tracking-tight"> ? 'Enter Verification Code'
{done : 'Create New Password';
? 'Password Reset Complete' const subtitle = done
: step === 'email' ? 'Your password has been changed. Redirecting to login…'
? 'Forgot Password' : step === 'email'
: step === 'code' ? "Enter your email address and we'll send you a 6-digit verification code."
? 'Enter Verification Code' : step === 'code'
: 'Create New Password'} ? `We emailed a ${CODE_LENGTH}-digit code to ${email}. It is valid for 10 minutes.`
</h1> : 'Choose a strong new password that meets the security policy.';
<p className="text-xs text-slate-500 max-w-[340px] leading-relaxed">
{done
? 'Your password has been changed. Redirecting to login…'
: step === 'email'
? "Enter your email address and we'll send you a 6-digit verification code."
: step === 'code'
? `We emailed a ${CODE_LENGTH}-digit code to ${email}. It is valid for 10 minutes.`
: 'Choose a strong new password that meets the security policy.'}
</p>
</div>
return (
<AuthShell title={title} subtitle={subtitle}>
{error && ( {error && (
<div className="mb-4 p-3 rounded-lg bg-rose-50 border border-rose-200 text-rose-700 text-xs flex items-start gap-2"> <div className="mb-4 p-3 rounded-lg bg-rose-50 border border-rose-200 text-rose-700 text-xs flex items-start gap-2">
<AlertCircle size={16} className="shrink-0 mt-0.5" /> <AlertCircle size={16} className="shrink-0 mt-0.5" />
@ -175,35 +173,33 @@ export function ForgotPasswordPage() {
<CheckCircle2 size={36} className="text-emerald-600" /> <CheckCircle2 size={36} className="text-emerald-600" />
<span className="font-semibold">{message}</span> <span className="font-semibold">{message}</span>
</div> </div>
<Button type="button" full onClick={goToLogin} className="bg-emerald-600 hover:bg-emerald-700 text-white"> <Button type="button" full onClick={goToLogin} className="mt-1 !rounded-full !h-12 !text-base bg-gradient-to-r from-[#3aa75a] to-[#0f5a30] hover:brightness-110 text-white border-transparent shadow-lg shadow-emerald-900/20">
Go to Login Go to Login
</Button> </Button>
</div> </div>
) : step === 'email' ? ( ) : step === 'email' ? (
/* Step 1: Email Form */ /* Step 1: Email Form */
<form onSubmit={handleEmailSubmit} className="flex flex-col gap-4"> <form onSubmit={handleEmailSubmit} className="flex flex-col gap-4">
<label className="flex flex-col gap-1.5 font-sans"> <div className="relative flex items-center">
<span className="text-sm font-semibold text-emerald-800 flex items-center gap-1.5"> <Mail size={18} className="absolute left-4 text-slate-500 pointer-events-none" />
<Mail size={15} />
Email address <span className="text-emerald-500">*</span>
</span>
<input <input
aria-label="Email address"
type="email" type="email"
value={email} value={email}
onChange={(e) => setEmail(e.target.value)} onChange={(e) => setEmail(e.target.value)}
required required
autoFocus autoFocus
disabled={submitting} disabled={submitting}
className="h-[42px] px-3 border border-emerald-200 bg-emerald-50/50 rounded-md outline-none focus:ring-2 focus:ring-emerald-400 focus:border-emerald-400 transition-all text-emerald-900 placeholder:text-slate-400 text-sm" className="h-12 w-full pl-11 pr-4 rounded-full border border-slate-200 bg-slate-100/80 outline-none focus:ring-2 focus:ring-emerald-300 focus:border-emerald-400 transition-all text-slate-900 placeholder:text-slate-500 text-sm"
placeholder="you@example.com" placeholder="Email"
/> />
</label> </div>
<Button <Button
type="submit" type="submit"
full full
disabled={submitting} disabled={submitting}
className="mt-2 bg-emerald-600 hover:bg-emerald-700 text-white border-transparent" className="mt-1 !rounded-full !h-12 !text-base bg-gradient-to-r from-[#3aa75a] to-[#0f5a30] hover:brightness-110 text-white border-transparent shadow-lg shadow-emerald-900/20"
> >
{submitting ? 'Sending code…' : 'Send Reset Code'} {submitting ? 'Sending code…' : 'Send Reset Code'}
</Button> </Button>
@ -219,12 +215,11 @@ export function ForgotPasswordPage() {
) : step === 'code' ? ( ) : step === 'code' ? (
/* Step 2: OTP Verification Form */ /* Step 2: OTP Verification Form */
<form onSubmit={handleCodeSubmit} className="flex flex-col gap-4"> <form onSubmit={handleCodeSubmit} className="flex flex-col gap-4">
<label className="flex flex-col gap-1.5 font-sans"> <div className="relative flex items-center">
<span className="text-sm font-semibold text-emerald-800 flex items-center gap-1.5"> <KeyRound size={18} className="absolute left-4 text-slate-500 pointer-events-none" />
<KeyRound size={15} />
Reset Code <span className="text-emerald-500">*</span>
</span>
<input <input
ref={codeInputRef}
aria-label="Reset code"
type="text" type="text"
inputMode="numeric" inputMode="numeric"
autoComplete="one-time-code" autoComplete="one-time-code"
@ -234,16 +229,16 @@ export function ForgotPasswordPage() {
required required
autoFocus autoFocus
disabled={submitting} disabled={submitting}
className="h-[46px] px-3 text-center text-xl font-bold tracking-[0.3em] border border-emerald-200 bg-emerald-50/50 rounded-md outline-none focus:ring-2 focus:ring-emerald-400 focus:border-emerald-400 transition-all text-emerald-900 placeholder:text-slate-400" className="h-12 w-full px-11 text-center text-xl font-bold tracking-[0.3em] rounded-full border border-slate-200 bg-slate-100/80 outline-none focus:ring-2 focus:ring-emerald-300 focus:border-emerald-400 transition-all text-slate-900 placeholder:text-slate-400"
placeholder="123456" placeholder="123456"
/> />
</label> </div>
<Button <Button
type="submit" type="submit"
full full
disabled={submitting || code.length !== CODE_LENGTH} disabled={submitting || code.length !== CODE_LENGTH}
className="mt-2 bg-emerald-600 hover:bg-emerald-700 text-white border-transparent" className="mt-1 !rounded-full !h-12 !text-base bg-gradient-to-r from-[#3aa75a] to-[#0f5a30] hover:brightness-110 text-white border-transparent shadow-lg shadow-emerald-900/20"
> >
{submitting ? 'Verifying…' : 'Verify Code'} {submitting ? 'Verifying…' : 'Verify Code'}
</Button> </Button>
@ -274,11 +269,10 @@ export function ForgotPasswordPage() {
/* Step 3: New Password Form */ /* Step 3: New Password Form */
<form onSubmit={handlePasswordSubmit} className="flex flex-col gap-4"> <form onSubmit={handlePasswordSubmit} className="flex flex-col gap-4">
<div className="flex flex-col gap-1.5 font-sans"> <div className="flex flex-col gap-1.5 font-sans">
<span className="text-sm font-semibold text-emerald-800">
New password <span className="text-emerald-500">*</span>
</span>
<div className="relative flex items-center"> <div className="relative flex items-center">
<Lock size={18} className="absolute left-4 text-slate-500 pointer-events-none" />
<input <input
aria-label="New password"
type={showPassword ? 'text' : 'password'} type={showPassword ? 'text' : 'password'}
autoComplete="new-password" autoComplete="new-password"
value={password} value={password}
@ -286,45 +280,44 @@ export function ForgotPasswordPage() {
required required
autoFocus autoFocus
disabled={submitting} disabled={submitting}
className="h-[42px] w-full px-3 pr-10 border border-emerald-200 bg-emerald-50/50 rounded-md outline-none focus:ring-2 focus:ring-emerald-400 focus:border-emerald-400 transition-all text-emerald-900 placeholder:text-slate-400 text-sm" className="h-12 w-full pl-11 pr-11 rounded-full border border-slate-200 bg-slate-100/80 outline-none focus:ring-2 focus:ring-emerald-300 focus:border-emerald-400 transition-all text-slate-900 placeholder:text-slate-500 text-sm"
placeholder="Enter new password" placeholder="New password"
/> />
<button <button
type="button" type="button"
onClick={() => setShowPassword((s) => !s)} onClick={() => setShowPassword((s) => !s)}
disabled={submitting} disabled={submitting}
tabIndex={-1} tabIndex={-1}
className="absolute right-3 text-slate-400 hover:text-slate-600 transition-colors cursor-pointer" className="absolute right-4 text-slate-600 hover:text-slate-800 transition-colors cursor-pointer"
> >
{showPassword ? <EyeOff size={16} /> : <Eye size={16} />} {showPassword ? <Eye size={18} /> : <EyeOff size={18} />}
</button> </button>
</div> </div>
<PasswordRulesChecklist policy={policy} password={password} email={email} className="mt-1" /> <PasswordRulesChecklist policy={policy} password={password} email={email} className="mt-1" />
</div> </div>
<div className="flex flex-col gap-1.5 font-sans"> <div className="flex flex-col gap-1.5 font-sans">
<span className="text-sm font-semibold text-emerald-800">
Confirm new password <span className="text-emerald-500">*</span>
</span>
<div className="relative flex items-center"> <div className="relative flex items-center">
<Lock size={18} className="absolute left-4 text-slate-500 pointer-events-none" />
<input <input
aria-label="Confirm new password"
type={showConfirm ? 'text' : 'password'} type={showConfirm ? 'text' : 'password'}
autoComplete="new-password" autoComplete="new-password"
value={confirm} value={confirm}
onChange={(e) => setConfirm(e.target.value)} onChange={(e) => setConfirm(e.target.value)}
required required
disabled={submitting} disabled={submitting}
className="h-[42px] w-full px-3 pr-10 border border-emerald-200 bg-emerald-50/50 rounded-md outline-none focus:ring-2 focus:ring-emerald-400 focus:border-emerald-400 transition-all text-emerald-900 placeholder:text-slate-400 text-sm" className="h-12 w-full pl-11 pr-11 rounded-full border border-slate-200 bg-slate-100/80 outline-none focus:ring-2 focus:ring-emerald-300 focus:border-emerald-400 transition-all text-slate-900 placeholder:text-slate-500 text-sm"
placeholder="Re-enter new password" placeholder="Confirm new password"
/> />
<button <button
type="button" type="button"
onClick={() => setShowConfirm((s) => !s)} onClick={() => setShowConfirm((s) => !s)}
disabled={submitting} disabled={submitting}
tabIndex={-1} tabIndex={-1}
className="absolute right-3 text-slate-400 hover:text-slate-600 transition-colors cursor-pointer" className="absolute right-4 text-slate-600 hover:text-slate-800 transition-colors cursor-pointer"
> >
{showConfirm ? <EyeOff size={16} /> : <Eye size={16} />} {showConfirm ? <Eye size={18} /> : <EyeOff size={18} />}
</button> </button>
</div> </div>
</div> </div>
@ -333,7 +326,7 @@ export function ForgotPasswordPage() {
type="submit" type="submit"
full full
disabled={submitting} disabled={submitting}
className="mt-2 bg-emerald-600 hover:bg-emerald-700 text-white border-transparent" className="mt-1 !rounded-full !h-12 !text-base bg-gradient-to-r from-[#3aa75a] to-[#0f5a30] hover:brightness-110 text-white border-transparent shadow-lg shadow-emerald-900/20"
> >
{submitting ? 'Updating password…' : 'Set New Password'} {submitting ? 'Updating password…' : 'Set New Password'}
</Button> </Button>
@ -351,7 +344,6 @@ export function ForgotPasswordPage() {
</button> </button>
</form> </form>
)} )}
</Card> </AuthShell>
</div>
); );
} }

View File

@ -1,9 +1,9 @@
import { useState, type FormEvent } from 'react'; import { useState, type FormEvent } from 'react';
import { Navigate, useNavigate, Link } from 'react-router-dom'; import { Navigate, useNavigate, Link } from 'react-router-dom';
import { Eye, EyeOff, Lock, AlertCircle, ArrowLeft } from 'lucide-react'; import { Eye, EyeOff, Lock, Mail, AlertCircle, ArrowLeft, ArrowRight } from 'lucide-react';
import { useAuth, type PasswordChangeRequiredInfo } from '../auth/context'; import { useAuth, type PasswordChangeRequiredInfo } from '../auth/context';
import { Button } from '../components/buttons'; import { Button } from '../components/buttons';
import { Card } from '../components/reusable'; import { AuthShell } from '../components/auth/AuthShell';
import { PasswordRulesChecklist } from '../components/auth/PasswordRulesChecklist'; import { PasswordRulesChecklist } from '../components/auth/PasswordRulesChecklist';
import { usePasswordPolicy } from '../hooks/usePasswordPolicy'; import { usePasswordPolicy } from '../hooks/usePasswordPolicy';
import { cn } from '../lib/cn'; import { cn } from '../lib/cn';
@ -12,7 +12,6 @@ import {
passwordTooLong, passwordTooLong,
PASSWORD_MAX_CHARS, PASSWORD_MAX_CHARS,
} from '../utils/passwordPolicy'; } from '../utils/passwordPolicy';
import logo from '../assets/logo.png';
/** Login gate. Redirects to /daily once authenticated. */ /** Login gate. Redirects to /daily once authenticated. */
export function LoginPage() { export function LoginPage() {
@ -149,20 +148,10 @@ export function LoginPage() {
} }
return ( return (
<div className="min-h-screen flex items-center justify-center p-4 bg-emerald-50"> <AuthShell
<Card className="w-full max-w-[420px] shadow-lg border-emerald-100 bg-white p-6 sm:p-7"> title={changeRequired ? 'Set a New Password' : 'Welcome Back'}
<div className="flex flex-col gap-2 items-center mb-6 text-center"> subtitle={changeRequired ? changeRequired.message : 'Sign in to continue to Krishna Sales'}
<img src={logo} alt="Krishna Sales Logo" className="h-16 w-auto object-contain" /> >
<h1 className="m-0 text-2xl font-extrabold text-emerald-900 tracking-[-0.02em]">
{changeRequired ? 'Set a New Password' : 'Krishna Sales'}
</h1>
{changeRequired && (
<p className="text-xs text-slate-600 mt-1 max-w-[340px] leading-relaxed">
{changeRequired.message}
</p>
)}
</div>
{error && ( {error && (
<div className="mb-4 text-xs text-rose-700 bg-rose-50 border border-rose-200 p-3 rounded-lg flex items-start gap-2"> <div className="mb-4 text-xs text-rose-700 bg-rose-50 border border-rose-200 p-3 rounded-lg flex items-start gap-2">
<AlertCircle size={16} className="shrink-0 mt-0.5" /> <AlertCircle size={16} className="shrink-0 mt-0.5" />
@ -182,10 +171,8 @@ export function LoginPage() {
{/* New Password */} {/* New Password */}
<div className="flex flex-col gap-1.5 font-sans"> <div className="flex flex-col gap-1.5 font-sans">
<label className="text-xs font-semibold text-slate-700">
New password <span className="text-emerald-500">*</span>
</label>
<div className="relative flex items-center"> <div className="relative flex items-center">
<Lock size={18} className="absolute left-4 text-slate-500 pointer-events-none" />
<input <input
type={showNewPassword ? 'text' : 'password'} type={showNewPassword ? 'text' : 'password'}
autoComplete="new-password" autoComplete="new-password"
@ -198,21 +185,22 @@ export function LoginPage() {
autoFocus autoFocus
disabled={busy} disabled={busy}
className={cn( className={cn(
'h-[42px] w-full px-3 pr-10 rounded-md outline-none transition-all text-sm', 'h-12 w-full pl-11 pr-11 rounded-full outline-none transition-all text-sm',
fieldErrors.newPassword fieldErrors.newPassword
? 'border-2 border-rose-500 bg-rose-50/20 text-rose-900 focus:ring-2 focus:ring-rose-400 focus:border-rose-500' ? 'border-2 border-rose-500 bg-rose-50/20 text-rose-900 focus:ring-2 focus:ring-rose-400'
: 'border border-emerald-200 bg-emerald-50/50 text-emerald-900 placeholder:text-slate-400 focus:ring-2 focus:ring-emerald-400 focus:border-emerald-400' : 'border border-slate-200 bg-slate-100/80 text-slate-900 placeholder:text-slate-500 focus:ring-2 focus:ring-emerald-300 focus:border-emerald-400'
)} )}
placeholder="Enter new password" aria-label="New password"
placeholder="New password"
/> />
<button <button
type="button" type="button"
onClick={() => setShowNewPassword((s) => !s)} onClick={() => setShowNewPassword((s) => !s)}
disabled={busy} disabled={busy}
tabIndex={-1} tabIndex={-1}
className="absolute right-3 text-slate-400 hover:text-slate-600 transition-colors cursor-pointer" className="absolute right-4 text-slate-600 hover:text-slate-800 transition-colors cursor-pointer"
> >
{showNewPassword ? <EyeOff size={16} /> : <Eye size={16} />} {showNewPassword ? <Eye size={18} /> : <EyeOff size={18} />}
</button> </button>
</div> </div>
<PasswordRulesChecklist <PasswordRulesChecklist
@ -225,10 +213,8 @@ export function LoginPage() {
{/* Confirm New Password */} {/* Confirm New Password */}
<div className="flex flex-col gap-1.5 font-sans"> <div className="flex flex-col gap-1.5 font-sans">
<label className="text-xs font-semibold text-slate-700">
Confirm new password <span className="text-emerald-500">*</span>
</label>
<div className="relative flex items-center"> <div className="relative flex items-center">
<Lock size={18} className="absolute left-4 text-slate-500 pointer-events-none" />
<input <input
type={showConfirmPassword ? 'text' : 'password'} type={showConfirmPassword ? 'text' : 'password'}
autoComplete="new-password" autoComplete="new-password"
@ -240,11 +226,12 @@ export function LoginPage() {
}} }}
disabled={busy} disabled={busy}
className={cn( className={cn(
'h-[42px] w-full px-3 pr-10 rounded-md outline-none transition-all text-sm', 'h-12 w-full pl-11 pr-11 rounded-full outline-none transition-all text-sm',
fieldErrors.confirmPassword fieldErrors.confirmPassword
? 'border-2 border-rose-500 bg-rose-50/20 text-rose-900 focus:ring-2 focus:ring-rose-400 focus:border-rose-500' ? 'border-2 border-rose-500 bg-rose-50/20 text-rose-900 focus:ring-2 focus:ring-rose-400'
: 'border border-emerald-200 bg-emerald-50/50 text-emerald-900 placeholder:text-slate-400 focus:ring-2 focus:ring-emerald-400 focus:border-emerald-400' : 'border border-slate-200 bg-slate-100/80 text-slate-900 placeholder:text-slate-500 focus:ring-2 focus:ring-emerald-300 focus:border-emerald-400'
)} )}
aria-label="Confirm new password"
placeholder="Confirm new password" placeholder="Confirm new password"
/> />
<button <button
@ -252,9 +239,9 @@ export function LoginPage() {
onClick={() => setShowConfirmPassword((s) => !s)} onClick={() => setShowConfirmPassword((s) => !s)}
disabled={busy} disabled={busy}
tabIndex={-1} tabIndex={-1}
className="absolute right-3 text-slate-400 hover:text-slate-600 transition-colors cursor-pointer" className="absolute right-4 text-slate-600 hover:text-slate-800 transition-colors cursor-pointer"
> >
{showConfirmPassword ? <EyeOff size={16} /> : <Eye size={16} />} {showConfirmPassword ? <Eye size={18} /> : <EyeOff size={18} />}
</button> </button>
</div> </div>
</div> </div>
@ -263,9 +250,9 @@ export function LoginPage() {
type="submit" type="submit"
full full
disabled={busy} disabled={busy}
className="mt-2 bg-emerald-600 hover:bg-emerald-700 text-white border-transparent" className="mt-1 !rounded-full !h-12 !text-base bg-gradient-to-r from-[#3aa75a] to-[#0f5a30] hover:brightness-110 text-white border-transparent shadow-lg shadow-emerald-900/20"
> >
{busy ? 'Updating & Signing in…' : 'Update Password & Sign In'} {busy ? 'Updating & Signing in…' : (<>Update Password & Sign In <ArrowRight size={18} className="ml-2" /></>)}
</Button> </Button>
<button <button
@ -281,12 +268,11 @@ export function LoginPage() {
) : ( ) : (
/* Standard Login Form */ /* Standard Login Form */
<form noValidate onSubmit={submit} className="flex flex-col gap-4"> <form noValidate onSubmit={submit} className="flex flex-col gap-4">
<label className="flex flex-col gap-1.5 font-sans"> <div className="relative flex items-center">
<span className="text-sm font-semibold text-emerald-800"> <Mail size={18} className="absolute left-4 text-slate-500 pointer-events-none" />
Email <span className="text-emerald-500">*</span>
</span>
<input <input
type="email" type="email"
aria-label="Email"
value={email} value={email}
onChange={(e) => { onChange={(e) => {
setEmail(e.target.value); setEmail(e.target.value);
@ -296,30 +282,21 @@ export function LoginPage() {
autoFocus autoFocus
disabled={busy} disabled={busy}
className={cn( className={cn(
'h-[42px] px-3 rounded-md outline-none transition-all text-sm', 'h-12 w-full pl-11 pr-4 rounded-full outline-none transition-all text-sm',
fieldErrors.email fieldErrors.email
? 'border-2 border-rose-500 bg-rose-50/20 text-rose-900 focus:ring-2 focus:ring-rose-400 focus:border-rose-500' ? 'border-2 border-rose-500 bg-rose-50/20 text-rose-900 focus:ring-2 focus:ring-rose-400'
: 'border border-emerald-200 bg-emerald-50/50 text-emerald-900 placeholder:text-slate-400 focus:ring-2 focus:ring-emerald-400 focus:border-emerald-400' : 'border border-slate-200 bg-slate-100/80 text-slate-900 placeholder:text-slate-500 focus:ring-2 focus:ring-emerald-300 focus:border-emerald-400'
)} )}
placeholder="Enter your email" placeholder="Email"
/> />
</label> </div>
<div className="flex flex-col gap-1.5 font-sans"> <div className="flex flex-col gap-3">
<div className="flex items-center justify-between">
<span className="text-sm font-semibold text-emerald-800">
Password <span className="text-emerald-500">*</span>
</span>
<Link
to="/forgot-password"
className="text-xs font-semibold text-emerald-700 hover:text-emerald-900 transition-colors"
>
Forgot password?
</Link>
</div>
<div className="relative flex items-center"> <div className="relative flex items-center">
<Lock size={18} className="absolute left-4 text-slate-500 pointer-events-none" />
<input <input
type={showPassword ? 'text' : 'password'} type={showPassword ? 'text' : 'password'}
aria-label="Password"
value={password} value={password}
onChange={(e) => { onChange={(e) => {
setPassword(e.target.value); setPassword(e.target.value);
@ -328,36 +305,42 @@ export function LoginPage() {
}} }}
disabled={busy} disabled={busy}
className={cn( className={cn(
'h-[42px] w-full px-3 pr-10 rounded-md outline-none transition-all text-sm', 'h-12 w-full pl-11 pr-11 rounded-full outline-none transition-all text-sm',
fieldErrors.password fieldErrors.password
? 'border-2 border-rose-500 bg-rose-50/20 text-rose-900 focus:ring-2 focus:ring-rose-400 focus:border-rose-500' ? 'border-2 border-rose-500 bg-rose-50/20 text-rose-900 focus:ring-2 focus:ring-rose-400'
: 'border border-emerald-200 bg-emerald-50/50 text-emerald-900 placeholder:text-slate-400 focus:ring-2 focus:ring-emerald-400 focus:border-emerald-400' : 'border border-slate-200 bg-slate-100/80 text-slate-900 placeholder:text-slate-500 focus:ring-2 focus:ring-emerald-300 focus:border-emerald-400'
)} )}
placeholder="Enter your password" placeholder="Password"
/> />
<button <button
type="button" type="button"
onClick={() => setShowPassword((s) => !s)} onClick={() => setShowPassword((s) => !s)}
disabled={busy} disabled={busy}
tabIndex={-1} tabIndex={-1}
className="absolute right-3 text-slate-400 hover:text-slate-600 transition-colors cursor-pointer" aria-label={showPassword ? 'Hide password' : 'Show password'}
className="absolute right-4 text-slate-600 hover:text-slate-800 transition-colors cursor-pointer"
> >
{showPassword ? <EyeOff size={16} /> : <Eye size={16} />} {showPassword ? <Eye size={18} /> : <EyeOff size={18} />}
</button> </button>
</div> </div>
<Link
to="/forgot-password"
className="self-end text-sm font-medium text-emerald-900 hover:text-emerald-700 transition-colors"
>
Forgot password?
</Link>
</div> </div>
<Button <Button
type="submit" type="submit"
full full
disabled={busy} disabled={busy}
className="mt-2 bg-emerald-600 hover:bg-emerald-700 text-white border-transparent" className="mt-1 !rounded-full !h-12 !text-base bg-gradient-to-r from-[#3aa75a] to-[#0f5a30] hover:brightness-110 text-white border-transparent shadow-lg shadow-emerald-900/20"
> >
{busy ? 'Signing in…' : 'Sign in'} {busy ? 'Signing in…' : (<>Sign In <ArrowRight size={18} className="ml-2" /></>)}
</Button> </Button>
</form> </form>
)} )}
</Card> </AuthShell>
</div>
); );
} }