feat(04-01): wire BrowserRouter + BottomTabBar + empty Lists surface

- App.tsx: BrowserRouter with /calendar, /lists, /lists/:listId routes; / redirects to /calendar
- BottomTabBar.tsx: fixed-bottom 56px tab bar with Calendar + Lists NavLinks, active accent
- AppNav.tsx: add Calendar/Lists NavLinks to desktop sidebar (≥768px)
- ListsIndex.tsx: full-height surface with isLoading/isError/empty state branches; "+ New List" FAB placeholder
- ListDetail.tsx: placeholder stub for /lists/:listId (Plan 04-04 fills in)
- listsStore.ts: Zustand UI-only store (activeTab, createListSheetOpen)
- listsClient.ts: fetchLists + List/ListItem types (initial; Plans 04-02/03 expand)
- Fix Wave-0 RED stubs: add vitest imports so stubs execute (todo) not error on import
- Fix CalendarShell.test.tsx: wrap renderWithClient in MemoryRouter (AppNav uses NavLink)
This commit is contained in:
Lucas Berger
2026-06-09 12:03:52 -04:00
parent 2f25b15949
commit c0088edf44
10 changed files with 563 additions and 3 deletions
+47
View File
@@ -0,0 +1,47 @@
/**
* 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).
*
* This file provides only the functions needed in Plan 04-01 (ListsIndex empty
* state). Plans 04-02 and 04-03 expand it with createList, deleteList, item CRUD.
*/
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
}
export interface ListItem {
id: number
listId: number
text: string
checked: boolean
rank: string
}
export interface ListsResponse {
lists: List[]
}
// ── Functions ──────────────────────────────────────────────────────────────
export async function fetchLists(): Promise<ListsResponse> {
return apiFetch('/lists').then((r) => r.json() as Promise<ListsResponse>)
}