feat(invites): group picker in new-invite form
- load() fetches /api/groups in parallel with /api/invites; returns sorted groups array and groupsLoadError for partial failures - create action forwards groupIds[] to POST /api/invites so invited users are placed in the selected groups on registration - +page.svelte: group checkboxes via UserGroupsSection inside the form; amber warning banner when groups could not be loaded - page.svelte.test.ts: groups checkboxes + warning banner tests - page.server.test.ts: parallel fetch, sorting, error fallback, groupIds in POST body Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,7 @@ import { fail } from '@sveltejs/kit';
|
||||
import { env } from '$env/dynamic/private';
|
||||
import { parseBackendError } from '$lib/shared/errors';
|
||||
import type { Actions, PageServerLoad } from './$types';
|
||||
import type { components } from '$lib/generated/api';
|
||||
|
||||
export interface InviteListItem {
|
||||
id: string;
|
||||
@@ -17,22 +18,37 @@ export interface InviteListItem {
|
||||
shareableUrl: string;
|
||||
}
|
||||
|
||||
export type UserGroup = components['schemas']['UserGroup'];
|
||||
|
||||
export const load: PageServerLoad = async ({ url, fetch }) => {
|
||||
const status = url.searchParams.get('status') ?? 'active';
|
||||
const apiUrl = env.API_INTERNAL_URL || 'http://localhost:8080';
|
||||
const res = await fetch(`${apiUrl}/api/invites?status=${encodeURIComponent(status)}`);
|
||||
|
||||
if (!res.ok) {
|
||||
const backendError = await parseBackendError(res);
|
||||
return {
|
||||
invites: [] as InviteListItem[],
|
||||
status,
|
||||
loadError: backendError?.code ?? 'INTERNAL_ERROR'
|
||||
};
|
||||
const [invitesRes, groupsRes] = await Promise.all([
|
||||
fetch(`${apiUrl}/api/invites?status=${encodeURIComponent(status)}`),
|
||||
fetch(`${apiUrl}/api/groups`)
|
||||
]);
|
||||
|
||||
let invites: InviteListItem[] = [];
|
||||
let loadError: string | null = null;
|
||||
if (!invitesRes.ok) {
|
||||
const backendError = await parseBackendError(invitesRes);
|
||||
loadError = backendError?.code ?? 'INTERNAL_ERROR';
|
||||
} else {
|
||||
invites = await invitesRes.json();
|
||||
}
|
||||
|
||||
const invites: InviteListItem[] = await res.json();
|
||||
return { invites, status, loadError: null };
|
||||
let groups: UserGroup[] = [];
|
||||
let groupsLoadError: string | null = null;
|
||||
if (!groupsRes.ok) {
|
||||
const backendError = await parseBackendError(groupsRes);
|
||||
groupsLoadError = backendError?.code ?? 'INTERNAL_ERROR';
|
||||
} else {
|
||||
const raw: UserGroup[] = await groupsRes.json();
|
||||
groups = [...raw].sort((a, b) => a.name.localeCompare(b.name));
|
||||
}
|
||||
|
||||
return { invites, status, loadError, groups, groupsLoadError };
|
||||
};
|
||||
|
||||
export const actions = {
|
||||
@@ -45,6 +61,7 @@ export const actions = {
|
||||
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 apiUrl = env.API_INTERNAL_URL || 'http://localhost:8080';
|
||||
const res = await fetch(`${apiUrl}/api/invites`, {
|
||||
@@ -56,7 +73,8 @@ export const actions = {
|
||||
prefillFirstName,
|
||||
prefillLastName,
|
||||
prefillEmail,
|
||||
expiresAt
|
||||
expiresAt,
|
||||
groupIds
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user