Implement event creation frontend (EventCreateView)

Form with client-side validation, server error handling, aria-invalid/
aria-describedby for a11y, localStorage persistence via useEventStorage
composable. Routes for /create and /events/:token.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-05 10:56:59 +01:00
parent f3d4b5fa17
commit 84feeb9997
6 changed files with 719 additions and 12 deletions

View File

@@ -0,0 +1,119 @@
import { describe, it, expect, beforeEach } from 'vitest'
import { useEventStorage } from '../useEventStorage'
// jsdom provides a working localStorage in the window object
// but Node's --localstorage-file warning can be ignored
function clearStorage() {
try {
window.localStorage.setItem('fete:events', '[]')
} catch {
// Provide a minimal mock if localStorage is broken
const store: Record<string, string> = {}
Object.defineProperty(globalThis, 'localStorage', {
value: {
getItem: (key: string) => store[key] ?? null,
setItem: (key: string, val: string) => {
store[key] = val
},
removeItem: (key: string) => {
delete store[key]
},
},
writable: true,
configurable: true,
})
}
}
describe('useEventStorage', () => {
beforeEach(() => {
clearStorage()
})
it('returns empty array when no events stored', () => {
const { getStoredEvents } = useEventStorage()
expect(getStoredEvents()).toEqual([])
})
it('saves and retrieves a created event', () => {
const { saveCreatedEvent, getStoredEvents } = useEventStorage()
saveCreatedEvent({
eventToken: 'abc-123',
organizerToken: 'org-456',
title: 'Birthday',
dateTime: '2026-06-15T20:00:00+02:00',
expiryDate: '2026-07-15',
})
const events = getStoredEvents()
expect(events).toHaveLength(1)
expect(events[0]!.eventToken).toBe('abc-123')
expect(events[0]!.organizerToken).toBe('org-456')
expect(events[0]!.title).toBe('Birthday')
})
it('returns organizer token for known event', () => {
const { saveCreatedEvent, getOrganizerToken } = useEventStorage()
saveCreatedEvent({
eventToken: 'abc-123',
organizerToken: 'org-456',
title: 'Test',
dateTime: '2026-06-15T20:00:00+02:00',
expiryDate: '2026-07-15',
})
expect(getOrganizerToken('abc-123')).toBe('org-456')
})
it('returns undefined organizer token for unknown event', () => {
const { getOrganizerToken } = useEventStorage()
expect(getOrganizerToken('unknown')).toBeUndefined()
})
it('stores multiple events independently', () => {
const { saveCreatedEvent, getStoredEvents } = useEventStorage()
saveCreatedEvent({
eventToken: 'event-1',
title: 'First',
dateTime: '2026-06-15T20:00:00+02:00',
expiryDate: '2026-07-15',
})
saveCreatedEvent({
eventToken: 'event-2',
title: 'Second',
dateTime: '2026-07-15T20:00:00+02:00',
expiryDate: '2026-08-15',
})
const events = getStoredEvents()
expect(events).toHaveLength(2)
expect(events.map((e) => e.eventToken)).toContain('event-1')
expect(events.map((e) => e.eventToken)).toContain('event-2')
})
it('overwrites event with same token', () => {
const { saveCreatedEvent, getStoredEvents } = useEventStorage()
saveCreatedEvent({
eventToken: 'abc-123',
title: 'Old Title',
dateTime: '2026-06-15T20:00:00+02:00',
expiryDate: '2026-07-15',
})
saveCreatedEvent({
eventToken: 'abc-123',
title: 'New Title',
dateTime: '2026-06-15T20:00:00+02:00',
expiryDate: '2026-07-15',
})
const events = getStoredEvents()
expect(events).toHaveLength(1)
expect(events[0]!.title).toBe('New Title')
})
})

View File

@@ -0,0 +1,41 @@
export interface StoredEvent {
eventToken: string
organizerToken?: string
title: string
dateTime: string
expiryDate: string
}
const STORAGE_KEY = 'fete:events'
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))
}
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[] {
return readEvents()
}
function getOrganizerToken(eventToken: string): string | undefined {
const event = readEvents().find((e) => e.eventToken === eventToken)
return event?.organizerToken
}
return { saveCreatedEvent, getStoredEvents, getOrganizerToken }
}