Files
SixFlagsSuperCalendar/components/WeekNav.tsx
Josh Wright 5b575f962e
All checks were successful
Build and Deploy / Build & Push (push) Successful in 53s
feat: persistent Coaster Mode toggle in header top-right
- Moves the coaster toggle out of WeekNav and into the homepage header
  top-right as "🎢 Coaster Mode", alongside the parks open badge
- State is stored in localStorage ("coasterMode") so the preference
  persists across sessions and page refreshes
- Dropped the ?coasters=1 URL param entirely; the server always fetches
  both rideCounts and coasterCounts, and HomePageClient picks which to
  display client-side — no flash or server round-trip on toggle
- Individual park pages: LiveRidePanel reads localStorage on mount and
  pre-selects the Coasters Only filter when Coaster Mode is active
- Extracted HomePageClient (client component) to own the full homepage
  UI; page.tsx is now pure data-fetching

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 08:36:38 -04:00

152 lines
4.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { useEffect } from "react";
import { useRouter } from "next/navigation";
interface WeekNavProps {
weekStart: string; // YYYY-MM-DD (Sunday)
weekDates: string[]; // 7 dates YYYY-MM-DD
isCurrentWeek: boolean;
}
const MONTHS = [
"Jan","Feb","Mar","Apr","May","Jun",
"Jul","Aug","Sep","Oct","Nov","Dec",
];
function formatLabel(dates: string[]): string {
const s = new Date(dates[0] + "T00:00:00");
const e = new Date(dates[6] + "T00:00:00");
if (s.getFullYear() === e.getFullYear() && s.getMonth() === e.getMonth()) {
return `${MONTHS[s.getMonth()]} ${s.getDate()}${e.getDate()}, ${s.getFullYear()}`;
}
const startStr = `${MONTHS[s.getMonth()]} ${s.getDate()}`;
const endStr = `${MONTHS[e.getMonth()]} ${e.getDate()}, ${e.getFullYear()}`;
return `${startStr} ${endStr}`;
}
function shiftWeek(weekStart: string, delta: number): string {
const d = new Date(weekStart + "T00:00:00");
d.setDate(d.getDate() + delta * 7);
return d.toISOString().slice(0, 10);
}
export function WeekNav({ weekStart, weekDates, isCurrentWeek }: WeekNavProps) {
const router = useRouter();
const nav = (delta: number) => {
router.push(`/?week=${shiftWeek(weekStart, delta)}`);
};
useEffect(() => {
const onKey = (e: KeyboardEvent) => {
if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) return;
if (e.key === "ArrowLeft") nav(-1);
if (e.key === "ArrowRight") nav(1);
};
window.addEventListener("keydown", onKey);
return () => window.removeEventListener("keydown", onKey);
}, [weekStart]);
return (
<div style={{ display: "flex", alignItems: "center", gap: 8 }}>
<button
onClick={() => nav(-1)}
aria-label="Previous week"
style={navBtnStyle}
onMouseOver={(e) => Object.assign((e.target as HTMLElement).style, navBtnHover)}
onMouseOut={(e) => Object.assign((e.target as HTMLElement).style, navBtnStyle)}
>
</button>
{!isCurrentWeek && (
<button
onClick={() => router.push("/")}
aria-label="Jump to current week"
style={todayBtnStyle}
onMouseOver={(e) => Object.assign((e.target as HTMLElement).style, todayBtnHover)}
onMouseOut={(e) => Object.assign((e.target as HTMLElement).style, todayBtnStyle)}
>
Today
</button>
)}
<span style={{
fontSize: "1rem",
fontWeight: 600,
color: "var(--color-text)",
minWidth: 200,
textAlign: "center",
letterSpacing: "-0.01em",
fontVariantNumeric: "tabular-nums",
}}>
{formatLabel(weekDates)}
</span>
<button
onClick={() => nav(1)}
aria-label="Next week"
style={navBtnStyle}
onMouseOver={(e) => Object.assign((e.target as HTMLElement).style, navBtnHover)}
onMouseOut={(e) => Object.assign((e.target as HTMLElement).style, navBtnStyle)}
>
</button>
</div>
);
}
const navBtnStyle: React.CSSProperties = {
padding: "6px 14px",
borderRadius: 6,
border: "1px solid var(--color-border)",
background: "var(--color-surface)",
color: "var(--color-text-muted)",
cursor: "pointer",
fontSize: "1rem",
lineHeight: 1,
transition: "background 150ms ease, border-color 150ms ease, color 150ms ease",
};
const navBtnHover: React.CSSProperties = {
padding: "6px 14px",
borderRadius: 6,
border: "1px solid var(--color-text-dim)",
background: "var(--color-surface-2)",
color: "var(--color-text-secondary)",
cursor: "pointer",
fontSize: "1rem",
lineHeight: 1,
transition: "background 150ms ease, border-color 150ms ease, color 150ms ease",
};
const todayBtnStyle: React.CSSProperties = {
padding: "5px 12px",
borderRadius: 6,
border: "1px solid var(--color-accent-muted)",
background: "transparent",
color: "var(--color-accent)",
cursor: "pointer",
fontSize: "0.75rem",
fontWeight: 600,
letterSpacing: "0.04em",
textTransform: "uppercase",
lineHeight: 1,
transition: "background 150ms ease, color 150ms ease",
};
const todayBtnHover: React.CSSProperties = {
padding: "5px 12px",
borderRadius: 6,
border: "1px solid var(--color-accent-muted)",
background: "var(--color-accent-muted)",
color: "var(--color-accent-text)",
cursor: "pointer",
fontSize: "0.75rem",
fontWeight: 600,
letterSpacing: "0.04em",
textTransform: "uppercase",
lineHeight: 1,
transition: "background 150ms ease, color 150ms ease",
};