feat: add Fast Lane wait times toggle on park pages
Build and Deploy / Build & Push (push) Successful in 1m3s
Build and Deploy / Build & Push (push) Successful in 1m3s
Join Fast Lane waits from the Six Flags /wait-times endpoint onto Queue-Times rides by name. A new toggle on the live ride panel swaps the shown wait to the Fast Lane number; regular waits and open status still come from Queue-Times. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+134
-53
@@ -12,7 +12,9 @@ interface LiveRidePanelProps {
|
||||
export function LiveRidePanel({ liveRides, parkOpenToday, isWeatherDelay }: LiveRidePanelProps) {
|
||||
const { rides } = liveRides;
|
||||
const hasCoasters = rides.some((r) => r.isCoaster);
|
||||
const hasFastLane = rides.some((r) => r.hasFastLane);
|
||||
const [coastersOnly, setCoastersOnly] = useState(false);
|
||||
const [fastLaneMode, setFastLaneMode] = useState(false);
|
||||
|
||||
// Pre-select coaster filter if Coaster Mode is enabled on the homepage.
|
||||
useEffect(() => {
|
||||
@@ -21,6 +23,21 @@ export function LiveRidePanel({ liveRides, parkOpenToday, isWeatherDelay }: Live
|
||||
}
|
||||
}, [hasCoasters]);
|
||||
|
||||
// Restore Fast Lane mode from a previous visit.
|
||||
useEffect(() => {
|
||||
if (hasFastLane && localStorage.getItem("fastLaneMode") === "true") {
|
||||
setFastLaneMode(true);
|
||||
}
|
||||
}, [hasFastLane]);
|
||||
|
||||
const toggleFastLane = () => {
|
||||
setFastLaneMode((v) => {
|
||||
const next = !v;
|
||||
localStorage.setItem("fastLaneMode", String(next));
|
||||
return next;
|
||||
});
|
||||
};
|
||||
|
||||
const visible = coastersOnly ? rides.filter((r) => r.isCoaster) : rides;
|
||||
const openRides = visible.filter((r) => r.isOpen);
|
||||
const closedRides = visible.filter((r) => !r.isOpen);
|
||||
@@ -94,35 +111,69 @@ export function LiveRidePanel({ liveRides, parkOpenToday, isWeatherDelay }: Live
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Coaster toggle — only shown when the park has categorised coasters */}
|
||||
{hasCoasters && (
|
||||
<button
|
||||
onClick={() => setCoastersOnly((v) => !v)}
|
||||
style={{
|
||||
marginLeft: "auto",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: 5,
|
||||
padding: "4px 12px",
|
||||
borderRadius: 20,
|
||||
border: coastersOnly
|
||||
? "1px solid var(--color-accent)"
|
||||
: "1px solid var(--color-border)",
|
||||
background: coastersOnly
|
||||
? "var(--color-accent-muted)"
|
||||
: "var(--color-surface)",
|
||||
color: coastersOnly
|
||||
? "var(--color-accent)"
|
||||
: "var(--color-text-muted)",
|
||||
fontSize: "0.72rem",
|
||||
fontWeight: 600,
|
||||
cursor: "pointer",
|
||||
transition: "background 150ms ease, border-color 150ms ease, color 150ms ease",
|
||||
whiteSpace: "nowrap",
|
||||
}}
|
||||
>
|
||||
🎢 Coasters only
|
||||
</button>
|
||||
{/* Toggle group — pushed to the right */}
|
||||
{(hasCoasters || hasFastLane) && (
|
||||
<div style={{ marginLeft: "auto", display: "flex", gap: 8, flexWrap: "wrap" }}>
|
||||
{/* Fast Lane toggle — swaps shown waits to Fast Lane numbers */}
|
||||
{hasFastLane && (
|
||||
<button
|
||||
onClick={toggleFastLane}
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: 5,
|
||||
padding: "4px 12px",
|
||||
borderRadius: 20,
|
||||
border: fastLaneMode
|
||||
? "1px solid var(--color-accent)"
|
||||
: "1px solid var(--color-border)",
|
||||
background: fastLaneMode
|
||||
? "var(--color-accent-muted)"
|
||||
: "var(--color-surface)",
|
||||
color: fastLaneMode
|
||||
? "var(--color-accent)"
|
||||
: "var(--color-text-muted)",
|
||||
fontSize: "0.72rem",
|
||||
fontWeight: 600,
|
||||
cursor: "pointer",
|
||||
transition: "background 150ms ease, border-color 150ms ease, color 150ms ease",
|
||||
whiteSpace: "nowrap",
|
||||
}}
|
||||
>
|
||||
⚡ Fast Lane
|
||||
</button>
|
||||
)}
|
||||
|
||||
{/* Coaster toggle — only shown when the park has categorised coasters */}
|
||||
{hasCoasters && (
|
||||
<button
|
||||
onClick={() => setCoastersOnly((v) => !v)}
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: 5,
|
||||
padding: "4px 12px",
|
||||
borderRadius: 20,
|
||||
border: coastersOnly
|
||||
? "1px solid var(--color-accent)"
|
||||
: "1px solid var(--color-border)",
|
||||
background: coastersOnly
|
||||
? "var(--color-accent-muted)"
|
||||
: "var(--color-surface)",
|
||||
color: coastersOnly
|
||||
? "var(--color-accent)"
|
||||
: "var(--color-text-muted)",
|
||||
fontSize: "0.72rem",
|
||||
fontWeight: 600,
|
||||
cursor: "pointer",
|
||||
transition: "background 150ms ease, border-color 150ms ease, color 150ms ease",
|
||||
whiteSpace: "nowrap",
|
||||
}}
|
||||
>
|
||||
🎢 Coasters only
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -132,16 +183,18 @@ export function LiveRidePanel({ liveRides, parkOpenToday, isWeatherDelay }: Live
|
||||
gridTemplateColumns: "repeat(auto-fill, minmax(280px, 1fr))",
|
||||
gap: 6,
|
||||
}}>
|
||||
{openRides.map((ride) => <RideRow key={ride.name} ride={ride} />)}
|
||||
{closedRides.map((ride) => <RideRow key={ride.name} ride={ride} />)}
|
||||
{openRides.map((ride) => <RideRow key={ride.name} ride={ride} fastLaneMode={fastLaneMode} />)}
|
||||
{closedRides.map((ride) => <RideRow key={ride.name} ride={ride} fastLaneMode={fastLaneMode} />)}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function RideRow({ ride }: { ride: LiveRide }) {
|
||||
function RideRow({ ride, fastLaneMode }: { ride: LiveRide; fastLaneMode: boolean }) {
|
||||
const showWait = ride.isOpen && ride.waitMinutes > 0;
|
||||
const fastLaneActive = fastLaneMode && ride.hasFastLane;
|
||||
const flWait = ride.fastLaneMinutes ?? 0;
|
||||
|
||||
return (
|
||||
<div style={{
|
||||
@@ -174,27 +227,55 @@ function RideRow({ ride }: { ride: LiveRide }) {
|
||||
{ride.name}
|
||||
</span>
|
||||
</div>
|
||||
{showWait && (
|
||||
<span style={{
|
||||
fontSize: "0.72rem",
|
||||
color: "var(--color-open-hours)",
|
||||
fontWeight: 600,
|
||||
flexShrink: 0,
|
||||
whiteSpace: "nowrap",
|
||||
}}>
|
||||
{ride.waitMinutes} min
|
||||
</span>
|
||||
)}
|
||||
{ride.isOpen && !showWait && (
|
||||
<span style={{
|
||||
fontSize: "0.68rem",
|
||||
color: "var(--color-open-text)",
|
||||
fontWeight: 500,
|
||||
flexShrink: 0,
|
||||
opacity: 0.7,
|
||||
}}>
|
||||
walk-on
|
||||
</span>
|
||||
{/* Fast Lane mode: swap the shown wait to the Fast Lane number */}
|
||||
{fastLaneMode ? (
|
||||
ride.isOpen && fastLaneActive ? (
|
||||
<span style={{
|
||||
fontSize: "0.72rem",
|
||||
color: "var(--color-accent)",
|
||||
fontWeight: 600,
|
||||
flexShrink: 0,
|
||||
whiteSpace: "nowrap",
|
||||
}}>
|
||||
{flWait > 0 ? `⚡ ${flWait} min` : "⚡ walk-on"}
|
||||
</span>
|
||||
) : ride.isOpen ? (
|
||||
<span style={{
|
||||
fontSize: "0.68rem",
|
||||
color: "var(--color-text-muted)",
|
||||
fontWeight: 500,
|
||||
flexShrink: 0,
|
||||
opacity: 0.7,
|
||||
whiteSpace: "nowrap",
|
||||
}}>
|
||||
no Fast Lane
|
||||
</span>
|
||||
) : null
|
||||
) : (
|
||||
<>
|
||||
{showWait && (
|
||||
<span style={{
|
||||
fontSize: "0.72rem",
|
||||
color: "var(--color-open-hours)",
|
||||
fontWeight: 600,
|
||||
flexShrink: 0,
|
||||
whiteSpace: "nowrap",
|
||||
}}>
|
||||
{ride.waitMinutes} min
|
||||
</span>
|
||||
)}
|
||||
{ride.isOpen && !showWait && (
|
||||
<span style={{
|
||||
fontSize: "0.68rem",
|
||||
color: "var(--color-open-text)",
|
||||
fontWeight: 500,
|
||||
flexShrink: 0,
|
||||
opacity: 0.7,
|
||||
}}>
|
||||
walk-on
|
||||
</span>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user