@@ -6,6 +6,49 @@ import { transformEventsApiDates } from '../api/events';
66import type { Event } from '../types/events' ;
77import { Temporal } from 'temporal-polyfill' ;
88
9+ /**
10+ * For recurring events, advances `start` (and `end`) to the next occurrence
11+ * on or after today. Non-recurring events are returned unchanged.
12+ */
13+ function getNextOccurrence ( event : Event ) : Event {
14+ if ( ! event . repeats ) return event ;
15+
16+ const today = Temporal . Now . plainDateISO ( ) ;
17+ const startDT = Temporal . PlainDateTime . from ( event . start ) ;
18+ const endDT = event . end ? Temporal . PlainDateTime . from ( event . end ) : null ;
19+ const repeatEnds = event . repeatEnds
20+ ? Temporal . PlainDateTime . from ( event . repeatEnds )
21+ : null ;
22+
23+ const intervalDays = event . repeats === 'weekly' ? 7 : 14 ;
24+
25+ const excludedDates = new Set ( event . repeatExcludes ?. map ( String ) ?? [ ] ) ;
26+
27+ const daysBehind = startDT . toPlainDate ( ) . until ( today ) . days ;
28+ const stepsNeeded = daysBehind > 0 ? Math . ceil ( daysBehind / intervalDays ) : 0 ;
29+ let current = startDT . add ( { days : stepsNeeded * intervalDays } ) ;
30+
31+ // Skip any excluded dates
32+ while ( excludedDates . has ( current . toPlainDate ( ) . toString ( ) ) ) {
33+ current = current . add ( { days : intervalDays } ) ;
34+ }
35+
36+ // If the next occurrence is past the recurrence end, keep the original
37+ if ( repeatEnds && Temporal . PlainDateTime . compare ( current , repeatEnds ) > 0 ) {
38+ return event ;
39+ }
40+
41+ const duration = endDT ? startDT . until ( endDT ) : null ;
42+
43+ return {
44+ ...event ,
45+ start : current . toString ( { smallestUnit : 'second' } ) ,
46+ end : duration
47+ ? current . add ( duration ) . toString ( { smallestUnit : 'second' } )
48+ : event . end ,
49+ } ;
50+ }
51+
952function formatDate ( dateStr : string ) : string {
1053 const date = new Date ( dateStr ) ;
1154 return date . toLocaleDateString ( 'en-US' , {
@@ -146,20 +189,19 @@ const UpcomingEvents = () => {
146189 useEffect ( ( ) => {
147190 const fetchEvents = async ( ) => {
148191 try {
149- const response = (
150- await eventsApiClient . apiV1EventsGet ( {
151- upcomingOnly : true ,
152- featuredOnly : true ,
153- } )
154- )
155- . filter ( ( x ) => x . featured )
192+ const raw = await eventsApiClient . apiV1EventsGet ( {
193+ upcomingOnly : true ,
194+ featuredOnly : true ,
195+ } ) ;
196+ const response = transformEventsApiDates ( raw . filter ( ( x ) => x . featured ) )
197+ . map ( getNextOccurrence )
156198 . sort ( ( a , b ) => {
157199 const aStart = Temporal . PlainDateTime . from ( a . start ) ;
158200 const bStart = Temporal . PlainDateTime . from ( b . start ) ;
159201 return Temporal . PlainDateTime . compare ( aStart , bStart ) ;
160202 } )
161203 . slice ( 0 , 3 ) ;
162- setFeaturedEvents ( transformEventsApiDates ( response ) ) ;
204+ setFeaturedEvents ( response ) ;
163205 } catch ( error ) {
164206 console . error ( 'Error fetching events:' , error ) ;
165207 } finally {
0 commit comments