import React, { useState } from 'react'; import { useLocation, useNavigate } from 'react-router-dom'; import { AlertCircle, Check, Loader2, Eye, EyeOff, CheckCircle2 } from 'lucide-react'; import { DOORMILE_WORDMARK_URL } from '@/assets/brand'; import { Checkbox } from '@/components/ds'; import { useAuth } from '@/lib/AuthContext'; import { readStoredUser } from '@/api/doormile'; import './Login.css'; /** * Console sign-in — `POST /admin/login`. * * The credential is checked by the Doormile API, so there is no client-side * password policy here beyond "you typed something": a length rule invented on * this side would reject a valid account whose password predates the rule, and * the server is the only thing that actually knows. * * The look is the Doormile Express console's sign-in design — lit brand panel * on the left, one elevated card on the right — so the two consoles open the * same way. It is expressed in a page-scoped stylesheet (Login.css) rather than * utility classes because the panel needs a layered gradient, an oversized arc * and a masked line pattern, none of which are Tailwind's to give. Nothing in * that stylesheet is reachable from any other page. */ const FEATURES = ['Real-time fleet visibility', 'AI-optimised dispatch routes', 'Tenant, pricing & invoice control']; export default function Login() { const navigate = useNavigate(); const location = useLocation(); const { login } = useAuth(); const from = location.state?.from; const returnTo = typeof from === 'string' && from.startsWith('/doormile') ? from : '/doormile/dispatch'; const [form, setForm] = useState({ email: '', password: '', remember: true }); const [showPassword, setShowPassword] = useState(false); const [errors, setErrors] = useState(/** @type {{ email?: string, password?: string }} */({})); const [authError, setAuthError] = useState(''); const [status, setStatus] = useState('idle'); const validate = () => { const next = {}; if (!form.email.trim()) next.email = 'Enter your work email'; else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(form.email.trim())) next.email = 'Enter a valid email address'; if (!form.password) next.password = 'Enter your password'; setErrors(next); return Object.keys(next).length === 0; }; const submit = async (event) => { event.preventDefault(); setAuthError(''); if (!validate()) return; setStatus('loading'); const result = await login(form.email.trim(), form.password); if (!result.success) { setStatus('idle'); setAuthError(result.message); return; } /* The data layer scopes some requests off the signed-in identity read straight from localStorage rather than through React context — the zone picker, for one, narrows the hub list to the tenant's own city. Those keys are written here, at the one moment the identity is known. */ const user = readStoredUser() || {}; localStorage.setItem('authname', user.email || user.name || form.email.trim()); localStorage.setItem('firstname', user.name || ''); localStorage.setItem('userid', user.id ?? ''); localStorage.setItem('roleid', user.role ?? ''); localStorage.setItem('tenantid', user.tenantid ?? ''); setStatus('success'); setTimeout(() => navigate(returnTo, { replace: true }), 320); }; const busy = status !== 'idle'; return (
{/* ---- Brand panel. The ONLY Doormile logo on this page. ---- */} {/* ---- Form panel ---- */}

Welcome back

Sign in to the Doormile Express console


{authError && (
)}
setForm({ ...form, email: e.target.value })} placeholder="you@company.com" /> {errors.email && (

{errors.email}

)}
setForm({ ...form, password: e.target.value })} placeholder="Enter your password" />
{errors.password && (

{errors.password}

)}
© {new Date().getFullYear()} Doormile Terms & Conditions Privacy Policy
); }