From 8aeacc860728d588e01054092ed2789922042261 Mon Sep 17 00:00:00 2001 From: Lucas Berger Date: Fri, 5 Jun 2026 18:35:51 -0400 Subject: [PATCH] feat(03-06): add deleteEvent, fetchSyncStatus client calls and delete/sync Zustand keys - deleteEvent(uid): DELETE /api/events/:uid with credentials:include, throws on !ok - fetchSyncStatus(uid): GET /api/events/sync-status?uid= returning SyncStatus - Export SyncStatus and SyncStatusValue types - Zustand: deleteDialogOpen/deleteDialogUid/lastSyncedUid keys + setDeleteDialog/setLastSyncedUid setters --- apps/pwa/src/api/client.ts | 54 +++++++++++++++++++++++++++++ apps/pwa/src/store/calendarStore.ts | 31 +++++++++++++++++ 2 files changed, 85 insertions(+) diff --git a/apps/pwa/src/api/client.ts b/apps/pwa/src/api/client.ts index e997fda..ea2d8ca 100644 --- a/apps/pwa/src/api/client.ts +++ b/apps/pwa/src/api/client.ts @@ -189,6 +189,60 @@ export async function updateEvent( return res.json() as Promise } +/** + * Delete a calendar event. + * + * DELETEs /api/events/:uid; the API enqueues the delete to the outbox (D-05). + * Returns 202 Accepted (async). Throws on any non-ok response. + */ +export async function deleteEvent(uid: string): Promise { + const res = await fetch(`/api/events/${uid}`, { + method: 'DELETE', + credentials: 'include', + }) + + if (!res.ok) { + throw new Error(`DELETE /api/events/${uid} failed: ${res.status}`) + } +} + +// ── /api/events/sync-status (Plan 03-06) ───────────────────────────────────── + +/** + * Status values for an outbox write operation. + * Mirrors the calendarOutbox.status enum on the server. + */ +export type SyncStatusValue = 'pending' | 'done' | 'failed' | 'dead' + +/** + * Response from GET /api/events/sync-status?uid= + * The server returns the current outbox status for the given UID + member. + */ +export interface SyncStatus { + uid: string + status: SyncStatusValue + /** Present on failed status — may contain '412' prefix for conflict detection. */ + error?: string +} + +/** + * Poll the sync-status for a specific event UID. + * + * Used by SyncStateToast to track pending → done | failed | dead transitions. + * The server filters by the current member so no cross-member leakage (T-03-19). + */ +export async function fetchSyncStatus(uid: string): Promise { + const res = await fetch(`/api/events/sync-status?uid=${uid}`, { + credentials: 'include', + }) + + if (!res.ok) { + throw new Error(`GET /api/events/sync-status failed: ${res.status}`) + } + + return res.json() as Promise +} + /** * Fetch the authoritative list of writable calendars for the current member. * diff --git a/apps/pwa/src/store/calendarStore.ts b/apps/pwa/src/store/calendarStore.ts index 0ab50d0..38a319f 100644 --- a/apps/pwa/src/store/calendarStore.ts +++ b/apps/pwa/src/store/calendarStore.ts @@ -43,6 +43,13 @@ export interface CalendarStore { eventFormMode: 'create' | 'edit' eventFormUid: string | null + // ── Delete dialog + sync-state UI state (Plan 03-06) ───────────────────── + // Drives DeleteConfirmationDialog visibility and SyncStateToast polling. + deleteDialogOpen: boolean + deleteDialogUid: string | null + /** UID of the most recently enqueued write. SyncStateToast polls sync-status for this. */ + lastSyncedUid: string | null + setSelectedView: (view: string) => void setSelectedDate: (date: string) => void setOpenEventId: (id: string | null) => void @@ -56,6 +63,20 @@ export interface CalendarStore { * @param uid UID of the event to pre-populate in edit mode (null otherwise) */ setEventForm: (open: boolean, mode?: 'create' | 'edit', uid?: string | null) => void + + /** + * Open or close the DeleteConfirmationDialog. + * + * @param open true to open, false to close + * @param uid UID of the event to confirm-delete (null when closing) + */ + setDeleteDialog: (open: boolean, uid?: string | null) => void + + /** + * Set the UID of the most recently enqueued write, driving SyncStateToast polling. + * Pass null to dismiss the toast. + */ + setLastSyncedUid: (uid: string | null) => void } // ── Helpers ──────────────────────────────────────────────────────────────── @@ -115,6 +136,11 @@ export const useCalendarStore = create((set) => ({ eventFormMode: 'create', eventFormUid: null, + // Delete dialog + sync-state UI state — closed/null by default + deleteDialogOpen: false, + deleteDialogUid: null, + lastSyncedUid: null, + setSelectedView: (view: string) => { set({ selectedView: view }) // Persist to localStorage keyed by breakpoint group @@ -135,4 +161,9 @@ export const useCalendarStore = create((set) => ({ setEventForm: (open: boolean, mode: 'create' | 'edit' = 'create', uid: string | null = null) => set({ eventFormOpen: open, eventFormMode: mode, eventFormUid: uid }), + + setDeleteDialog: (open: boolean, uid: string | null = null) => + set({ deleteDialogOpen: open, deleteDialogUid: uid }), + + setLastSyncedUid: (uid: string | null) => set({ lastSyncedUid: uid }), }))