From 224ce0a756f9c70bada5dad7509863a613f80045 Mon Sep 17 00:00:00 2001
From: navin10sharma <3096611+navin10sharma@users.noreply.github.com>
Date: Sat, 26 Sep 2026 08:24:52 +0530
Subject: [PATCH 1/3] Hide the navigation and route heading with ?embed=1
---
src/App.tsx | 35 +++++++++++++++++++++--------------
src/styles.css | 10 ++++++++++
2 files changed, 31 insertions(+), 14 deletions(-)
diff --git a/src/App.tsx b/src/App.tsx
index 4d3668d..9de3f38 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,4 +1,4 @@
-import { NavLink, Outlet } from "react-router-dom";
+import { NavLink, Outlet, useSearchParams } from "react-router-dom";
const routes = [
{ to: "/", label: "Single event", end: true },
@@ -10,20 +10,27 @@ const routes = [
];
export default function App() {
+ // Add ?embed=1 to any route to show only the example itself, without the
+ // navigation and the route heading, for use inside an iframe.
+ const [searchParams] = useSearchParams();
+ const embed = searchParams.get("embed") === "1";
+
return (
-
-
+
+ {embed ? null : (
+
+ )}
diff --git a/src/styles.css b/src/styles.css
index 31dc594..d44f88f 100644
--- a/src/styles.css
+++ b/src/styles.css
@@ -32,6 +32,16 @@ body {
padding: 24px 16px 48px;
}
+/* ?embed=1 drops the navigation and the route heading. See src/App.tsx. */
+.page.embed {
+ max-width: none;
+ padding: 0;
+}
+
+.page.embed .intro {
+ display: none;
+}
+
h1 {
font-size: 1.6rem;
margin: 0 0 4px;
From 6d3e74fe180274ea4a00bb31d9a809fb6f8f9320 Mon Sep 17 00:00:00 2001
From: navin10sharma <3096611+navin10sharma@users.noreply.github.com>
Date: Sat, 26 Sep 2026 08:24:52 +0530
Subject: [PATCH 2/3] Fall back to the main event key when an optional one is
empty
---
src/lib/events.ts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/lib/events.ts b/src/lib/events.ts
index 9822bf9..71e9457 100644
--- a/src/lib/events.ts
+++ b/src/lib/events.ts
@@ -15,6 +15,6 @@ export interface EventOption {
export const events: EventOption[] = [
{ key: eventKey, name: "Opening night" },
- { key: import.meta.env.VITE_SEATLAYER_EVENT_KEY_2 ?? eventKey, name: "Saturday matinee" },
- { key: import.meta.env.VITE_SEATLAYER_EVENT_KEY_3 ?? eventKey, name: "Closing night" },
+ { key: import.meta.env.VITE_SEATLAYER_EVENT_KEY_2 || eventKey, name: "Saturday matinee" },
+ { key: import.meta.env.VITE_SEATLAYER_EVENT_KEY_3 || eventKey, name: "Closing night" },
];
From b3ff4c017cc269b09893c6866b145503a6fd3463 Mon Sep 17 00:00:00 2001
From: navin10sharma <3096611+navin10sharma@users.noreply.github.com>
Date: Sat, 26 Sep 2026 08:24:52 +0530
Subject: [PATCH 3/3] Load every route in CI when demo keys are present, and
document Pages hosting
---
.github/workflows/ci.yml | 19 +++++++++++++++
README.md | 20 ++++++++++++++++
scripts/check-routes.mjs | 52 ++++++++++++++++++++++++++++++++++++++++
3 files changed, 91 insertions(+)
create mode 100644 scripts/check-routes.mjs
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index dc731a6..a766178 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -7,6 +7,12 @@ on:
jobs:
build:
runs-on: ubuntu-latest
+ env:
+ # Repository secrets for a test event. Forks do not have them, so there
+ # the job only builds and the route checks are skipped.
+ VITE_SEATLAYER_EVENT_KEY: ${{ secrets.DEMO_EVENT_KEY }}
+ VITE_SEATLAYER_PUBLIC_KEY: ${{ secrets.DEMO_PUBLIC_KEY }}
+ VITE_SEATLAYER_SEASON_KEY: ${{ secrets.DEMO_SEASON_KEY }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
@@ -14,3 +20,16 @@ jobs:
node-version: 20
- run: npm ci
- run: npm run build
+
+ - name: Install the route check browser
+ if: env.VITE_SEATLAYER_EVENT_KEY != '' && env.VITE_SEATLAYER_PUBLIC_KEY != ''
+ run: |
+ npm install --no-save playwright@1.63.0
+ npx playwright install --with-deps chromium
+
+ - name: Load every route
+ if: env.VITE_SEATLAYER_EVENT_KEY != '' && env.VITE_SEATLAYER_PUBLIC_KEY != ''
+ run: |
+ npm run preview -- --port 4173 --strictPort &
+ for i in $(seq 1 30); do curl -sf http://localhost:4173 > /dev/null && break; sleep 2; done
+ node scripts/check-routes.mjs
diff --git a/README.md b/README.md
index 6d92eed..d3611d7 100644
--- a/README.md
+++ b/README.md
@@ -229,6 +229,26 @@ countdown.
- [Server SDKs](https://docs.seatlayer.io/server-sdk/)
- [seatlayer-sdk on GitHub](https://github.com/seatlayer/seatlayer-sdk)
+## Hosting on Cloudflare
+
+The build output in `dist` is a static site, so it runs on Cloudflare Pages as
+it is. Use `npm run build` as the build command and `dist` as the output
+directory. Set the `VITE_` variables as build variables, because Vite writes
+them into the bundle at build time.
+
+The routes are handled in the browser by React Router. Pages serves
+`index.html` for any path that is not a file when the site has no top level
+`404.html`, so deep links such as `/seat-picker` work without a `_redirects`
+file.
+
+Add `?embed=1` to any route to hide the navigation and the route heading, so
+the example sits cleanly in an iframe.
+
+CI builds every push. When the repository has `DEMO_EVENT_KEY` and
+`DEMO_PUBLIC_KEY` secrets (and optionally `DEMO_SEASON_KEY`), it also serves the
+build with `npm run preview` and loads every route with
+`scripts/check-routes.mjs`, failing on an HTTP error or a console error.
+
## License
MIT. See [LICENSE](./LICENSE).
diff --git a/scripts/check-routes.mjs b/scripts/check-routes.mjs
new file mode 100644
index 0000000..8d4315b
--- /dev/null
+++ b/scripts/check-routes.mjs
@@ -0,0 +1,52 @@
+/**
+ * Loads every route in a real browser and fails on an HTTP error, an uncaught
+ * exception or a console error. Each route is also loaded with ?embed=1, where
+ * the navigation must be hidden.
+ *
+ * BASE_URL=http://localhost:4173 node scripts/check-routes.mjs
+ *
+ * CI runs it against `npm run preview` when the demo keys are available.
+ */
+import { chromium } from "playwright";
+
+const baseUrl = process.env.BASE_URL ?? "http://localhost:4173";
+
+const routes = ["/", "/seat-picker", "/season", "/events", "/best-available", "/control-room"];
+
+const browser = await chromium.launch();
+const failures = [];
+
+for (const route of routes) {
+ for (const path of [route, `${route}?embed=1`]) {
+ const page = await browser.newPage();
+ const problems = [];
+ page.on("pageerror", (error) => problems.push(`uncaught: ${error.message}`));
+ page.on("console", (message) => {
+ if (message.type() === "error") problems.push(`console: ${message.text()}`);
+ });
+
+ const response = await page.goto(baseUrl + path, { waitUntil: "load" });
+ if (!response || !response.ok()) {
+ problems.push(`HTTP ${response ? response.status() : "no response"}`);
+ }
+
+ // Give the seat map time to fetch its chart and render.
+ await page.waitForTimeout(4000);
+
+ if (path.endsWith("?embed=1") && (await page.locator(".nav").isVisible())) {
+ problems.push("navigation is visible in embed mode");
+ }
+
+ console.log(`${problems.length ? "FAIL" : "ok "} ${path}`);
+ for (const problem of problems) console.log(` ${problem}`);
+ if (problems.length) failures.push(path);
+ await page.close();
+ }
+}
+
+await browser.close();
+
+if (failures.length) {
+ console.error(`\n${failures.length} route(s) failed.`);
+ process.exit(1);
+}