8a749d3041
Squashed commits for V2 release including Dark Mode, Chatbot, and Landing Page enhancements.
213 lines
12 KiB
React
213 lines
12 KiB
React
import React, { useState, useRef } from 'react';
|
|
import { useNavigate } from 'react-router-dom';
|
|
import { useAuth } from '../context/AuthContext';
|
|
import { User, Briefcase, Lock, ArrowRight, AlertCircle, Home } from 'lucide-react';
|
|
import { SpotlightCard } from '../components/SpotlightCard';
|
|
|
|
|
|
// Rainbow Button Component
|
|
const RainbowButton = ({ children, onClick, type = "button", className = "" }) => {
|
|
const btnRef = useRef(null);
|
|
const [position, setPosition] = useState({ x: 0, y: 0 });
|
|
const [opacity, setOpacity] = useState(0);
|
|
|
|
const handleMouseMove = (e) => {
|
|
if (!btnRef.current) return;
|
|
const rect = btnRef.current.getBoundingClientRect();
|
|
setPosition({ x: e.clientX - rect.left, y: e.clientY - rect.top });
|
|
};
|
|
|
|
return (
|
|
<button
|
|
ref={btnRef}
|
|
onClick={onClick}
|
|
type={type}
|
|
onMouseMove={handleMouseMove}
|
|
onMouseEnter={() => setOpacity(1)}
|
|
onMouseLeave={() => setOpacity(0)}
|
|
onFocus={() => setOpacity(1)}
|
|
onBlur={() => setOpacity(0)}
|
|
className={`relative group w-full py-4 rounded-xl font-bold text-white overflow-hidden transition-all duration-300 transform active:scale-[0.98] ${className}`}
|
|
>
|
|
{/* Rainbow Glow Layer */}
|
|
<div
|
|
className='pointer-events-none absolute -inset-px opacity-0 transition duration-300'
|
|
style={{
|
|
opacity,
|
|
background: `conic-gradient(from 0deg, #ff0000, #ff8800, #ffff00, #00ff00, #00ffff, #0000ff, #ff00ff, #ff0000)`,
|
|
WebkitMaskImage: `radial-gradient(150px circle at ${position.x}px ${position.y}px, black, transparent 80%)`,
|
|
maskImage: `radial-gradient(150px circle at ${position.x}px ${position.y}px, black, transparent 80%)`,
|
|
}}
|
|
/>
|
|
|
|
{/* Button Background (Glass/Neo) */}
|
|
<div className="absolute inset-[1px] rounded-[11px] bg-zinc-900 border border-white/10 group-hover:bg-zinc-800 transition-colors z-0" />
|
|
|
|
{/* Content */}
|
|
<div className="relative z-10 flex items-center justify-center space-x-2">
|
|
{children}
|
|
</div>
|
|
</button>
|
|
);
|
|
};
|
|
|
|
const Login = () => {
|
|
const [isEmployee, setIsEmployee] = useState(false);
|
|
const [identifier, setIdentifier] = useState('');
|
|
const [password, setPassword] = useState('');
|
|
const [error, setError] = useState('');
|
|
|
|
const { login } = useAuth();
|
|
const navigate = useNavigate();
|
|
|
|
const handleLogin = (e) => {
|
|
e.preventDefault();
|
|
setError('');
|
|
|
|
if (!identifier || !password) {
|
|
setError('Please fill in all fields');
|
|
return;
|
|
}
|
|
|
|
const type = isEmployee ? 'employee' : 'customer';
|
|
const result = login(identifier, password, type);
|
|
|
|
if (result.success) {
|
|
if (type === 'customer') {
|
|
navigate('/');
|
|
} else {
|
|
navigate('/emp/fa/dashboard'); // Default for employees
|
|
}
|
|
} else {
|
|
setError(result.message);
|
|
}
|
|
};
|
|
|
|
const fillDemo = (role) => {
|
|
if (role === 'customer') {
|
|
setIsEmployee(false);
|
|
setIdentifier('alice');
|
|
setPassword('password');
|
|
} else if (role === 'agent') {
|
|
setIsEmployee(true);
|
|
setIdentifier('FA001');
|
|
setPassword('password');
|
|
} else if (role === 'admin') {
|
|
setIsEmployee(true);
|
|
setIdentifier('ADM01');
|
|
setPassword('password');
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="min-h-screen bg-zinc-50 dark:bg-[#050505] flex items-center justify-center p-4 relative overflow-hidden selection:bg-blue-500/20 dark:selection:bg-white/20 transition-colors duration-300">
|
|
{/* Subtle Ambient Background */}
|
|
<div className="absolute inset-0 bg-[url('https://grainy-gradients.vercel.app/noise.svg')] opacity-[0.03] dark:opacity-5 pointer-events-none mix-blend-overlay"></div>
|
|
<div className="absolute top-0 left-0 w-full h-full overflow-hidden z-0 pointer-events-none">
|
|
<div className="absolute top-[-20%] left-[-20%] w-[70%] h-[70%] bg-purple-500/5 dark:bg-purple-900/10 blur-[150px] rounded-full opacity-50"></div>
|
|
<div className="absolute bottom-[-20%] right-[-20%] w-[70%] h-[70%] bg-blue-500/5 dark:bg-blue-900/10 blur-[150px] rounded-full opacity-50"></div>
|
|
</div>
|
|
|
|
|
|
|
|
<div className="w-full max-w-md z-10 relative">
|
|
<SpotlightCard className="bg-white/60 dark:bg-black/30 backdrop-blur-3xl border-0 ring-1 ring-black/5 dark:ring-white/5 shadow-2xl dark:shadow-2xl">
|
|
<div className="p-8">
|
|
<div className="text-center mb-10 pt-2">
|
|
<div className="w-14 h-14 bg-gradient-to-br from-zinc-100 to-zinc-300 dark:from-white dark:to-zinc-400 rounded-2xl flex items-center justify-center mx-auto mb-6 shadow-xl shadow-black/5 dark:shadow-white/5 ring-1 ring-black/5 dark:ring-white/20">
|
|
<Home size={28} className="text-black" />
|
|
</div>
|
|
<h1 className="text-3xl font-black text-zinc-900 dark:text-white mb-2 tracking-tighter">
|
|
Welcome Back
|
|
</h1>
|
|
<p className="text-zinc-500 font-medium">Sign in to your Plano Realty account</p>
|
|
</div>
|
|
|
|
{/* Neo-Toggle */}
|
|
<div className="flex p-1.5 bg-zinc-100 dark:bg-black/40 rounded-xl mb-8 border border-zinc-200 dark:border-white/5 mx-1">
|
|
<button
|
|
onClick={() => setIsEmployee(false)}
|
|
className={`flex-1 flex items-center justify-center py-2.5 text-xs font-bold uppercase tracking-wider rounded-lg transition-all duration-300 ${!isEmployee
|
|
? 'bg-white dark:bg-zinc-800 text-zinc-900 dark:text-white shadow-sm border border-black/5 dark:border-white/5'
|
|
: 'text-zinc-400 dark:text-zinc-600 hover:text-zinc-600 dark:hover:text-zinc-400'}`}
|
|
>
|
|
<User size={14} className="mr-2" />
|
|
Customer
|
|
</button>
|
|
<button
|
|
onClick={() => setIsEmployee(true)}
|
|
className={`flex-1 flex items-center justify-center py-2.5 text-xs font-bold uppercase tracking-wider rounded-lg transition-all duration-300 ${isEmployee
|
|
? 'bg-white dark:bg-zinc-800 text-zinc-900 dark:text-white shadow-sm border border-black/5 dark:border-white/5'
|
|
: 'text-zinc-400 dark:text-zinc-600 hover:text-zinc-600 dark:hover:text-zinc-400'}`}
|
|
>
|
|
<Briefcase size={14} className="mr-2" />
|
|
Employee
|
|
</button>
|
|
</div>
|
|
|
|
<form onSubmit={handleLogin} className="space-y-6">
|
|
<div className="space-y-2">
|
|
<label className="text-[10px] font-bold text-zinc-500 ml-1 uppercase tracking-widest">
|
|
{isEmployee ? 'Employee ID' : 'Username'}
|
|
</label>
|
|
<div className="relative group">
|
|
<div className="absolute left-4 top-1/2 -translate-y-1/2 text-zinc-500 group-focus-within:text-zinc-900 dark:group-focus-within:text-white transition-colors">
|
|
{isEmployee ? <Briefcase size={18} /> : <User size={18} />}
|
|
</div>
|
|
<input
|
|
type="text"
|
|
value={identifier}
|
|
onChange={(e) => setIdentifier(e.target.value)}
|
|
className="w-full bg-zinc-100 dark:bg-black/20 border border-zinc-200 dark:border-white/5 text-zinc-900 dark:text-white rounded-xl py-4 pl-12 pr-4 focus:outline-none focus:ring-1 focus:ring-black/10 dark:focus:ring-white/20 focus:bg-white dark:focus:bg-black/40 transition-all placeholder:text-zinc-400 dark:placeholder:text-zinc-700 font-medium"
|
|
placeholder={isEmployee ? "e.g., FA001" : "e.g., alice"}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<label className="text-[10px] font-bold text-zinc-500 ml-1 uppercase tracking-widest">Password</label>
|
|
<div className="relative group">
|
|
<div className="absolute left-4 top-1/2 -translate-y-1/2 text-zinc-500 group-focus-within:text-zinc-900 dark:group-focus-within:text-white transition-colors">
|
|
<Lock size={18} />
|
|
</div>
|
|
<input
|
|
type="password"
|
|
value={password}
|
|
onChange={(e) => setPassword(e.target.value)}
|
|
className="w-full bg-zinc-100 dark:bg-black/20 border border-zinc-200 dark:border-white/5 text-zinc-900 dark:text-white rounded-xl py-4 pl-12 pr-4 focus:outline-none focus:ring-1 focus:ring-black/10 dark:focus:ring-white/20 focus:bg-white dark:focus:bg-black/40 transition-all placeholder:text-zinc-400 dark:placeholder:text-zinc-700 font-medium"
|
|
placeholder="Enter your password"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{error && (
|
|
<div className="flex items-center space-x-2 text-red-600 dark:text-red-400 bg-red-50 dark:bg-red-500/10 border border-red-200 dark:border-red-500/20 p-3 rounded-xl text-xs font-bold uppercase tracking-wide">
|
|
<AlertCircle size={16} />
|
|
<span>{error}</span>
|
|
</div>
|
|
)}
|
|
|
|
<RainbowButton type="submit" className="mt-2 text-white">
|
|
<span>Sign In</span>
|
|
<ArrowRight size={18} />
|
|
</RainbowButton>
|
|
</form>
|
|
|
|
{/* Demo Quick Links */}
|
|
<div className="mt-8 pt-6 border-t border-zinc-200 dark:border-white/5">
|
|
<p className="text-[10px] text-center text-zinc-500 uppercase tracking-widest font-bold mb-4">Quick Demo Access</p>
|
|
<div className="flex gap-2 justify-center">
|
|
<button onClick={() => fillDemo('customer')} className="text-[10px] font-bold uppercase tracking-wider bg-zinc-100 dark:bg-zinc-900/50 border border-zinc-200 dark:border-white/5 text-zinc-500 px-3 py-2 rounded-lg hover:border-zinc-300 dark:hover:border-white/20 hover:text-zinc-800 dark:hover:text-zinc-300 transition-colors">Customer</button>
|
|
<button onClick={() => fillDemo('agent')} className="text-[10px] font-bold uppercase tracking-wider bg-zinc-100 dark:bg-zinc-900/50 border border-zinc-200 dark:border-white/5 text-zinc-500 px-3 py-2 rounded-lg hover:border-zinc-300 dark:hover:border-white/20 hover:text-zinc-800 dark:hover:text-zinc-300 transition-colors">Agent</button>
|
|
<button onClick={() => fillDemo('admin')} className="text-[10px] font-bold uppercase tracking-wider bg-zinc-100 dark:bg-zinc-900/50 border border-zinc-200 dark:border-white/5 text-zinc-500 px-3 py-2 rounded-lg hover:border-zinc-300 dark:hover:border-white/20 hover:text-zinc-800 dark:hover:text-zinc-300 transition-colors">Admin</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</SpotlightCard>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default Login;
|