diff --git a/app/components/sport-season/EventSchedule.tsx b/app/components/sport-season/EventSchedule.tsx index 1bf4b50..64f4cd8 100644 --- a/app/components/sport-season/EventSchedule.tsx +++ b/app/components/sport-season/EventSchedule.tsx @@ -1,4 +1,5 @@ -import { format, parseISO } from "date-fns"; +import { format } from "date-fns"; +import { useHasMounted } from "~/hooks/useHasMounted"; import { Link } from "react-router"; import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card"; import { Badge } from "~/components/ui/badge"; @@ -25,28 +26,13 @@ interface EventScheduleProps { function formatEventDate(dateStr: string | null | undefined): string { if (!dateStr) return "Date TBD"; try { - // eventDate is stored as a date string "YYYY-MM-DD" - return format(parseISO(dateStr), "MMM d, yyyy"); + const [year, month, day] = dateStr.split("-").map(Number); + return format(new Date(year, month - 1, day), "MMM d, yyyy"); } catch { return dateStr; } } -/** - * Returns the display date for an event. - * Prefers eventStartsAt (a full UTC timestamp → correct local date) over - * eventDate (the UTC calendar date, which is off by one day for late-night - * events in UTC-negative timezones like PDT). - */ -function getDisplayDate(event: Pick): string { - if (event.eventStartsAt) { - const d = new Date(event.eventStartsAt); - if (!isNaN(d.getTime())) { - return format(d, "MMM d, yyyy"); - } - } - return formatEventDate(event.eventDate); -} function getEventTypeLabel(eventType: string): string { switch (eventType) { @@ -64,6 +50,16 @@ function getEventTypeLabel(eventType: string): string { } export function EventSchedule({ upcomingEvents, recentEvents, leagueId, sportsSeasonId }: EventScheduleProps) { + const isMounted = useHasMounted(); + + function getDisplayDate(event: Pick): string { + if (isMounted && event.eventStartsAt) { + const d = new Date(event.eventStartsAt as string); + if (!isNaN(d.getTime())) return format(d, "MMM d, yyyy"); + } + return formatEventDate(event.eventDate); + } + const hasUpcoming = upcomingEvents.length > 0; const hasRecent = recentEvents.length > 0; @@ -112,11 +108,9 @@ export function EventSchedule({ upcomingEvents, recentEvents, leagueId, sportsSe

- {getDisplayDate(event)} - {event.eventStartsAt && ( - - · {format(new Date(event.eventStartsAt), "h:mm a")} - + {getDisplayDate(event)} + {isMounted && event.eventStartsAt && ( + · {format(new Date(event.eventStartsAt), "h:mm a")} )}

@@ -178,7 +172,7 @@ export function EventSchedule({ upcomingEvents, recentEvents, leagueId, sportsSe

- {getDisplayDate(event)} + {getDisplayDate(event)}

{event.eventType !== "schedule_event" && ( diff --git a/app/components/sport-season/UpcomingCalendarPanel.tsx b/app/components/sport-season/UpcomingCalendarPanel.tsx index 8b954f5..3cdcde3 100644 --- a/app/components/sport-season/UpcomingCalendarPanel.tsx +++ b/app/components/sport-season/UpcomingCalendarPanel.tsx @@ -1,6 +1,7 @@ import { format } from "date-fns"; import { Calendar, ChevronRight, Users } from "lucide-react"; import { useState, useEffect } from "react"; +import { useHasMounted } from "~/hooks/useHasMounted"; import { Link } from "react-router"; import { Badge } from "~/components/ui/badge"; import { @@ -58,9 +59,10 @@ function ParticipantList({ participants }: { participants: Array<{ id: string; n } function EventRow({ event, showLeague }: { event: CalendarPanelEvent; showLeague: boolean }) { + const isMounted = useHasMounted(); // Prefer game-level scheduledAt over event-level eventDate for display const gameDate = event.earliestGameTime ? new Date(event.earliestGameTime) : null; - const dateStr = gameDate + const dateStr = isMounted && gameDate ? format(gameDate, "MMM d") : formatEventDate(event.eventDate); const participantCount = event.relevantParticipants.length; @@ -73,14 +75,11 @@ function EventRow({ event, showLeague }: { event: CalendarPanelEvent; showLeague
{/* Date pill */}
- + {dateStr ?? "TBD"} - {gameDate && ( -

+ {isMounted && gameDate && ( +

{gameDate.toLocaleTimeString(undefined, { hour: "numeric", minute: "2-digit", diff --git a/app/hooks/useHasMounted.ts b/app/hooks/useHasMounted.ts new file mode 100644 index 0000000..32585a3 --- /dev/null +++ b/app/hooks/useHasMounted.ts @@ -0,0 +1,7 @@ +import { useSyncExternalStore } from "react"; + +const subscribe = () => () => {}; + +export function useHasMounted(): boolean { + return useSyncExternalStore(subscribe, () => true, () => false); +} diff --git a/app/lib/date-utils.ts b/app/lib/date-utils.ts index 3aeddcf..1c69328 100644 --- a/app/lib/date-utils.ts +++ b/app/lib/date-utils.ts @@ -16,7 +16,7 @@ * clear the field. Always write the result to a separate hidden input, not back to * the datetime-local input itself. */ -import { format, parseISO } from "date-fns"; +import { format } from "date-fns"; /** * Format a YYYY-MM-DD date string for display (e.g. "Apr 9"). @@ -25,7 +25,8 @@ import { format, parseISO } from "date-fns"; export function formatEventDate(dateStr: string | null | undefined): string | null { if (!dateStr) return null; try { - return format(parseISO(dateStr), "MMM d"); + const [year, month, day] = dateStr.split("-").map(Number); + return format(new Date(year, month - 1, day), "MMM d"); } catch { return null; }