import { fail } from '@sveltejs/kit'; import { createApiClient } from '$lib/shared/api.server'; import type { Actions, PageServerLoad } from './$types'; import type { components } from '$lib/generated/api'; // The spec marks shareableUrl optional but the backend always populates it. // Keeping the required shape here avoids null-guarding throughout the page component. export interface InviteListItem { id: string; code: string; displayCode: string; label?: string; useCount: number; maxUses?: number; expiresAt?: string; revoked: boolean; status: string; createdAt: string; shareableUrl: string; } export type UserGroup = components['schemas']['UserGroup']; const VALID_STATUSES = ['ACTIVE', 'REVOKED', 'EXPIRED'] as const; type InviteStatus = (typeof VALID_STATUSES)[number]; export const load: PageServerLoad = async ({ url, fetch }) => { const rawStatus = url.searchParams.get('status'); const status: InviteStatus | 'active' = VALID_STATUSES.includes(rawStatus as InviteStatus) ? (rawStatus as InviteStatus) : 'active'; const api = createApiClient(fetch); const [invitesResult, groupsResult] = await Promise.all([ api.GET('/api/invites', { params: { query: { status } } }), api.GET('/api/groups') ]); let invites: InviteListItem[] = []; let loadError: string | null = null; if (!invitesResult.response.ok) { const code = (invitesResult.error as unknown as { code?: string })?.code; loadError = code ?? 'INTERNAL_ERROR'; } else { invites = (invitesResult.data ?? []) as unknown as InviteListItem[]; } let groups: UserGroup[] = []; let groupsLoadError: string | null = null; if (!groupsResult.response.ok) { const code = (groupsResult.error as unknown as { code?: string })?.code; groupsLoadError = code ?? 'INTERNAL_ERROR'; } else { const raw = groupsResult.data ?? []; groups = [...raw].sort((a, b) => a.name.localeCompare(b.name)); } return { invites, status, loadError, groups, groupsLoadError }; }; export const actions = { create: async ({ request, fetch }) => { const formData = await request.formData(); const label = (formData.get('label') as string) || undefined; const maxUsesRaw = formData.get('maxUses') as string; const maxUses = maxUsesRaw ? parseInt(maxUsesRaw, 10) : undefined; const prefillFirstName = (formData.get('prefillFirstName') as string) || undefined; const prefillLastName = (formData.get('prefillLastName') as string) || undefined; const prefillEmail = (formData.get('prefillEmail') as string) || undefined; const expiresAt = (formData.get('expiresAt') as string) || undefined; const groupIds = formData.getAll('groupIds') as string[]; const api = createApiClient(fetch); const result = await api.POST('/api/invites', { body: { label, maxUses, prefillFirstName, prefillLastName, prefillEmail, expiresAt, groupIds } }); if (!result.response.ok) { const code = (result.error as unknown as { code?: string })?.code; return fail(result.response.status, { createError: code ?? 'INTERNAL_ERROR' }); } return { created: result.data! as unknown as InviteListItem }; }, revoke: async ({ request, fetch }) => { const formData = await request.formData(); const id = formData.get('id') as string; const api = createApiClient(fetch); const result = await api.DELETE('/api/invites/{id}', { params: { path: { id } } }); if (!result.response.ok) { const code = (result.error as unknown as { code?: string })?.code; return fail(result.response.status, { revokeError: code ?? 'INTERNAL_ERROR' }); } return { revoked: id }; } } satisfies Actions;