Files
finance/frontend/src/pages/AcceptInvitePage.tsx
T
2026-09-22 08:29:49 +05:00

114 lines
3.5 KiB
TypeScript

import { useMemo, useState } from 'react'
import { zodResolver } from '@hookform/resolvers/zod'
import { useForm } from 'react-hook-form'
import { useTranslation } from 'react-i18next'
import { Link, Navigate, useNavigate, useSearchParams } from 'react-router-dom'
import { z } from 'zod'
import { Button } from '@/components/ui/Button'
import { TextField } from '@/components/ui/fields'
import { AuthLayout } from '@/components/layout/AuthLayout'
import { useAcceptInvite, useMe } from '@/hooks/useAuth'
import { describeApiError } from '@/lib/apiErrors'
interface AcceptInviteValues {
fullName: string
password: string
confirm: string
}
export function AcceptInvitePage() {
const { t } = useTranslation()
const [searchParams] = useSearchParams()
const token = searchParams.get('token') ?? ''
const accept = useAcceptInvite()
const me = useMe()
const navigate = useNavigate()
const [apiError, setApiError] = useState<string | null>(null)
const schema = useMemo(
() =>
z
.object({
fullName: z.string().min(1, t('common.requiredField')),
password: z.string().min(12, t('auth.passwordHint')),
confirm: z.string().min(1, t('common.requiredField')),
})
.refine((values) => values.password === values.confirm, {
path: ['confirm'],
message: t('auth.passwordMismatch'),
}),
[t],
)
const {
register,
handleSubmit,
formState: { errors },
} = useForm<AcceptInviteValues>({
resolver: zodResolver(schema),
defaultValues: { fullName: '', password: '', confirm: '' },
})
if (me.isSuccess) return <Navigate to="/accounts" replace />
const submit = handleSubmit((values) => {
setApiError(null)
accept.mutate(
{ token, fullName: values.fullName, password: values.password },
{
onSuccess: () => navigate('/accounts', { replace: true }),
onError: (error) => setApiError(describeApiError(error, t)),
},
)
})
return (
<AuthLayout
title={t('auth.inviteTitle')}
subtitle={t('auth.inviteSubtitle')}
context={t('authShell.invitation')}
>
{apiError ? (
<div className="banner banner-danger" role="alert">
{apiError}
</div>
) : null}
{token ? (
<form onSubmit={submit} noValidate className="form-grid">
<TextField
label={t('auth.fullName')}
autoComplete="name"
error={errors.fullName?.message}
{...register('fullName')}
/>
<TextField
label={t('auth.password')}
type="password"
autoComplete="new-password"
hint={t('auth.passwordHint')}
error={errors.password?.message}
{...register('password')}
/>
<TextField
label={t('auth.passwordConfirm')}
type="password"
autoComplete="new-password"
error={errors.confirm?.message}
{...register('confirm')}
/>
<Button type="submit" disabled={accept.isPending}>
{accept.isPending ? t('auth.accepting') : t('auth.accept')}
</Button>
</form>
) : (
<div className="banner banner-warn" role="alert">
{t('auth.inviteTokenMissing')}
</div>
)}
<p className="auth-alt">
<Link to="/login">{t('auth.backToLogin')}</Link>
</p>
</AuthLayout>
)
}