Add RSVP frontend: bottom sheet form, RsvpBar, and localStorage persistence

Introduces BottomSheet and RsvpBar components, integrates the RSVP
submission flow into EventDetailView, extends useEventStorage with
saveRsvp/getRsvp, and adds unit tests plus an E2E spec for the RSVP
workflow.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-08 12:47:53 +01:00
parent d9136481d8
commit be1c5062a2
11 changed files with 856 additions and 42 deletions

View File

@@ -116,4 +116,52 @@ describe('useEventStorage', () => {
expect(events).toHaveLength(1)
expect(events[0]!.title).toBe('New Title')
})
it('saves and retrieves RSVP for an existing event', () => {
const { saveCreatedEvent, saveRsvp, getRsvp } = useEventStorage()
saveCreatedEvent({
eventToken: 'abc-123',
title: 'Birthday',
dateTime: '2026-06-15T20:00:00+02:00',
expiryDate: '2026-07-15',
})
saveRsvp('abc-123', 'rsvp-token-1', 'Max', 'Birthday', '2026-06-15T20:00:00+02:00')
const rsvp = getRsvp('abc-123')
expect(rsvp).toEqual({ rsvpToken: 'rsvp-token-1', rsvpName: 'Max' })
})
it('saves RSVP for a new event (not previously stored)', () => {
const { saveRsvp, getRsvp, getStoredEvents } = useEventStorage()
saveRsvp('new-event', 'rsvp-token-2', 'Anna', 'Party', '2026-08-01T18:00:00+02:00')
const rsvp = getRsvp('new-event')
expect(rsvp).toEqual({ rsvpToken: 'rsvp-token-2', rsvpName: 'Anna' })
const events = getStoredEvents()
expect(events).toHaveLength(1)
expect(events[0]!.eventToken).toBe('new-event')
expect(events[0]!.title).toBe('Party')
})
it('returns undefined RSVP for event without RSVP', () => {
const { saveCreatedEvent, getRsvp } = useEventStorage()
saveCreatedEvent({
eventToken: 'abc-123',
title: 'Test',
dateTime: '2026-06-15T20:00:00+02:00',
expiryDate: '2026-07-15',
})
expect(getRsvp('abc-123')).toBeUndefined()
})
it('returns undefined RSVP for unknown event', () => {
const { getRsvp } = useEventStorage()
expect(getRsvp('unknown')).toBeUndefined()
})
})

View File

@@ -4,6 +4,8 @@ export interface StoredEvent {
title: string
dateTime: string
expiryDate: string
rsvpToken?: string
rsvpName?: string
}
const STORAGE_KEY = 'fete:events'
@@ -37,5 +39,25 @@ export function useEventStorage() {
return event?.organizerToken
}
return { saveCreatedEvent, getStoredEvents, getOrganizerToken }
function saveRsvp(eventToken: string, rsvpToken: string, rsvpName: string, title: string, dateTime: string): void {
const events = readEvents()
const existing = events.find((e) => e.eventToken === eventToken)
if (existing) {
existing.rsvpToken = rsvpToken
existing.rsvpName = rsvpName
} else {
events.push({ eventToken, title, dateTime, expiryDate: '', rsvpToken, rsvpName })
}
writeEvents(events)
}
function getRsvp(eventToken: string): { rsvpToken: string; rsvpName: string } | undefined {
const event = readEvents().find((e) => e.eventToken === eventToken)
if (event?.rsvpToken && event?.rsvpName) {
return { rsvpToken: event.rsvpToken, rsvpName: event.rsvpName }
}
return undefined
}
return { saveCreatedEvent, getStoredEvents, getOrganizerToken, saveRsvp, getRsvp }
}