import type { InvitationListView } from '@dorfteich/shared'; import { useQuery, useQueryClient } from '@tanstack/react-query'; import { useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Field, FormError } from '../components/forms'; import { apiDelete, apiGet, apiPost } from '../lib/api'; const INVITATIONS_QUERY_KEY = ['users', 'me', 'invitations'] as const; /** * Peer invitations in the user settings (issue #332): invite an e-mail * address, see your invitations with their status, revoke open ones. The * quota line shows how many of the instance-wide per-user allowance are * in use; with a quota of 0 the section explains that inviting is off. */ export function InvitationsSection(): React.JSX.Element { const { t } = useTranslation('invitations'); const queryClient = useQueryClient(); const [email, setEmail] = useState(''); const [sent, setSent] = useState(false); const [error, setError] = useState(null); const [busy, setBusy] = useState(false); const list = useQuery({ queryKey: INVITATIONS_QUERY_KEY, queryFn: () => apiGet('/invitations'), }); const submit = async (event: React.FormEvent): Promise => { event.preventDefault(); setError(null); setSent(false); setBusy(true); try { await apiPost('/invitations', { email }); setEmail(''); setSent(true); await queryClient.invalidateQueries({ queryKey: INVITATIONS_QUERY_KEY }); } catch (err) { setError(err); } finally { setBusy(false); } }; const revoke = async (id: string): Promise => { setError(null); await apiDelete(`/invitations/${id}`); await queryClient.invalidateQueries({ queryKey: INVITATIONS_QUERY_KEY }); }; const data = list.data; const disabled = data?.maxOpen === 0; return (

{t('section.title')}

{disabled ? (

{t('section.disabled')}

) : ( <>

{t('section.intro')}

{data && (

{t('section.quota', { open: data.open, max: data.maxOpen })}

)}
void submit(e)} noValidate className="invitations__form"> {/* Scoped status region: a bare getByRole('status') must stay unambiguous for other specs (lesson from #304/legal). */}

{sent ? t('form.sent') : ''}

setEmail(e.target.value)} /> {data && data.invitations.length === 0 &&

{t('section.empty')}

} {data && data.invitations.length > 0 && (
{data.invitations.map((invitation) => ( ))}
{t('columns.email')} {t('columns.status')} {t('columns.created')} {t('columns.expires')} {t('columns.actions')}
{invitation.email} {t(`status.${invitation.status}`)} {new Date(invitation.createdAt).toLocaleDateString()} {new Date(invitation.expiresAt).toLocaleDateString()} {invitation.status === 'pending' && ( )}
)} )}
); }