diff --git a/app/page.tsx b/app/page.tsx index d90edce..485d33b 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,9 +1,5 @@ -import { WeekCalendar } from "@/components/WeekCalendar"; -import { MobileCardList } from "@/components/MobileCardList"; -import { WeekNav } from "@/components/WeekNav"; -import { Legend } from "@/components/Legend"; -import { EmptyState } from "@/components/EmptyState"; -import { PARKS, groupByRegion } from "@/lib/parks"; +import { HomePageClient } from "@/components/HomePageClient"; +import { PARKS } from "@/lib/parks"; import { openDb, getDateRange } from "@/lib/db"; import { getTodayLocal, isWithinOperatingWindow } from "@/lib/env"; import { fetchLiveRides } from "@/lib/scrapers/queuetimes"; @@ -11,7 +7,7 @@ import { QUEUE_TIMES_IDS } from "@/lib/queue-times-map"; import { readParkMeta, getCoasterSet } from "@/lib/park-meta"; interface PageProps { - searchParams: Promise<{ week?: string; coasters?: string }>; + searchParams: Promise<{ week?: string }>; } function getWeekStart(param: string | undefined): string { @@ -46,7 +42,6 @@ function getCurrentWeekStart(): string { export default async function HomePage({ searchParams }: PageProps) { const params = await searchParams; const weekStart = getWeekStart(params.week); - const coastersOnly = params.coasters === "1"; const weekDates = getWeekDates(weekStart); const endDate = weekDates[6]; const today = getTodayLocal(); @@ -61,8 +56,7 @@ export default async function HomePage({ searchParams }: PageProps) { 0 ); - // Fetch live ride counts for parks open today (cached 5 min via Queue-Times). - // Only shown when the current time is within 1h before open to 1h after close. + // Always fetch both ride and coaster counts — the client decides which to display. const parkMeta = readParkMeta(); const hasCoasterData = PARKS.some((p) => (parkMeta[p.id]?.coasters.length ?? 0) > 0); @@ -91,108 +85,17 @@ export default async function HomePage({ searchParams }: PageProps) { ); } - const activeCounts = coastersOnly ? coasterCounts : rideCounts; - - const visibleParks = PARKS.filter((park) => - weekDates.some((date) => data[park.id]?.[date]?.isOpen) - ); - - const grouped = groupByRegion(visibleParks); - return ( -
- {/* ── Header ─────────────────────────────────────────────────────────── */} -
- {/* Row 1: Title + park count */} -
- - Thoosie Calendar - - - 0 ? "var(--color-open-bg)" : "var(--color-surface)", - border: `1px solid ${visibleParks.length > 0 ? "var(--color-open-border)" : "var(--color-border)"}`, - borderRadius: 20, - padding: "4px 14px", - fontSize: "0.78rem", - color: visibleParks.length > 0 ? "var(--color-open-hours)" : "var(--color-text-muted)", - fontWeight: 600, - }}> - {visibleParks.length} of {PARKS.length} parks open this week - -
- - {/* Row 2: Week nav + legend (legend hidden on mobile) */} -
- -
- -
-
-
- - {/* ── Main content ───────────────────────────────────────────────────── */} -
- {scrapedCount === 0 ? ( - - ) : ( - <> - {/* Mobile: card list (hidden on lg+) */} -
- -
- - {/* Desktop: week table (hidden below lg) */} -
- -
- - )} -
-
+ ); } diff --git a/components/HomePageClient.tsx b/components/HomePageClient.tsx new file mode 100644 index 0000000..848e960 --- /dev/null +++ b/components/HomePageClient.tsx @@ -0,0 +1,182 @@ +"use client"; + +import { useState, useEffect } from "react"; +import { WeekCalendar } from "./WeekCalendar"; +import { MobileCardList } from "./MobileCardList"; +import { WeekNav } from "./WeekNav"; +import { Legend } from "./Legend"; +import { EmptyState } from "./EmptyState"; +import { PARKS, groupByRegion } from "@/lib/parks"; +import type { DayData } from "@/lib/db"; + +const COASTER_MODE_KEY = "coasterMode"; + +interface HomePageClientProps { + weekStart: string; + weekDates: string[]; + today: string; + isCurrentWeek: boolean; + data: Record>; + rideCounts: Record; + coasterCounts: Record; + hasCoasterData: boolean; + scrapedCount: number; +} + +export function HomePageClient({ + weekStart, + weekDates, + today, + isCurrentWeek, + data, + rideCounts, + coasterCounts, + hasCoasterData, + scrapedCount, +}: HomePageClientProps) { + const [coastersOnly, setCoastersOnly] = useState(false); + + // Hydrate from localStorage after mount to avoid SSR mismatch. + useEffect(() => { + setCoastersOnly(localStorage.getItem(COASTER_MODE_KEY) === "true"); + }, []); + + const toggle = () => { + const next = !coastersOnly; + setCoastersOnly(next); + localStorage.setItem(COASTER_MODE_KEY, String(next)); + }; + + const activeCounts = coastersOnly ? coasterCounts : rideCounts; + + const visibleParks = PARKS.filter((park) => + weekDates.some((date) => data[park.id]?.[date]?.isOpen) + ); + const grouped = groupByRegion(visibleParks); + + return ( +
+ {/* ── Header ───────────────────────────────────────────────────────────── */} +
+ {/* Row 1: Title + controls */} +
+ + Thoosie Calendar + + +
+ {hasCoasterData && ( + + )} + + 0 ? "var(--color-open-bg)" : "var(--color-surface)", + border: `1px solid ${visibleParks.length > 0 ? "var(--color-open-border)" : "var(--color-border)"}`, + borderRadius: 20, + padding: "4px 14px", + fontSize: "0.78rem", + color: visibleParks.length > 0 ? "var(--color-open-hours)" : "var(--color-text-muted)", + fontWeight: 600, + }}> + {visibleParks.length} of {PARKS.length} parks open this week + +
+
+ + {/* Row 2: Week nav + legend */} +
+ +
+ +
+
+
+ + {/* ── Main content ────────────────────────────────────────────────────── */} +
+ {scrapedCount === 0 ? ( + + ) : ( + <> + {/* Mobile: card list (hidden on lg+) */} +
+ +
+ + {/* Desktop: week table (hidden below lg) */} +
+ +
+ + )} +
+
+ ); +} diff --git a/components/LiveRidePanel.tsx b/components/LiveRidePanel.tsx index 57f379b..cb641fa 100644 --- a/components/LiveRidePanel.tsx +++ b/components/LiveRidePanel.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState } from "react"; +import { useState, useEffect } from "react"; import type { LiveRidesResult, LiveRide } from "@/lib/scrapers/queuetimes"; interface LiveRidePanelProps { @@ -13,6 +13,13 @@ export function LiveRidePanel({ liveRides, parkOpenToday }: LiveRidePanelProps) const hasCoasters = rides.some((r) => r.isCoaster); const [coastersOnly, setCoastersOnly] = useState(false); + // Pre-select coaster filter if Coaster Mode is enabled on the homepage. + useEffect(() => { + if (hasCoasters && localStorage.getItem("coasterMode") === "true") { + setCoastersOnly(true); + } + }, [hasCoasters]); + const visible = coastersOnly ? rides.filter((r) => r.isCoaster) : rides; const openRides = visible.filter((r) => r.isOpen); const closedRides = visible.filter((r) => !r.isOpen); diff --git a/components/WeekNav.tsx b/components/WeekNav.tsx index 0548e3e..72c5118 100644 --- a/components/WeekNav.tsx +++ b/components/WeekNav.tsx @@ -7,8 +7,6 @@ interface WeekNavProps { weekStart: string; // YYYY-MM-DD (Sunday) weekDates: string[]; // 7 dates YYYY-MM-DD isCurrentWeek: boolean; - coastersOnly: boolean; - hasCoasterData: boolean; } const MONTHS = [ @@ -33,15 +31,10 @@ function shiftWeek(weekStart: string, delta: number): string { return d.toISOString().slice(0, 10); } -export function WeekNav({ weekStart, weekDates, isCurrentWeek, coastersOnly, hasCoasterData }: WeekNavProps) { +export function WeekNav({ weekStart, weekDates, isCurrentWeek }: WeekNavProps) { const router = useRouter(); - const weekParam = `week=${weekStart}`; const nav = (delta: number) => { - const base = `/?week=${shiftWeek(weekStart, delta)}`; - router.push(coastersOnly ? `${base}&coasters=1` : base); - }; - const toggleCoasters = () => { - router.push(coastersOnly ? `/?${weekParam}` : `/?${weekParam}&coasters=1`); + router.push(`/?week=${shiftWeek(weekStart, delta)}`); }; useEffect(() => { @@ -99,30 +92,6 @@ export function WeekNav({ weekStart, weekDates, isCurrentWeek, coastersOnly, has > → - - {hasCoasterData && ( - - )} ); }