Dealer_Onboard_Frontend/src/components/auth/LoginPage.tsx

359 lines
14 KiB
TypeScript

import { useState } from 'react';
import { Button } from '../ui/button';
import { Input } from '../ui/input';
import { Label } from '../ui/label';
import { Checkbox } from '../ui/checkbox';
import { AlertCircle, Copy, Check, Eye, EyeOff } from 'lucide-react';
import { mockUsers } from '../../lib/mock-data';
import { toast } from 'sonner';
interface LoginPageProps {
onLogin: (email: string, password: string) => void;
}
export function LoginPage({ onLogin }: LoginPageProps) {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [rememberMe, setRememberMe] = useState(false);
const [error, setError] = useState('');
const [showForgotPassword, setShowForgotPassword] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [showPassword, setShowPassword] = useState(false);
const [copiedIndex, setCopiedIndex] = useState<number | null>(null);
const copyToClipboard = async (text: string, index: number) => {
try {
// Try modern clipboard API first
if (navigator.clipboard && navigator.clipboard.writeText) {
await navigator.clipboard.writeText(text);
setCopiedIndex(index);
setTimeout(() => setCopiedIndex(null), 2000);
return;
}
} catch (err) {
// Clipboard API blocked, try fallback method
}
// Fallback method for older browsers or blocked clipboard
try {
const textArea = document.createElement('textarea');
textArea.value = text;
textArea.style.position = 'fixed';
textArea.style.left = '-999999px';
textArea.style.top = '-999999px';
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
const successful = document.execCommand('copy');
document.body.removeChild(textArea);
if (successful) {
setCopiedIndex(index);
setTimeout(() => setCopiedIndex(null), 2000);
}
} catch (err) {
// Both methods failed - silently ignore
}
};
const quickLogin = async (userEmail: string, userPassword: string) => {
setEmail(userEmail);
setPassword(userPassword);
setError('');
setIsLoading(true);
try {
await onLogin(userEmail, userPassword);
} catch (err: any) {
const msg = err.response?.data?.message || err.message || 'Auto-login failed';
setError(msg);
toast.error(msg);
} finally {
setIsLoading(false);
}
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (isLoading) return;
setError('');
if (!email || !password) {
setError('Please enter both email and password');
return;
}
setIsLoading(true);
try {
await onLogin(email, password);
} catch (err: any) {
const msg = err.response?.data?.message || err.message || 'Login failed';
setError(msg);
toast.error(msg);
} finally {
setIsLoading(false);
}
};
const handleForgotPassword = (e: React.FormEvent) => {
e.preventDefault();
// Mock password reset
alert('Password reset link sent to ' + email);
setShowForgotPassword(false);
};
return (
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-slate-900 via-slate-800 to-slate-900 p-4 overflow-y-auto">
{/* Background decorative elements */}
<div className="absolute inset-0 overflow-hidden pointer-events-none">
<div className="absolute -top-40 -right-40 w-80 h-80 bg-amber-600/10 rounded-full blur-3xl"></div>
<div className="absolute -bottom-40 -left-40 w-80 h-80 bg-amber-600/10 rounded-full blur-3xl"></div>
</div>
<div className="relative w-full max-w-6xl grid md:grid-cols-2 gap-8 my-8">
{/* Left side - Login Form */}
<div className="flex flex-col">
{/* Logo and Header */}
<div className="text-center mb-8">
<div className="inline-flex items-center justify-center w-20 h-20 bg-amber-600 rounded-full mb-4">
<svg viewBox="0 0 24 24" className="w-12 h-12 text-white" fill="currentColor">
<path d="M12 2L4 6v6c0 5.5 3.8 10.7 8 12 4.2-1.3 8-6.5 8-12V6l-8-4zm0 2.2l6 3v4.8c0 4.5-3.1 8.7-6 10-2.9-1.3-6-5.5-6-10V7.2l6-3z" />
<circle cx="12" cy="12" r="3" />
</svg>
</div>
<h1 className="text-white mb-2">Royal Enfield</h1>
<p className="text-slate-400">Dealership Onboarding System</p>
</div>
{/* Login Form */}
<div className="bg-white rounded-lg shadow-2xl p-8">
{!showForgotPassword ? (
<form onSubmit={handleSubmit} className="space-y-6">
<div className="space-y-2">
<Label htmlFor="email">Email Address</Label>
<Input
id="email"
type="email"
placeholder="you@royalenfield.com"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="w-full"
disabled={isLoading}
/>
</div>
<div className="space-y-2">
<Label htmlFor="password">Password</Label>
<div className="relative">
<Input
id="password"
type={showPassword ? "text" : "password"}
placeholder="Enter your password"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="w-full pr-10"
disabled={isLoading}
/>
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="absolute right-3 top-1/2 -translate-y-1/2 text-slate-400 hover:text-slate-600 focus:outline-none"
disabled={isLoading}
>
{showPassword ? (
<EyeOff className="w-5 h-5" />
) : (
<Eye className="w-5 h-5" />
)}
</button>
</div>
</div>
<div className="flex items-center justify-between">
<div className="flex items-center space-x-2">
<Checkbox
id="remember"
checked={rememberMe}
onCheckedChange={(checked) => setRememberMe(checked as boolean)}
disabled={isLoading}
/>
<Label htmlFor="remember" className="cursor-pointer">
Remember Me
</Label>
</div>
<button
type="button"
onClick={() => setShowForgotPassword(true)}
className="text-amber-600 hover:text-amber-700 disabled:opacity-50"
disabled={isLoading}
>
Forgot Password?
</button>
</div>
{error && (
<div className="flex items-center gap-2 p-3 bg-red-50 border border-red-200 rounded-md">
<AlertCircle className="w-4 h-4 text-red-600" />
<span className="text-red-600 font-medium text-sm">{error}</span>
</div>
)}
<Button
type="submit"
className="w-full bg-amber-600 hover:bg-amber-700 h-11"
disabled={isLoading}
>
{isLoading ? (
<div className="flex items-center justify-center gap-2">
<div className="w-4 h-4 border-2 border-white/30 border-t-white rounded-full animate-spin" />
<span>Logging in...</span>
</div>
) : (
'Login'
)}
</Button>
<div className="text-center">
<div className="relative my-4">
<div className="absolute inset-0 flex items-center">
<span className="w-full border-t border-slate-200" />
</div>
<div className="relative flex justify-center text-xs uppercase">
<span className="bg-white px-2 text-slate-500">Or</span>
</div>
</div>
<Button
type="button"
variant="outline"
className="w-full border-amber-600 text-amber-600 hover:bg-amber-50 h-11"
onClick={() => window.location.href = '/prospective-login'}
>
Prospective User Login
</Button>
</div>
</form>
) : (
<form onSubmit={handleForgotPassword} className="space-y-6">
<div>
<h2 className="mb-2">Reset Password</h2>
<p className="text-slate-600">Enter your email to receive a password reset link</p>
</div>
<div className="space-y-2">
<Label htmlFor="reset-email">Email Address</Label>
<Input
id="reset-email"
type="email"
placeholder="you@royalenfield.com"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
className="w-full"
/>
</div>
<div className="flex gap-3">
<Button
type="button"
variant="outline"
onClick={() => setShowForgotPassword(false)}
className="flex-1"
>
Back to Login
</Button>
<Button type="submit" className="flex-1 bg-amber-600 hover:bg-amber-700">
Send Reset Link
</Button>
</div>
</form>
)}
</div>
{/* Footer */}
<div className="text-center mt-6 text-slate-400">
<p>© 2025 Royal Enfield. All rights reserved.</p>
</div>
</div>
{/* Right side - Test Credentials */}
<div className="bg-white rounded-lg shadow-2xl p-8 overflow-y-auto max-h-[800px]">
<div className="mb-6">
<h2 className="mb-2">Test User Credentials</h2>
<p className="text-slate-600">Click on any user to auto-login</p>
</div>
<div className="space-y-3">
{mockUsers.map((user, index) => (
<div
key={user.id}
className="border border-slate-200 rounded-lg p-4 hover:border-amber-600 hover:bg-amber-50 transition-all cursor-pointer"
onClick={() => quickLogin(user.email, user.password)}
>
<div className="flex items-start justify-between mb-2">
<div className="flex-1">
<div className="flex items-center gap-2 mb-1">
<span className="px-2 py-1 bg-amber-100 text-amber-800 rounded text-xs">
{user.role}
</span>
</div>
<p className="text-slate-900">{user.name}</p>
</div>
</div>
<div className="space-y-2">
<div className="flex items-center justify-between gap-2">
<div className="flex-1">
<p className="text-slate-500">Email:</p>
<p className="text-slate-900 font-mono break-all">{user.email}</p>
</div>
<button
type="button"
onClick={(e) => {
e.stopPropagation();
copyToClipboard(user.email, index * 2);
}}
className="p-2 hover:bg-slate-100 rounded"
>
{copiedIndex === index * 2 ? (
<Check className="w-4 h-4 text-green-600" />
) : (
<Copy className="w-4 h-4 text-slate-400" />
)}
</button>
</div>
<div className="flex items-center justify-between gap-2">
<div className="flex-1">
<p className="text-slate-500">Password:</p>
<p className="text-slate-900 font-mono">{user.password}</p>
</div>
<button
type="button"
onClick={(e) => {
e.stopPropagation();
copyToClipboard(user.password, index * 2 + 1);
}}
className="p-2 hover:bg-slate-100 rounded"
>
{copiedIndex === index * 2 + 1 ? (
<Check className="w-4 h-4 text-green-600" />
) : (
<Copy className="w-4 h-4 text-slate-400" />
)}
</button>
</div>
</div>
<div className="mt-3 pt-3 border-t border-slate-200">
<p className="text-amber-600 text-center">Click to login as {user.role}</p>
</div>
</div>
))}
</div>
</div>
</div>
</div>
);
}