import React from 'react'; import { useLocation, useNavigate } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { Loader2, Lock, Mail } from 'lucide-react'; import { useMutation } from '@tanstack/react-query'; import { adminPath, ADMIN_DEFAULT_AFTER_LOGIN_PATH, ADMIN_EVENTS_PATH } from '../constants'; import { useAuth } from '../auth/context'; import { resolveReturnTarget } from '../lib/returnTo'; 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 MobileLoginPage() { 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: ['tenantAdminLoginMobile'], 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 (
Fotospiel

{t('login.panel_title', 'Team Login')}

{t('login.panel_copy', 'Melde dich an, um Events, Fotos und Aufgaben zu verwalten.')}

setLogin(e.target.value)} placeholder={t('login.username_or_email_placeholder', 'name@example.com')} className="w-full bg-transparent text-sm text-white placeholder:text-white/50 focus:outline-none" required />
setPassword(e.target.value)} placeholder={t('login.password_placeholder', '••••••••')} className="w-full bg-transparent text-sm text-white placeholder:text-white/50 focus:outline-none" required />
{error ? (
{error}
) : null}
{t('login.support', 'Fragen? Schreib uns an support@fotospiel.de oder antworte direkt auf deine Einladung.')}
); }