login screen fix
This commit is contained in:
parent
144f70cd34
commit
dc0b46fbd9
63
src/components/auth/AuthShell.tsx
Normal file
63
src/components/auth/AuthShell.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@ -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 { Eye, EyeOff, ArrowLeft, Mail, KeyRound, CheckCircle2, AlertCircle } from 'lucide-react';
|
||||
import { Card } from '../components/reusable';
|
||||
import { Eye, EyeOff, ArrowLeft, Mail, Lock, KeyRound, CheckCircle2, AlertCircle } from 'lucide-react';
|
||||
import { AuthShell } from '../components/auth/AuthShell';
|
||||
import { Button } from '../components/buttons';
|
||||
import { PasswordRulesChecklist } from '../components/auth/PasswordRulesChecklist';
|
||||
import { forgotPassword, verifyResetCode, resetPassword } from '../api/auth';
|
||||
@ -11,7 +11,6 @@ import {
|
||||
passwordTooLong,
|
||||
PASSWORD_MAX_CHARS,
|
||||
} from '../utils/passwordPolicy';
|
||||
import logo from '../assets/logo.png';
|
||||
|
||||
const CODE_LENGTH = 6;
|
||||
const RESEND_COOLDOWN = 60; // seconds
|
||||
@ -34,6 +33,13 @@ export function ForgotPasswordPage() {
|
||||
const [message, setMessage] = useState('');
|
||||
|
||||
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
|
||||
useEffect(() => {
|
||||
@ -137,31 +143,23 @@ export function ForgotPasswordPage() {
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center p-4 bg-emerald-50">
|
||||
<Card className="w-full max-w-[420px] shadow-lg border-emerald-100 bg-white p-6 sm:p-7">
|
||||
<div className="flex flex-col gap-2 items-center mb-6 text-center">
|
||||
<img src={logo} alt="Krishna Sales Logo" className="h-14 w-auto object-contain" />
|
||||
<h1 className="text-xl font-extrabold text-emerald-950 tracking-tight">
|
||||
{done
|
||||
? 'Password Reset Complete'
|
||||
: step === 'email'
|
||||
? 'Forgot Password'
|
||||
: step === 'code'
|
||||
? 'Enter Verification Code'
|
||||
: 'Create New Password'}
|
||||
</h1>
|
||||
<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>
|
||||
const title = done
|
||||
? 'Password Reset Complete'
|
||||
: step === 'email'
|
||||
? 'Forgot Password'
|
||||
: step === 'code'
|
||||
? 'Enter Verification Code'
|
||||
: 'Create New Password';
|
||||
const subtitle = 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.';
|
||||
|
||||
return (
|
||||
<AuthShell title={title} subtitle={subtitle}>
|
||||
{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">
|
||||
<AlertCircle size={16} className="shrink-0 mt-0.5" />
|
||||
@ -175,35 +173,33 @@ export function ForgotPasswordPage() {
|
||||
<CheckCircle2 size={36} className="text-emerald-600" />
|
||||
<span className="font-semibold">{message}</span>
|
||||
</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
|
||||
</Button>
|
||||
</div>
|
||||
) : step === 'email' ? (
|
||||
/* Step 1: Email Form */
|
||||
<form onSubmit={handleEmailSubmit} className="flex flex-col gap-4">
|
||||
<label className="flex flex-col gap-1.5 font-sans">
|
||||
<span className="text-sm font-semibold text-emerald-800 flex items-center gap-1.5">
|
||||
<Mail size={15} />
|
||||
Email address <span className="text-emerald-500">*</span>
|
||||
</span>
|
||||
<div className="relative flex items-center">
|
||||
<Mail size={18} className="absolute left-4 text-slate-500 pointer-events-none" />
|
||||
<input
|
||||
aria-label="Email address"
|
||||
type="email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
required
|
||||
autoFocus
|
||||
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"
|
||||
placeholder="you@example.com"
|
||||
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="Email"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
full
|
||||
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'}
|
||||
</Button>
|
||||
@ -219,12 +215,11 @@ export function ForgotPasswordPage() {
|
||||
) : step === 'code' ? (
|
||||
/* Step 2: OTP Verification Form */
|
||||
<form onSubmit={handleCodeSubmit} className="flex flex-col gap-4">
|
||||
<label className="flex flex-col gap-1.5 font-sans">
|
||||
<span className="text-sm font-semibold text-emerald-800 flex items-center gap-1.5">
|
||||
<KeyRound size={15} />
|
||||
Reset Code <span className="text-emerald-500">*</span>
|
||||
</span>
|
||||
<div className="relative flex items-center">
|
||||
<KeyRound size={18} className="absolute left-4 text-slate-500 pointer-events-none" />
|
||||
<input
|
||||
ref={codeInputRef}
|
||||
aria-label="Reset code"
|
||||
type="text"
|
||||
inputMode="numeric"
|
||||
autoComplete="one-time-code"
|
||||
@ -234,16 +229,16 @@ export function ForgotPasswordPage() {
|
||||
required
|
||||
autoFocus
|
||||
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"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
full
|
||||
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'}
|
||||
</Button>
|
||||
@ -274,11 +269,10 @@ export function ForgotPasswordPage() {
|
||||
/* Step 3: New Password Form */
|
||||
<form onSubmit={handlePasswordSubmit} className="flex flex-col gap-4">
|
||||
<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">
|
||||
<Lock size={18} className="absolute left-4 text-slate-500 pointer-events-none" />
|
||||
<input
|
||||
aria-label="New password"
|
||||
type={showPassword ? 'text' : 'password'}
|
||||
autoComplete="new-password"
|
||||
value={password}
|
||||
@ -286,45 +280,44 @@ export function ForgotPasswordPage() {
|
||||
required
|
||||
autoFocus
|
||||
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"
|
||||
placeholder="Enter new password"
|
||||
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="New password"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowPassword((s) => !s)}
|
||||
disabled={submitting}
|
||||
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>
|
||||
</div>
|
||||
<PasswordRulesChecklist policy={policy} password={password} email={email} className="mt-1" />
|
||||
</div>
|
||||
|
||||
<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">
|
||||
<Lock size={18} className="absolute left-4 text-slate-500 pointer-events-none" />
|
||||
<input
|
||||
aria-label="Confirm new password"
|
||||
type={showConfirm ? 'text' : 'password'}
|
||||
autoComplete="new-password"
|
||||
value={confirm}
|
||||
onChange={(e) => setConfirm(e.target.value)}
|
||||
required
|
||||
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"
|
||||
placeholder="Re-enter new password"
|
||||
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="Confirm new password"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowConfirm((s) => !s)}
|
||||
disabled={submitting}
|
||||
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>
|
||||
</div>
|
||||
</div>
|
||||
@ -333,7 +326,7 @@ export function ForgotPasswordPage() {
|
||||
type="submit"
|
||||
full
|
||||
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'}
|
||||
</Button>
|
||||
@ -351,7 +344,6 @@ export function ForgotPasswordPage() {
|
||||
</button>
|
||||
</form>
|
||||
)}
|
||||
</Card>
|
||||
</div>
|
||||
</AuthShell>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import { useState, type FormEvent } from 'react';
|
||||
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 { Button } from '../components/buttons';
|
||||
import { Card } from '../components/reusable';
|
||||
import { AuthShell } from '../components/auth/AuthShell';
|
||||
import { PasswordRulesChecklist } from '../components/auth/PasswordRulesChecklist';
|
||||
import { usePasswordPolicy } from '../hooks/usePasswordPolicy';
|
||||
import { cn } from '../lib/cn';
|
||||
@ -12,7 +12,6 @@ import {
|
||||
passwordTooLong,
|
||||
PASSWORD_MAX_CHARS,
|
||||
} from '../utils/passwordPolicy';
|
||||
import logo from '../assets/logo.png';
|
||||
|
||||
/** Login gate. Redirects to /daily once authenticated. */
|
||||
export function LoginPage() {
|
||||
@ -149,20 +148,10 @@ export function LoginPage() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center p-4 bg-emerald-50">
|
||||
<Card className="w-full max-w-[420px] shadow-lg border-emerald-100 bg-white p-6 sm:p-7">
|
||||
<div className="flex flex-col gap-2 items-center mb-6 text-center">
|
||||
<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>
|
||||
|
||||
<AuthShell
|
||||
title={changeRequired ? 'Set a New Password' : 'Welcome Back'}
|
||||
subtitle={changeRequired ? changeRequired.message : 'Sign in to continue to Krishna Sales'}
|
||||
>
|
||||
{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">
|
||||
<AlertCircle size={16} className="shrink-0 mt-0.5" />
|
||||
@ -182,10 +171,8 @@ export function LoginPage() {
|
||||
|
||||
{/* New Password */}
|
||||
<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">
|
||||
<Lock size={18} className="absolute left-4 text-slate-500 pointer-events-none" />
|
||||
<input
|
||||
type={showNewPassword ? 'text' : 'password'}
|
||||
autoComplete="new-password"
|
||||
@ -198,21 +185,22 @@ export function LoginPage() {
|
||||
autoFocus
|
||||
disabled={busy}
|
||||
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
|
||||
? 'border-2 border-rose-500 bg-rose-50/20 text-rose-900 focus:ring-2 focus:ring-rose-400 focus:border-rose-500'
|
||||
: '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-2 border-rose-500 bg-rose-50/20 text-rose-900 focus:ring-2 focus:ring-rose-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
|
||||
type="button"
|
||||
onClick={() => setShowNewPassword((s) => !s)}
|
||||
disabled={busy}
|
||||
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>
|
||||
</div>
|
||||
<PasswordRulesChecklist
|
||||
@ -225,10 +213,8 @@ export function LoginPage() {
|
||||
|
||||
{/* Confirm New Password */}
|
||||
<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">
|
||||
<Lock size={18} className="absolute left-4 text-slate-500 pointer-events-none" />
|
||||
<input
|
||||
type={showConfirmPassword ? 'text' : 'password'}
|
||||
autoComplete="new-password"
|
||||
@ -240,11 +226,12 @@ export function LoginPage() {
|
||||
}}
|
||||
disabled={busy}
|
||||
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
|
||||
? 'border-2 border-rose-500 bg-rose-50/20 text-rose-900 focus:ring-2 focus:ring-rose-400 focus:border-rose-500'
|
||||
: '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-2 border-rose-500 bg-rose-50/20 text-rose-900 focus:ring-2 focus:ring-rose-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"
|
||||
/>
|
||||
<button
|
||||
@ -252,9 +239,9 @@ export function LoginPage() {
|
||||
onClick={() => setShowConfirmPassword((s) => !s)}
|
||||
disabled={busy}
|
||||
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>
|
||||
</div>
|
||||
</div>
|
||||
@ -263,9 +250,9 @@ export function LoginPage() {
|
||||
type="submit"
|
||||
full
|
||||
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
|
||||
@ -281,12 +268,11 @@ export function LoginPage() {
|
||||
) : (
|
||||
/* Standard Login Form */
|
||||
<form noValidate onSubmit={submit} className="flex flex-col gap-4">
|
||||
<label className="flex flex-col gap-1.5 font-sans">
|
||||
<span className="text-sm font-semibold text-emerald-800">
|
||||
Email <span className="text-emerald-500">*</span>
|
||||
</span>
|
||||
<div className="relative flex items-center">
|
||||
<Mail size={18} className="absolute left-4 text-slate-500 pointer-events-none" />
|
||||
<input
|
||||
type="email"
|
||||
aria-label="Email"
|
||||
value={email}
|
||||
onChange={(e) => {
|
||||
setEmail(e.target.value);
|
||||
@ -296,30 +282,21 @@ export function LoginPage() {
|
||||
autoFocus
|
||||
disabled={busy}
|
||||
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
|
||||
? 'border-2 border-rose-500 bg-rose-50/20 text-rose-900 focus:ring-2 focus:ring-rose-400 focus:border-rose-500'
|
||||
: '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-2 border-rose-500 bg-rose-50/20 text-rose-900 focus:ring-2 focus:ring-rose-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 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="flex flex-col gap-3">
|
||||
<div className="relative flex items-center">
|
||||
<Lock size={18} className="absolute left-4 text-slate-500 pointer-events-none" />
|
||||
<input
|
||||
type={showPassword ? 'text' : 'password'}
|
||||
aria-label="Password"
|
||||
value={password}
|
||||
onChange={(e) => {
|
||||
setPassword(e.target.value);
|
||||
@ -328,36 +305,42 @@ export function LoginPage() {
|
||||
}}
|
||||
disabled={busy}
|
||||
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
|
||||
? 'border-2 border-rose-500 bg-rose-50/20 text-rose-900 focus:ring-2 focus:ring-rose-400 focus:border-rose-500'
|
||||
: '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-2 border-rose-500 bg-rose-50/20 text-rose-900 focus:ring-2 focus:ring-rose-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
|
||||
type="button"
|
||||
onClick={() => setShowPassword((s) => !s)}
|
||||
disabled={busy}
|
||||
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>
|
||||
</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>
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
full
|
||||
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>
|
||||
</form>
|
||||
)}
|
||||
</Card>
|
||||
</div>
|
||||
</AuthShell>
|
||||
);
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user