import React from 'react'; import { useTranslation } from 'react-i18next'; import { useLocation, useNavigate } from 'react-router-dom'; import { Loader2 } from 'lucide-react'; import { Alert, AlertDescription } from '@/components/ui/alert'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { useAuth } from '../auth/context'; import { ADMIN_DEFAULT_AFTER_LOGIN_PATH, ADMIN_EVENTS_PATH } from '../constants'; import { resolveReturnTarget } from '../lib/returnTo'; import { useMutation } from '@tanstack/react-query'; type LoginResponse = { token: string; token_type: string; abilities: string[]; }; async function performLogin(payload: { login: string; password: string; return_to?: string | null }): Promise { const response = await fetch('/api/v1/tenant-auth/login', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', }, body: JSON.stringify({ ...payload, remember: true, }), }); if (response.status === 422) { const data = await response.json(); const errors = data.errors ?? {}; const flattened = Object.values(errors).flat(); throw new Error(flattened.join(' ') || 'Validation failed'); } if (!response.ok) { throw new Error('Login failed.'); } return (await response.json()) as LoginResponse; } export default function LoginPage(): React.ReactElement { const { status, applyToken, abilities } = useAuth(); const { t } = useTranslation('auth'); const location = useLocation(); const navigate = useNavigate(); const searchParams = React.useMemo(() => new URLSearchParams(location.search), [location.search]); const rawReturnTo = searchParams.get('return_to'); const computeDefaultAfterLogin = React.useCallback( (abilityList?: string[]) => { const source = abilityList ?? abilities; return source.includes('tenant-admin') ? ADMIN_DEFAULT_AFTER_LOGIN_PATH : ADMIN_EVENTS_PATH; }, [abilities], ); const fallbackTarget = computeDefaultAfterLogin(); const { finalTarget } = React.useMemo( () => resolveReturnTarget(rawReturnTo, fallbackTarget), [rawReturnTo, fallbackTarget] ); React.useEffect(() => { if (status === 'authenticated') { navigate(finalTarget, { replace: true }); } }, [finalTarget, navigate, status]); const [login, setLogin] = React.useState(''); const [password, setPassword] = React.useState(''); const [error, setError] = React.useState(null); const mutation = useMutation({ mutationKey: ['tenantAdminLogin'], mutationFn: performLogin, onError: (err: unknown) => { const message = err instanceof Error ? err.message : String(err); setError(message); }, onSuccess: async (data) => { setError(null); await applyToken(data.token, data.abilities ?? []); const postLoginFallback = computeDefaultAfterLogin(data.abilities ?? []); const { finalTarget: successTarget } = resolveReturnTarget(rawReturnTo, postLoginFallback); navigate(successTarget, { replace: true }); }, }); const isSubmitting = (mutation as { isPending?: boolean; isLoading?: boolean }).isPending ?? (mutation as { isPending?: boolean; isLoading?: boolean }).isLoading ?? false; const handleSubmit = (event: React.FormEvent) => { event.preventDefault(); setError(null); mutation.mutate({ login, password, return_to: rawReturnTo, }); }; return (
{t('login.brand_alt',

{t('login.panel_title', t('login.title', 'Team Login für Fotospiel'))}

{t( 'login.panel_copy', 'Melde dich an, um Events zu planen, Fotos zu moderieren und Aufgaben im Fotospiel-Team zu koordinieren.' )}

setLogin(event.target.value)} className="h-12 rounded-xl border-slate-200/60 bg-white/90 px-4 text-base shadow-inner shadow-slate-200/40 focus-visible:ring-[#ff8bb1]/40 dark:border-slate-800/70 dark:bg-slate-900/70 dark:text-slate-100" placeholder={t('login.username_or_email_placeholder', 'name@example.com')} />
setPassword(event.target.value)} className="h-12 rounded-xl border-slate-200/60 bg-white/90 px-4 text-base shadow-inner shadow-slate-200/40 focus-visible:ring-[#ff8bb1]/40 dark:border-slate-800/70 dark:bg-slate-900/70 dark:text-slate-100" placeholder={t('login.password_placeholder', '••••••••')} />

{t('login.remember_hint', 'Wir halten dich automatisch angemeldet, solange du dieses Gerät nutzt.')}

{error ? ( {error} ) : null}
); }