Files
fete/frontend/src/composables/useEventStorage.ts
nitrix c450849e4d
All checks were successful
CI / backend-test (push) Successful in 1m0s
CI / frontend-test (push) Successful in 27s
CI / frontend-e2e (push) Successful in 1m30s
CI / build-and-publish (push) Has been skipped
Implement watch-event feature (017) with bookmark in RsvpBar
Add client-side watch/bookmark functionality: users can save events to
localStorage without RSVPing via a bookmark button next to the "I'm attending"
CTA. Watched events appear in the event list with a "Watching" label.
Bookmark is only visible for visitors (not attendees or organizers).

Includes spec, plan, research, tasks, unit tests, and E2E tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 22:20:57 +01:00

112 lines
3.2 KiB
TypeScript

export interface StoredEvent {
eventToken: string
organizerToken?: string
title: string
dateTime: string
rsvpToken?: string
rsvpName?: string
}
import { ref } from 'vue'
const STORAGE_KEY = 'fete:events'
const version = ref(0)
export function isValidStoredEvent(e: unknown): e is StoredEvent {
if (typeof e !== 'object' || e === null) return false
const obj = e as Record<string, unknown>
return (
typeof obj.eventToken === 'string' &&
obj.eventToken.length > 0 &&
typeof obj.title === 'string' &&
obj.title.length > 0 &&
typeof obj.dateTime === 'string' &&
obj.dateTime.length > 0 &&
!isNaN(new Date(obj.dateTime).getTime())
)
}
function readEvents(): StoredEvent[] {
try {
const raw = localStorage.getItem(STORAGE_KEY)
return raw ? (JSON.parse(raw) as StoredEvent[]) : []
} catch {
return []
}
}
function writeEvents(events: StoredEvent[]): void {
localStorage.setItem(STORAGE_KEY, JSON.stringify(events))
version.value++
}
export function useEventStorage() {
function saveCreatedEvent(event: StoredEvent): void {
const events = readEvents().filter((e) => e.eventToken !== event.eventToken)
events.push(event)
writeEvents(events)
}
function getStoredEvents(): StoredEvent[] {
void version.value
return readEvents()
}
function getOrganizerToken(eventToken: string): string | undefined {
const event = readEvents().find((e) => e.eventToken === eventToken)
return event?.organizerToken
}
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, 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
}
function removeRsvp(eventToken: string): void {
const events = readEvents()
const event = events.find((e) => e.eventToken === eventToken)
if (event) {
delete event.rsvpToken
delete event.rsvpName
writeEvents(events)
}
}
function saveWatch(eventToken: string, title: string, dateTime: string): void {
const events = readEvents()
const existing = events.find((e) => e.eventToken === eventToken)
if (!existing) {
events.push({ eventToken, title, dateTime })
writeEvents(events)
}
}
function isStored(eventToken: string): boolean {
void version.value
return readEvents().some((e) => e.eventToken === eventToken)
}
function removeEvent(eventToken: string): void {
const events = readEvents().filter((e) => e.eventToken !== eventToken)
writeEvents(events)
}
return { saveCreatedEvent, getStoredEvents, getOrganizerToken, saveRsvp, getRsvp, removeRsvp, saveWatch, isStored, removeEvent }
}