refactor: store selected week in a cookie, not the URL
Build and Deploy / Build & Push (push) Successful in 1m7s
Build and Deploy / Build & Push (push) Successful in 1m7s
The home page no longer reads ?week=YYYY-MM-DD from the URL. Selected week lives in the tcWeek cookie, set via a server action that revalidates the home page so the next render reflects it. The URL stays at "/" regardless of which week the user is viewing. WeekNav prev/next/today buttons (and the arrow-key bindings) call the server action directly — no router.refresh dance, no client-side cookie write. BackToCalendarLink drops its localStorage-based href reconstruction and just links to "/" since the cookie already remembers the right week across navigations. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -105,7 +105,7 @@ The backend starts on port 3001, initializes the database, and begins the cron s
|
|||||||
npm run dev
|
npm run dev
|
||||||
```
|
```
|
||||||
|
|
||||||
Open [http://localhost:3000](http://localhost:3000). Navigate weeks with the `←` / `→` buttons, or pass `?week=YYYY-MM-DD` directly. Click any park name to open its detail page.
|
Open [http://localhost:3000](http://localhost:3000). Navigate weeks with the `←` / `→` buttons (or arrow keys); your selected week persists across visits via the `tcWeek` cookie. Click any park name to open its detail page.
|
||||||
|
|
||||||
### Debug a specific park + date
|
### Debug a specific park + date
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
"use server";
|
||||||
|
|
||||||
|
import { cookies } from "next/headers";
|
||||||
|
import { revalidatePath } from "next/cache";
|
||||||
|
|
||||||
|
const WEEK_COOKIE = "tcWeek";
|
||||||
|
const MAX_AGE = 60 * 60 * 24 * 30; // 30 days
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Persist the selected week start (YYYY-MM-DD) in a server-readable cookie
|
||||||
|
* and revalidate the home page so the new week renders.
|
||||||
|
*/
|
||||||
|
export async function setWeek(weekStart: string): Promise<void> {
|
||||||
|
if (!/^\d{4}-\d{2}-\d{2}$/.test(weekStart)) return;
|
||||||
|
const cookieStore = await cookies();
|
||||||
|
cookieStore.set(WEEK_COOKIE, weekStart, {
|
||||||
|
path: "/",
|
||||||
|
maxAge: MAX_AGE,
|
||||||
|
sameSite: "lax",
|
||||||
|
});
|
||||||
|
revalidatePath("/");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Clear the saved week — used by the "Today" button to jump back to current. */
|
||||||
|
export async function clearWeek(): Promise<void> {
|
||||||
|
const cookieStore = await cookies();
|
||||||
|
cookieStore.delete(WEEK_COOKIE);
|
||||||
|
revalidatePath("/");
|
||||||
|
}
|
||||||
+8
-9
@@ -1,15 +1,14 @@
|
|||||||
|
import { cookies } from "next/headers";
|
||||||
import { HomePageClient } from "@/components/HomePageClient";
|
import { HomePageClient } from "@/components/HomePageClient";
|
||||||
import { getTodayLocal, formatDateLocal } from "@/lib/env";
|
import { getTodayLocal, formatDateLocal } from "@/lib/env";
|
||||||
|
|
||||||
const BACKEND_URL = process.env.BACKEND_URL ?? "http://localhost:3001";
|
const BACKEND_URL = process.env.BACKEND_URL ?? "http://localhost:3001";
|
||||||
|
|
||||||
interface PageProps {
|
const WEEK_COOKIE = "tcWeek";
|
||||||
searchParams: Promise<{ week?: string }>;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getWeekStart(param: string | undefined): string {
|
function getWeekStart(saved: string | undefined): string {
|
||||||
if (param && /^\d{4}-\d{2}-\d{2}$/.test(param)) {
|
if (saved && /^\d{4}-\d{2}-\d{2}$/.test(saved)) {
|
||||||
const d = new Date(param + "T00:00:00");
|
const d = new Date(saved + "T00:00:00");
|
||||||
if (!isNaN(d.getTime())) {
|
if (!isNaN(d.getTime())) {
|
||||||
d.setDate(d.getDate() - d.getDay());
|
d.setDate(d.getDate() - d.getDay());
|
||||||
return formatDateLocal(d);
|
return formatDateLocal(d);
|
||||||
@@ -21,9 +20,9 @@ function getWeekStart(param: string | undefined): string {
|
|||||||
return formatDateLocal(d);
|
return formatDateLocal(d);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function HomePage({ searchParams }: PageProps) {
|
export default async function HomePage() {
|
||||||
const params = await searchParams;
|
const saved = (await cookies()).get(WEEK_COOKIE)?.value;
|
||||||
const weekStart = getWeekStart(params.week);
|
const weekStart = getWeekStart(saved);
|
||||||
|
|
||||||
const data = await fetch(
|
const data = await fetch(
|
||||||
`${BACKEND_URL}/api/calendar/week?start=${weekStart}`,
|
`${BACKEND_URL}/api/calendar/week?start=${weekStart}`,
|
||||||
|
|||||||
@@ -1,19 +1,12 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { useState, useEffect } from "react";
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
|
||||||
export function BackToCalendarLink() {
|
export function BackToCalendarLink() {
|
||||||
const [href, setHref] = useState("/");
|
// The selected week is stored in a server-readable cookie (tcWeek), so the
|
||||||
|
// home page already renders the right week without us needing to pass it
|
||||||
useEffect(() => {
|
// through the URL.
|
||||||
const saved = localStorage.getItem("lastWeek");
|
|
||||||
if (saved) setHref(`/?week=${saved}`);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
href={href}
|
href="/"
|
||||||
className="park-name-link"
|
className="park-name-link"
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
|
|||||||
@@ -109,11 +109,6 @@ export function HomePageClient({
|
|||||||
return () => timeouts.forEach(clearTimeout);
|
return () => timeouts.forEach(clearTimeout);
|
||||||
}, [isCurrentWeek, today, data, router]);
|
}, [isCurrentWeek, today, data, router]);
|
||||||
|
|
||||||
// Remember the current week so the park page back button returns here.
|
|
||||||
useEffect(() => {
|
|
||||||
localStorage.setItem("lastWeek", weekStart);
|
|
||||||
}, [weekStart]);
|
|
||||||
|
|
||||||
const toggle = () => {
|
const toggle = () => {
|
||||||
const next = !coastersOnly;
|
const next = !coastersOnly;
|
||||||
setCoastersOnly(next);
|
setCoastersOnly(next);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { useRouter } from "next/navigation";
|
import { setWeek, clearWeek } from "@/app/actions/week";
|
||||||
|
|
||||||
interface WeekNavProps {
|
interface WeekNavProps {
|
||||||
weekStart: string; // YYYY-MM-DD (Sunday)
|
weekStart: string; // YYYY-MM-DD (Sunday)
|
||||||
@@ -39,9 +39,11 @@ function shiftWeek(weekStart: string, delta: number): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function WeekNav({ weekStart, weekDates, isCurrentWeek }: WeekNavProps) {
|
export function WeekNav({ weekStart, weekDates, isCurrentWeek }: WeekNavProps) {
|
||||||
const router = useRouter();
|
|
||||||
const nav = (delta: number) => {
|
const nav = (delta: number) => {
|
||||||
router.push(`/?week=${shiftWeek(weekStart, delta)}`);
|
void setWeek(shiftWeek(weekStart, delta));
|
||||||
|
};
|
||||||
|
const jumpToToday = () => {
|
||||||
|
void clearWeek();
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -68,7 +70,7 @@ export function WeekNav({ weekStart, weekDates, isCurrentWeek }: WeekNavProps) {
|
|||||||
|
|
||||||
{!isCurrentWeek && (
|
{!isCurrentWeek && (
|
||||||
<button
|
<button
|
||||||
onClick={() => router.push("/")}
|
onClick={jumpToToday}
|
||||||
aria-label="Jump to current week"
|
aria-label="Jump to current week"
|
||||||
style={todayBtnStyle}
|
style={todayBtnStyle}
|
||||||
onMouseOver={(e) => Object.assign((e.target as HTMLElement).style, todayBtnHover)}
|
onMouseOver={(e) => Object.assign((e.target as HTMLElement).style, todayBtnHover)}
|
||||||
|
|||||||
@@ -420,12 +420,18 @@ park/[id]/page.tsx (Server)
|
|||||||
2. **Opening-time refresh**: For each park open today, calculates milliseconds until its opening time using `msUntilLocalTime()` (timezone-aware). Schedules `router.refresh()` at opening and again 30 seconds later (to pick up ride counts after Queue-Times starts reporting).
|
2. **Opening-time refresh**: For each park open today, calculates milliseconds until its opening time using `msUntilLocalTime()` (timezone-aware). Schedules `router.refresh()` at opening and again 30 seconds later (to pick up ride counts after Queue-Times starts reporting).
|
||||||
3. **Keyboard navigation**: Left/right arrow keys navigate between weeks (via `WeekNav` component).
|
3. **Keyboard navigation**: Left/right arrow keys navigate between weeks (via `WeekNav` component).
|
||||||
|
|
||||||
|
### Cookies
|
||||||
|
|
||||||
|
| Name | Purpose |
|
||||||
|
|------|---------|
|
||||||
|
| `tcWeek` | Selected week start date (`YYYY-MM-DD`). Set by `WeekNav` and read server-side by `app/page.tsx`, so the home page renders the right week without polluting the URL. |
|
||||||
|
|
||||||
### localStorage
|
### localStorage
|
||||||
|
|
||||||
| Key | Purpose |
|
| Key | Purpose |
|
||||||
|-----|---------|
|
|-----|---------|
|
||||||
| `lastWeek` | Remembers the last viewed week start date, used by `BackToCalendarLink` to return to the correct week |
|
|
||||||
| `coasterMode` | Persists the "Coasters only" toggle state across sessions |
|
| `coasterMode` | Persists the "Coasters only" toggle state across sessions |
|
||||||
|
| `fastLaneMode` | Persists the Fast Lane wait toggle on the park page |
|
||||||
|
|
||||||
### Responsive Design
|
### Responsive Design
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -55,7 +55,7 @@ npm run dev
|
|||||||
This starts the Next.js dev server on port 3000 with hot reload. Open [http://localhost:3000](http://localhost:3000).
|
This starts the Next.js dev server on port 3000 with hot reload. Open [http://localhost:3000](http://localhost:3000).
|
||||||
|
|
||||||
**Navigation:**
|
**Navigation:**
|
||||||
- Use the `←` / `→` buttons to navigate weeks, or pass `?week=YYYY-MM-DD` in the URL
|
- Use the `←` / `→` buttons (or arrow keys) to navigate weeks; the selected week persists across visits via the `tcWeek` cookie
|
||||||
- Click any park name to open its detail page with month calendar and ride status
|
- Click any park name to open its detail page with month calendar and ride status
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
Reference in New Issue
Block a user