import { useState } from 'react'; import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { useNavigate, useSearch } from '@tanstack/react-router'; import { useTranslation } from 'react-i18next'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { invitationCompleteFormSchema, type InvitationCompleteFormData } from '@/lib/schemas/auth'; import { FormErrorBanner } from '@/components/ui/FormErrorBanner'; import { FieldError } from '@/components/ui/FieldError'; import { useValidateInvitation, useCompleteInvitation } from '@/api/useInvitation'; import { ProblemDetailsError, NetworkError } from '@/lib/api-client'; interface FormError { message: string; } type LoadingState = 'loading' | 'ready' | 'error' | 'submitting' | 'success'; export function InviteCompletePage() { const { t } = useTranslation(); const navigate = useNavigate(); const search = useSearch({ strict: false }) as { token?: string }; const [serverError, setServerError] = useState(null); const [loadingState, setLoadingState] = useState('loading'); const token = search.token || null; const validationQuery = useValidateInvitation(token); const completeMutation = useCompleteInvitation(); // Handle validation query state changes if (!token) { if (loadingState === 'loading') { setLoadingState('error'); } } else if (validationQuery.isPending && loadingState !== 'submitting') { // Still loading } else if (validationQuery.error || (validationQuery.data && !validationQuery.data.valid)) { if (loadingState === 'loading') { setLoadingState('error'); } } else if (validationQuery.data?.valid && loadingState === 'loading') { setLoadingState('ready'); } const { register, handleSubmit, formState: { errors, isSubmitting } } = useForm({ resolver: zodResolver(invitationCompleteFormSchema), mode: 'onBlur', defaultValues: { name: '', password: '', confirmPassword: '' } }); const onSubmit = handleSubmit(async (values) => { setServerError(null); setLoadingState('submitting'); try { if (!token) { setServerError({ message: t('errors.invalidInvitationToken') }); setLoadingState('error'); return; } const { confirmPassword: _, ...fields } = values; await completeMutation.mutateAsync({ token, ...fields }); setLoadingState('success'); setTimeout(() => { navigate({ to: '/login' }); }, 2000); } catch (err) { setLoadingState('ready'); if (err instanceof ProblemDetailsError) { setServerError({ message: err.problem.detail || err.message || t('errors.generic') }); } else if (err instanceof NetworkError) { setServerError({ message: t('errors.network') }); } else { setServerError({ message: t('errors.generic') }); } } }); if (loadingState === 'loading') { return (

{t('inviteComplete.loadingMessage')}

); } if (loadingState === 'error') { return (
{t('errors.setupRequired')}

{t('inviteComplete.invalidTokenMessage')}

); } if (loadingState === 'success') { return (
✓ {t('inviteComplete.successMessage')}

{t('login.subtitle')}

); } const email = validationQuery.data?.email || ''; return (
{t('inviteComplete.title')} {t('login.subtitle')}
setServerError(null)} />
{errors.name && }
{errors.password && }
{errors.confirmPassword && }
); }