Blocked by #193.
The typed loader every competitions page reads from. Mirror src/lib/places.ts (74 lines), which is the working precedent.
Files
src/lib/competitions.ts - new
src/lib/types.ts - add the competition types alongside the existing Place types
Shape
Follow src/lib/places.ts closely:
export function getCompetitions(): Competition[]
export function filterCompetitions(
competitions: Competition[],
filters: { categories?: CompetitionCategory[]; format?: Format; region?: string;
participation?: Participation; feeMax?: number; age?: number;
deadlineBefore?: string; query?: string }
): Competition[]
export function getCategories(): CompetitionCategory[]
export function upcomingDates(c: Competition, now: Date): CompetitionDate[]
export function nextDate(c: Competition, now: Date): CompetitionDate | undefined
Types go in src/lib/types.ts next to Place, following its conventions: a const tuple for the enum plus a derived union (src/lib/types.ts:1-10), and a Record<T, string> label map (:20-27).
Loading
Static ESM import of the JSON files, the way studymap.config.ts:12-17 does it. resolveJsonModule: true is already set in tsconfig.json. No fs, no ISR, no fetch. The dataset bundles at build time exactly like places.
Do not put this in studymap.config.ts. That file is the map's configuration (center, bounds, cities, places) and it already contradicts its own dataset, which is open issue #156. Competitions gets its own module.
Date flattening
upcomingDates and nextDate are what the browse cards, the countdown and the calendar all read. Flatten dates[] and country_tracks[].stages[] into one comparable list, keep the country tag on stage-derived entries so callers can filter by country, and sort ascending. Getting this right once here is what stops three components each rolling their own.
Do not add a date library. src/app/calendar/CalendarView.tsx handles a 478-line month grid with plain Date and the repo has no date-fns or dayjs on purpose.
Acceptance criteria
Blocked by #193.
The typed loader every competitions page reads from. Mirror
src/lib/places.ts(74 lines), which is the working precedent.Files
src/lib/competitions.ts- newsrc/lib/types.ts- add the competition types alongside the existingPlacetypesShape
Follow
src/lib/places.tsclosely:Types go in
src/lib/types.tsnext toPlace, following its conventions: aconsttuple for the enum plus a derived union (src/lib/types.ts:1-10), and aRecord<T, string>label map (:20-27).Loading
Static ESM import of the JSON files, the way
studymap.config.ts:12-17does it.resolveJsonModule: trueis already set intsconfig.json. Nofs, no ISR, no fetch. The dataset bundles at build time exactly like places.Do not put this in
studymap.config.ts. That file is the map's configuration (center, bounds, cities, places) and it already contradicts its own dataset, which is open issue #156. Competitions gets its own module.Date flattening
upcomingDatesandnextDateare what the browse cards, the countdown and the calendar all read. Flattendates[]andcountry_tracks[].stages[]into one comparable list, keep the country tag on stage-derived entries so callers can filter by country, and sort ascending. Getting this right once here is what stops three components each rolling their own.Do not add a date library.
src/app/calendar/CalendarView.tsxhandles a 478-line month grid with plainDateand the repo has nodate-fnsordayjson purpose.Acceptance criteria
getCompetitions()returns all records from every category filefilterCompetitionscomposes: two filters at once narrow correctlyquerymatches on name, organizer and subjects, case-insensitivelynextDateskips past dates and returns the soonest future oneupcomingDatesincludes country stages only when a country is passednpx tsc --noEmitclean, noany