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
This commit is contained in:
@@ -189,6 +189,60 @@ export async function updateEvent(
|
|||||||
return res.json() as Promise<CreateEventResponse>
|
return res.json() as Promise<CreateEventResponse>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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<void> {
|
||||||
|
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<SyncStatus> {
|
||||||
|
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<SyncStatus>
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch the authoritative list of writable calendars for the current member.
|
* Fetch the authoritative list of writable calendars for the current member.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -43,6 +43,13 @@ export interface CalendarStore {
|
|||||||
eventFormMode: 'create' | 'edit'
|
eventFormMode: 'create' | 'edit'
|
||||||
eventFormUid: string | null
|
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
|
setSelectedView: (view: string) => void
|
||||||
setSelectedDate: (date: string) => void
|
setSelectedDate: (date: string) => void
|
||||||
setOpenEventId: (id: string | null) => 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)
|
* @param uid UID of the event to pre-populate in edit mode (null otherwise)
|
||||||
*/
|
*/
|
||||||
setEventForm: (open: boolean, mode?: 'create' | 'edit', uid?: string | null) => void
|
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 ────────────────────────────────────────────────────────────────
|
// ── Helpers ────────────────────────────────────────────────────────────────
|
||||||
@@ -115,6 +136,11 @@ export const useCalendarStore = create<CalendarStore>((set) => ({
|
|||||||
eventFormMode: 'create',
|
eventFormMode: 'create',
|
||||||
eventFormUid: null,
|
eventFormUid: null,
|
||||||
|
|
||||||
|
// Delete dialog + sync-state UI state — closed/null by default
|
||||||
|
deleteDialogOpen: false,
|
||||||
|
deleteDialogUid: null,
|
||||||
|
lastSyncedUid: null,
|
||||||
|
|
||||||
setSelectedView: (view: string) => {
|
setSelectedView: (view: string) => {
|
||||||
set({ selectedView: view })
|
set({ selectedView: view })
|
||||||
// Persist to localStorage keyed by breakpoint group
|
// Persist to localStorage keyed by breakpoint group
|
||||||
@@ -135,4 +161,9 @@ export const useCalendarStore = create<CalendarStore>((set) => ({
|
|||||||
|
|
||||||
setEventForm: (open: boolean, mode: 'create' | 'edit' = 'create', uid: string | null = null) =>
|
setEventForm: (open: boolean, mode: 'create' | 'edit' = 'create', uid: string | null = null) =>
|
||||||
set({ eventFormOpen: open, eventFormMode: mode, eventFormUid: uid }),
|
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 }),
|
||||||
}))
|
}))
|
||||||
|
|||||||
Reference in New Issue
Block a user