import { useState, useEffect } from 'react'; import { useSubmit, useNavigation, useActionData, useNavigate, redirect } from 'react-router'; import type { Route } from './+types/login'; import { pb } from '~/lib/pocketbase'; import { Lock, Mail, Eye, EyeOff, Loader2, ArrowRight, AlertCircle } from 'lucide-react'; import { Input } from '~/components/ui/input'; import { Button } from '~/components/ui/button'; import { Label } from '~/components/ui/label'; import { toast } from 'sonner'; import { LogoMark } from '~/components/logo-mark'; import { AuthBrandPanel } from '~/components/auth/auth-brand-panel'; export async function clientLoader() { if (pb.authStore.isValid) { return redirect('/'); } return null; } export async function clientAction({ request }: Route.ClientActionArgs) { const formData = await request.formData(); const email = formData.get('email') as string; const password = formData.get('password') as string; if (!email || !password) { return { error: 'Please enter both email and password' }; } try { await pb.collection('users').authWithPassword(email, password); return redirect('/'); } catch (err: any) { return { error: err?.message || 'Invalid email or password' }; } } const fieldLabel = 'normal-case tracking-normal text-sm font-medium text-neutral-300'; const fieldIcon = 'pointer-events-none absolute left-4 top-1/2 -translate-y-1/2 text-neutral-500 transition-colors group-focus-within:text-orange-500'; const fieldInput = 'h-12 rounded-xl border-neutral-800 bg-[#141414] pl-11 text-[15px] focus-visible:border-orange-500/70 focus-visible:ring-4 focus-visible:ring-orange-500/10'; export default function Login() { const navigate = useNavigate(); const submit = useSubmit(); const navigation = useNavigation(); const actionData = useActionData(); const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [showPassword, setShowPassword] = useState(false); const [capsLock, setCapsLock] = useState(false); const [localError, setLocalError] = useState(''); const isLoading = navigation.state !== 'idle'; const errorMessage = localError || actionData?.error; // Already-authenticated redirect fallback useEffect(() => { if (pb.authStore.isValid) { navigate('/', { replace: true }); } }, [navigate]); // Handle authentication feedback useEffect(() => { if (actionData) { if (actionData.error) { setLocalError(actionData.error); toast.error(actionData.error); } else { toast.success('Logged in successfully'); } } }, [actionData]); const handleLogin = (e: React.FormEvent) => { e.preventDefault(); if (!email || !password) { setLocalError('Please enter both email and password'); return; } setLocalError(''); const formData = new FormData(); formData.append('email', email); formData.append('password', password); submit(formData, { method: 'post' }); }; const handleForgotPassword = () => { toast.info('Please contact your administrator to reset your password.'); }; return (
{/* Subtle dot texture */}

Welcome back

Sign in to your account to continue.

{errorMessage && (
{errorMessage}
)}
{ setEmail(e.target.value); if (localError) setLocalError(''); }} />
{ setPassword(e.target.value); if (localError) setLocalError(''); }} onKeyUp={(e) => setCapsLock(e.getModifierState('CapsLock'))} onBlur={() => setCapsLock(false)} />
{capsLock && (

Caps Lock is on

)}

Access is restricted to authorized team members.

); }