import React, { useState, useEffect } from 'react'; import { Box, Button, TextField, Container, Alert, Paper } from '@mui/material'; import { useNavigate } from 'react-router-dom'; import { useAuthStore } from '../../store/authStore'; import { accountService } from '../../api/accountService'; import type { Organization } from '../../api/accountService'; import { DbSelectionDialog } from './DbSelectionDialog'; import { useToastStore } from '../../store/toastStore'; export const Login: React.FC = () => { const [username, setUsername] = useState(''); const [password, setPassword] = useState(''); const [isInvalid, setIsInvalid] = useState(false); const [loading, setLoading] = useState(false); const [dialogOpen, setDialogOpen] = useState(false); const [dialogOrgs, setDialogOrgs] = useState([]); const [tempLoginResult, setTempLoginResult] = useState> | null>(null); const { login, isAuthenticated } = useAuthStore(); const navigate = useNavigate(); // If already logged in, redirect useEffect(() => { if (isAuthenticated) { navigate('/activity-feeds', { replace: true }); } }, [isAuthenticated, navigate]); const commitLogin = ( result: NonNullable, org: { org_id: number | string; db_name: string }, ) => { login({ access_token: result.access_token, refresh_token: result.refresh_token, user_id: result.user.user_id, username: result.user.username, email: result.user.email, role: result.user.role_name, org_id: String(org.org_id), db_name: org.db_name, }); navigate('/activity-feeds', { replace: true }); }; const handleLogin = async () => { setIsInvalid(false); setLoading(true); try { const result = await accountService.login(username, password); if (!result?.user) { setIsInvalid(true); useToastStore.getState().showToast('Invalid username or password.', 'error'); setLoading(false); return; } // Org entry is driven by where the user actually has work: supervisors // get the full list, everyone else only orgs with records assigned to // (or audited by) them. One org -> straight in; several -> pick; // none -> land in their profile org with an empty feed. let orgs: Organization[] = []; try { orgs = await accountService.getMyOrganizations(result.access_token); } catch { orgs = []; } if (orgs.length === 1) { commitLogin(result, orgs[0]); } else if (orgs.length === 0) { useToastStore.getState().showToast('No work assigned to you yet.', 'info'); commitLogin(result, { org_id: result.user.org_id, db_name: result.user.db_name }); } else { setTempLoginResult(result); setDialogOrgs(orgs); setDialogOpen(true); } } catch (error) { setIsInvalid(true); useToastStore.getState().showToast('Invalid username or password.', 'error'); } finally { setLoading(false); } }; const handleDbSelection = (selectedOrg?: Organization) => { setDialogOpen(false); if (selectedOrg && tempLoginResult) { commitLogin(tempLoginResult, selectedOrg); } setTempLoginResult(null); }; return ( {/* Logo */} Logo { e.currentTarget.src = "/images/SeekRightLogo.png"; }} /> setUsername(e.target.value)} onKeyDown={(e) => e.key === 'Enter' && handleLogin()} slotProps={{ input: { sx: { borderRadius: '50px', color: '#f8fafc', bgcolor: 'rgba(255, 255, 255, 0.02)', '& fieldset': { borderColor: 'rgba(255, 255, 255, 0.12)' }, '&:hover fieldset': { borderColor: 'rgba(255, 255, 255, 0.25)' }, '&.Mui-focused fieldset': { borderColor: '#3b82f6' } } } }} sx={{ mb: 1.5, '& .MuiInputBase-input': { padding: '12px 18px', fontSize: '0.9rem' } }} /> setPassword(e.target.value)} onKeyDown={(e) => e.key === 'Enter' && handleLogin()} slotProps={{ input: { sx: { borderRadius: '50px', color: '#f8fafc', bgcolor: 'rgba(255, 255, 255, 0.02)', '& fieldset': { borderColor: 'rgba(255, 255, 255, 0.12)' }, '&:hover fieldset': { borderColor: 'rgba(255, 255, 255, 0.25)' }, '&.Mui-focused fieldset': { borderColor: '#3b82f6' } } } }} sx={{ mt: 1.5, mb: 3, '& .MuiInputBase-input': { padding: '12px 18px', fontSize: '0.9rem' } }} /> {isInvalid && ( Invalid Credentials )} ); };