Enable users to see all their saved events on the home screen, sorted by date with upcoming events first. Key capabilities: - EventCard with title, relative time display, and organizer/attendee role badge - Sortable EventList with past-event visual distinction (faded style) - Empty state when no events are stored - Swipe-to-delete gesture with confirmation dialog - Floating action button for quick event creation - Rename router param :token → :eventToken across all views - useRelativeTime composable (Intl.RelativeTimeFormat) - useEventStorage: add validation, removeEvent(), reactive versioning - Full E2E and unit test coverage for all new components Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
89 lines
2.5 KiB
TypeScript
89 lines
2.5 KiB
TypeScript
export interface StoredEvent {
|
|
eventToken: string
|
|
organizerToken?: string
|
|
title: string
|
|
dateTime: string
|
|
expiryDate: 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, 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
|
|
}
|
|
|
|
function removeEvent(eventToken: string): void {
|
|
const events = readEvents().filter((e) => e.eventToken !== eventToken)
|
|
writeEvents(events)
|
|
}
|
|
|
|
return { saveCreatedEvent, getStoredEvents, getOrganizerToken, saveRsvp, getRsvp, removeEvent }
|
|
}
|