Mechanical reformat — no logic changes. 398 files changed, 19125 insertions(+), 16457 deletions(-). Prettier 3.8.4 with .prettierrc (singleQuote:true, semi:true, tabWidth:2, trailingComma:all, printWidth:100). Isolated per D-13-08 for reviewability.
131 lines
4.1 KiB
TypeScript
131 lines
4.1 KiB
TypeScript
/**
|
|
* Typed API client for the FamilySync lists API.
|
|
*
|
|
* credentials: 'include' is required so the OIDC session cookie is sent with
|
|
* every request (same pattern as client.ts).
|
|
*
|
|
* Exports: fetchLists, createList, patchList, deleteList,
|
|
* fetchListItems, addItem, patchListItem, deleteItem
|
|
* Types: List, ListItem, ListsResponse, ListItemsResponse
|
|
*/
|
|
|
|
const BASE = '/api';
|
|
|
|
async function apiFetch(path: string, init?: RequestInit): Promise<Response> {
|
|
const res = await fetch(`${BASE}${path}`, {
|
|
credentials: 'include',
|
|
...init,
|
|
});
|
|
if (!res.ok) throw new Error(`${init?.method ?? 'GET'} ${path} failed: ${res.status}`);
|
|
return res;
|
|
}
|
|
|
|
// ── Types ──────────────────────────────────────────────────────────────────
|
|
|
|
export interface List {
|
|
id: number;
|
|
name: string;
|
|
isShared: boolean;
|
|
ownerId: number;
|
|
activeCount: number;
|
|
doneCount: number;
|
|
createdAt?: string;
|
|
updatedAt?: string;
|
|
}
|
|
|
|
export interface ListItem {
|
|
id: number;
|
|
listId: number;
|
|
text: string;
|
|
checked: boolean;
|
|
rank: string;
|
|
createdAt?: string;
|
|
updatedAt?: string;
|
|
}
|
|
|
|
export interface ListsResponse {
|
|
lists: List[];
|
|
}
|
|
|
|
export interface ListItemsResponse {
|
|
items: ListItem[];
|
|
}
|
|
|
|
// ── Functions ──────────────────────────────────────────────────────────────
|
|
|
|
export async function fetchLists(): Promise<ListsResponse> {
|
|
return apiFetch('/lists').then((r) => r.json() as Promise<ListsResponse>);
|
|
}
|
|
|
|
export async function createList(payload: { name: string; isShared: boolean }): Promise<List> {
|
|
return apiFetch('/lists', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(payload),
|
|
}).then((r) => r.json() as Promise<List>);
|
|
}
|
|
|
|
export async function patchList(
|
|
id: number,
|
|
patch: { name?: string; isShared?: boolean },
|
|
): Promise<List> {
|
|
return apiFetch(`/lists/${id}`, {
|
|
method: 'PATCH',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(patch),
|
|
}).then((r) => r.json() as Promise<List>);
|
|
}
|
|
|
|
export async function deleteList(id: number): Promise<{ id: number }> {
|
|
return apiFetch(`/lists/${id}`, {
|
|
method: 'DELETE',
|
|
}).then((r) => r.json() as Promise<{ id: number }>);
|
|
}
|
|
|
|
// ── Item functions (LIST-02) ────────────────────────────────────────────────
|
|
|
|
/**
|
|
* Fetch all items for a list, ordered by rank ASC.
|
|
* Returns active and completed items; the UI splits them into sections.
|
|
*/
|
|
export async function fetchListItems(listId: number): Promise<ListItemsResponse> {
|
|
return apiFetch(`/lists/${listId}/items`).then((r) => r.json() as Promise<ListItemsResponse>);
|
|
}
|
|
|
|
/**
|
|
* Add an item to a list. Server assigns the fractional rank (D-13).
|
|
*/
|
|
export async function addItem(listId: number, payload: { text: string }): Promise<ListItem> {
|
|
return apiFetch(`/lists/${listId}/items`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(payload),
|
|
}).then((r) => r.json() as Promise<ListItem>);
|
|
}
|
|
|
|
/**
|
|
* Per-field PATCH for a list item (D-08).
|
|
* Exactly one of: checked, text, or position.
|
|
* Enforced server-side by zod refine; callers must pass exactly one field.
|
|
*/
|
|
export async function patchListItem(
|
|
itemId: number,
|
|
patch: { checked: boolean } | { text: string } | { position: string },
|
|
): Promise<ListItem> {
|
|
return apiFetch(`/list-items/${itemId}`, {
|
|
method: 'PATCH',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(patch),
|
|
}).then((r) => r.json() as Promise<ListItem>);
|
|
}
|
|
|
|
/**
|
|
* Delete an item instantly — no confirmation (D-06).
|
|
* Delete-wins semantics (D-09): no rollback in the client after success.
|
|
*/
|
|
export async function deleteItem(itemId: number): Promise<{ id: number }> {
|
|
return apiFetch(`/list-items/${itemId}`, {
|
|
method: 'DELETE',
|
|
}).then((r) => r.json() as Promise<{ id: number }>);
|
|
}
|