feat(03-05): add write client calls and eventForm Zustand keys

- createEvent(payload): POST /api/events/create, credentials:include, returns {uid}
- updateEvent(uid, payload): PATCH /api/events/:uid/edit
- fetchWritableCalendars(): GET /api/events/writable-calendars, returns calendars array (D-03 server-authoritative)
- Exported interfaces: CreateEventPayload, CreateEventResponse, WritableCalendar, RecurrencePreset
- calendarStore: eventFormOpen (bool), eventFormMode ('create'|'edit'), eventFormUid (string|null)
- setEventForm(open, mode?, uid?) setter with correct defaults
This commit is contained in:
Lucas Berger
2026-06-05 18:26:00 -04:00
parent 6400ce693c
commit 6ffcdcbd6b
2 changed files with 130 additions and 0 deletions
+105
View File
@@ -103,3 +103,108 @@ export async function fetchEvents(
// Phase 1 legacy types (CalendarEvent, EventsResponse, fetchEventsLegacy) removed in Plan 05
// when the Phase 1 broker-proof component was retired.
// ── /api/events (write — Plan 03-05) ─────────────────────────────────────────
/**
* Recurrence presets supported by the EventForm.
* Maps 1:1 to the RRULE frequency values the API accepts.
*/
export type RecurrencePreset = 'none' | 'daily' | 'weekly' | 'monthly' | 'yearly'
/**
* Payload for creating or updating a calendar event.
* Mirrors the Zod schema on POST /api/events/create and PATCH /api/events/:uid/edit.
*/
export interface CreateEventPayload {
title: string
allDay: boolean
start: string // 'YYYY-MM-DD' for allDay; ISO 8601 for timed
end: string // same format as start
recurrence: RecurrencePreset
location?: string
description?: string
calendarUrl?: string // omit to use the member's default writable calendar (D-01)
}
/** Response from POST /api/events/create and PATCH /api/events/:uid/edit */
export interface CreateEventResponse {
uid: string
}
/**
* A writable calendar returned by GET /api/events/writable-calendars.
* The server is the authoritative source of the writable set (D-03).
* The client never derives writability — it reads this endpoint verbatim.
*/
export interface WritableCalendar {
url: string
displayName: string
color: string
isShared: boolean
}
/**
* Create a new calendar event.
*
* POSTs to /api/events/create and returns immediately with 202 + uid.
* The server enqueues the write to CalDAV asynchronously (D-05/D-12).
*/
export async function createEvent(payload: CreateEventPayload): Promise<CreateEventResponse> {
const res = await fetch('/api/events/create', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify(payload),
})
if (!res.ok) {
throw new Error(`POST /api/events/create failed: ${res.status}`)
}
return res.json() as Promise<CreateEventResponse>
}
/**
* Update an existing calendar event.
*
* PATCHes /api/events/:uid/edit with the updated payload.
* Returns 202 + uid; the write is enqueued asynchronously (D-05).
*/
export async function updateEvent(
uid: string,
payload: CreateEventPayload,
): Promise<CreateEventResponse> {
const res = await fetch(`/api/events/${uid}/edit`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify(payload),
})
if (!res.ok) {
throw new Error(`PATCH /api/events/${uid}/edit failed: ${res.status}`)
}
return res.json() as Promise<CreateEventResponse>
}
/**
* Fetch the authoritative list of writable calendars for the current member.
*
* The server owns the D-03 writable set (WHERE userId=currentUser.id OR isShared=1).
* The client NEVER derives writability — it reads this endpoint verbatim.
* Drives the calendar picker visibility (D-02: hidden when only 1 writable calendar).
*/
export async function fetchWritableCalendars(): Promise<WritableCalendar[]> {
const res = await fetch('/api/events/writable-calendars', {
credentials: 'include',
})
if (!res.ok) {
throw new Error(`GET /api/events/writable-calendars failed: ${res.status}`)
}
const body = (await res.json()) as { calendars: WritableCalendar[] }
return body.calendars
}