From efd9672368165e7194712eb1b304e9943b473438 Mon Sep 17 00:00:00 2001
From: navin10sharma <3096611+navin10sharma@users.noreply.github.com>
Date: Sat, 26 Sep 2026 17:29:36 +0530
Subject: [PATCH 1/3] Hosted examples: the one-tag embed as HTML and React
examples/hosted/ has copy-paste pages for one event, a collection of dates,
a season card and a run-of-shows card. src/hosted/ has React components that
render the same tags (SeatLayerEvent, SeatLayerCollection, BookingCard) and
load each script once, scanning again after a client-side route change.
---
examples/hosted/collection.html | 21 +++++++++++++
examples/hosted/event.html | 26 ++++++++++++++++
examples/hosted/performance-card.html | 18 +++++++++++
examples/hosted/season-card.html | 20 ++++++++++++
src/hosted/BookingCard.tsx | 31 +++++++++++++++++++
src/hosted/SeatLayerCollection.tsx | 37 ++++++++++++++++++++++
src/hosted/SeatLayerEvent.tsx | 44 +++++++++++++++++++++++++++
src/hosted/loadScript.ts | 18 +++++++++++
8 files changed, 215 insertions(+)
create mode 100644 examples/hosted/collection.html
create mode 100644 examples/hosted/event.html
create mode 100644 examples/hosted/performance-card.html
create mode 100644 examples/hosted/season-card.html
create mode 100644 src/hosted/BookingCard.tsx
create mode 100644 src/hosted/SeatLayerCollection.tsx
create mode 100644 src/hosted/SeatLayerEvent.tsx
create mode 100644 src/hosted/loadScript.ts
diff --git a/examples/hosted/collection.html b/examples/hosted/collection.html
new file mode 100644
index 0000000..8c11bcc
--- /dev/null
+++ b/examples/hosted/collection.html
@@ -0,0 +1,21 @@
+
+
+
+
+
+ What's on
+
+
+
+
+
+
+
diff --git a/examples/hosted/event.html b/examples/hosted/event.html
new file mode 100644
index 0000000..fc0251f
--- /dev/null
+++ b/examples/hosted/event.html
@@ -0,0 +1,26 @@
+
+
+
+
+
+ Tickets
+
+
+
+
+
+
+
diff --git a/examples/hosted/performance-card.html b/examples/hosted/performance-card.html
new file mode 100644
index 0000000..954feb9
--- /dev/null
+++ b/examples/hosted/performance-card.html
@@ -0,0 +1,18 @@
+
+
+
+
+
+ Performances
+
+
+
+
+
+
diff --git a/examples/hosted/season-card.html b/examples/hosted/season-card.html
new file mode 100644
index 0000000..cb3e2e9
--- /dev/null
+++ b/examples/hosted/season-card.html
@@ -0,0 +1,20 @@
+
+
+
+
+
+ Season tickets
+
+
+
+
+
+
+
diff --git a/src/hosted/BookingCard.tsx b/src/hosted/BookingCard.tsx
new file mode 100644
index 0000000..ff2b1f4
--- /dev/null
+++ b/src/hosted/BookingCard.tsx
@@ -0,0 +1,31 @@
+interface BookingCardProps {
+ /** The booking link from your dashboard (a season or a run of shows). */
+ href: string;
+ title: string;
+ /** For example "3 performances: 6 Nov, 20 Nov and 4 Dec 2026". */
+ dates?: string;
+ /** For example "€120". */
+ fromPrice?: string;
+ /** For example "Book season tickets" or "Choose a performance". */
+ buttonLabel: string;
+ className?: string;
+}
+
+/**
+ * A plain link card for season tickets or a run of shows. No script runs: the
+ * button opens the SeatLayer booking page, which handles seats, payment and
+ * tickets. Style it with your own CSS.
+ * Try it live: https://seatlayer.io/demo/hosted/season-ticket/
+ */
+export function BookingCard({ href, title, dates, fromPrice, buttonLabel, className = "seatlayer-season-card" }: BookingCardProps) {
+ return (
+
+
{title}
+ {dates ?
{dates}
: null}
+ {fromPrice ?
From {fromPrice}
: null}
+
+ {buttonLabel}
+
+
+ );
+}
diff --git a/src/hosted/SeatLayerCollection.tsx b/src/hosted/SeatLayerCollection.tsx
new file mode 100644
index 0000000..e07de14
--- /dev/null
+++ b/src/hosted/SeatLayerCollection.tsx
@@ -0,0 +1,37 @@
+"use client";
+
+import { useEffect } from "react";
+import { loadSeatLayerScript } from "./loadScript";
+
+const COLLECTION_SRC = "https://app.seatlayer.io/sl-event-collection@0.js";
+
+interface SeatLayerCollectionProps {
+ /** Your workspace id (ws_...), from the dashboard. */
+ workspaceId: string;
+ layout?: "grid" | "list" | "calendar";
+ /** Where buyers go if the script cannot load. */
+ fallbackUrl: string;
+}
+
+/**
+ * Every upcoming public event in one workspace, listed on your page. Buyers
+ * pick a date, then their seats. Only events you list publicly appear.
+ * Try it live: https://seatlayer.io/demo/hosted/multi-date/
+ */
+export function SeatLayerCollection({ workspaceId, layout = "grid", fallbackUrl }: SeatLayerCollectionProps) {
+ useEffect(() => {
+ loadSeatLayerScript(COLLECTION_SRC, "SeatLayerCollections");
+ }, [workspaceId]);
+
+ return (
+
+ );
+}
diff --git a/src/hosted/SeatLayerEvent.tsx b/src/hosted/SeatLayerEvent.tsx
new file mode 100644
index 0000000..6963b22
--- /dev/null
+++ b/src/hosted/SeatLayerEvent.tsx
@@ -0,0 +1,44 @@
+"use client";
+
+import { useEffect } from "react";
+import { loadSeatLayerScript } from "./loadScript";
+
+const WIDGET_SRC = "https://app.seatlayer.io/sl-event-widget@0.js";
+
+interface SeatLayerEventProps {
+ /** Your event key, from the event's page in the dashboard. */
+ eventKey: string;
+ /**
+ * "picker" puts the seat map on your page. "card", "banner" and "button"
+ * show a smaller block that opens the seat map when clicked.
+ */
+ layout?: "picker" | "card" | "banner" | "button";
+ /** CSS height of the seat map when layout is "picker". */
+ height?: string;
+ /** Where buyers go if the script cannot load: your event's booking page. */
+ fallbackUrl: string;
+}
+
+/**
+ * Hosted Ticketing in React: the same one tag as the HTML version. SeatLayer
+ * runs the seat map, the payment through your own gateway and the tickets.
+ * Try it live: https://seatlayer.io/demo/hosted/paid-event/
+ */
+export function SeatLayerEvent({ eventKey, layout = "picker", height = "740px", fallbackUrl }: SeatLayerEventProps) {
+ useEffect(() => {
+ loadSeatLayerScript(WIDGET_SRC, "SeatLayerEvents");
+ }, [eventKey]);
+
+ return (
+
+ );
+}
diff --git a/src/hosted/loadScript.ts b/src/hosted/loadScript.ts
new file mode 100644
index 0000000..a4f9ec8
--- /dev/null
+++ b/src/hosted/loadScript.ts
@@ -0,0 +1,18 @@
+/**
+ * Loads a SeatLayer embed script once per page. When the script is already on
+ * the page (a second embed, or a client-side route change), it asks the
+ * runtime to scan again so the new element is rendered too.
+ */
+export function loadSeatLayerScript(src: string, runtime: "SeatLayerEvents" | "SeatLayerCollections") {
+ if (typeof window === "undefined") return;
+ const loaded = (window as unknown as Record void } | undefined>)[runtime];
+ if (loaded?.scan) {
+ loaded.scan();
+ return;
+ }
+ if (document.querySelector(`script[src="${src}"]`)) return;
+ const script = document.createElement("script");
+ script.src = src;
+ script.defer = true;
+ document.body.appendChild(script);
+}
From 9cc9cc8e54ec3ca977af28ea54e24800244a90f4 Mon Sep 17 00:00:00 2001
From: navin10sharma <3096611+navin10sharma@users.noreply.github.com>
Date: Sat, 26 Sep 2026 17:29:36 +0530
Subject: [PATCH 2/3] Remove the Workers hosting config
wrangler.jsonc only served our own copy of the demos. This repository is
example code: npm run build writes a static site any host can serve.
---
wrangler.jsonc | 20 --------------------
1 file changed, 20 deletions(-)
delete mode 100644 wrangler.jsonc
diff --git a/wrangler.jsonc b/wrangler.jsonc
deleted file mode 100644
index 8e99ff3..0000000
--- a/wrangler.jsonc
+++ /dev/null
@@ -1,20 +0,0 @@
-{
- // This example on Cloudflare Workers static assets.
- // Build first (npm run build), then deploy with: npx wrangler deploy
- // That deploys your own copy under the name below, with no custom domain.
- // The "hosted" environment is the SeatLayer copy on
- // examples-react.seatlayer.io; you do not need it.
- "$schema": "node_modules/wrangler/config-schema.json",
- "name": "seatlayer-react-example",
- "compatibility_date": "2026-09-01",
- "assets": {
- "directory": "./dist",
- "not_found_handling": "single-page-application"
- },
- "env": {
- "hosted": {
- "name": "seatlayer-examples-react",
- "routes": [{ "pattern": "examples-react.seatlayer.io", "custom_domain": true }]
- }
- }
-}
From d05b8b2956096aa97aa946c6f8ccc56ee6f9d645 Mon Sep 17 00:00:00 2001
From: navin10sharma <3096611+navin10sharma@users.noreply.github.com>
Date: Sat, 26 Sep 2026 17:29:36 +0530
Subject: [PATCH 3/3] README for customers: live demos first, Hosted then SDK
Starts with the live demos at seatlayer.io/demo, then the one-tag Hosted
integration, the SDK routes, where to find each key in the dashboard, and how
to run the app locally.
---
.env.example | 4 +-
README.md | 375 ++++++++++++++++++---------------------------------
2 files changed, 135 insertions(+), 244 deletions(-)
diff --git a/.env.example b/.env.example
index e0b127f..75cb8c0 100644
--- a/.env.example
+++ b/.env.example
@@ -1,7 +1,7 @@
-# Event key for the event you want to sell, for example ev_9f3a.
+# Event key: the top of the event's page in the dashboard. It is not the pk_ key.
VITE_SEATLAYER_EVENT_KEY=ev_replace_me
-# Publishable key for the same account. It is safe in browser code.
+# Publishable key: Dashboard → Developer. It is safe in browser code.
# Never put a secret sk_ key in a front end bundle.
VITE_SEATLAYER_PUBLIC_KEY=pk_test_replace_me
diff --git a/README.md b/README.md
index 2dda8b0..29458dd 100644
--- a/README.md
+++ b/README.md
@@ -1,290 +1,181 @@
-# Interactive seat map and seating chart examples for React (SeatLayer SDK)
+Try it live: https://seatlayer.io/demo
-Open source (MIT) example code showing how to embed an interactive seat map in a
-React ticketing app with seat selection, seat holds so two buyers cannot take one
-seat, best available seats, season tickets and a checkout handoff to your own
-payment gateway. Every example is a route in one small Vite and React
-application, and each one names the question it answers and links the matching
-documentation page.
+# SeatLayer examples for React: sell reserved seats with one tag, or build the seat map into your app
-[](https://vercel.com/new/clone?repository-url=https://github.com/seatlayer/seatlayer-react-example)
-[](https://app.netlify.com/start/deploy?repository=https://github.com/seatlayer/seatlayer-react-example)
+Open source (MIT) example code for the two ways to sell reserved seats with
+SeatLayer:
-## Run it
+- **Hosted**: paste one tag into any website. SeatLayer runs the seat map, the
+ payment through your own gateway, and the tickets.
+- **SDK**: put the seat map inside your own React app. SeatLayer holds the
+ seats, and your own checkout takes the payment.
-```sh
-npm install
-cp .env.example .env.local # add your event key and publishable key
-npm run dev
-```
-
-`.env.local` needs two values from your SeatLayer account:
-
-| Variable | What it is |
-| --- | --- |
-| `VITE_SEATLAYER_EVENT_KEY` | The event you want to sell, for example `ev_9f3a` |
-| `VITE_SEATLAYER_PUBLIC_KEY` | The publishable `pk_` key for the same account |
-
-Three more are optional: `VITE_SEATLAYER_EVENT_KEY_2` and
-`VITE_SEATLAYER_EVENT_KEY_3` give the multiple events route real variety, and
-`VITE_SEATLAYER_SEASON_KEY` turns on the season route. Every route shows a setup
-notice instead of a chart until its keys are present, so the repository runs the
-moment it is cloned.
+This repository is a small Vite and React app. A Next.js version with the same
+demos is at [seatlayer-nextjs-example](https://github.com/seatlayer/seatlayer-nextjs-example).
-Test mode is free, so you can run every step above before a live account exists.
-Register `http://localhost:5173` as an embed origin for the key, otherwise the
-chart will refuse to bootstrap. Never put a secret `sk_` key in browser code.
+## Hosted: one tag
-## Examples in this repo
-
-| Route | What it shows | Documentation |
-| --- | --- | --- |
-| `/` | The headless `SeatingChart` with your own cart, totals and hold button | [React seating chart](https://docs.seatlayer.io/buyer-sdk/react-seating-chart/) |
-| `/seat-picker` | The complete `SeatPicker` buyer flow and its `onCheckout` handoff | [SeatPicker reference](https://docs.seatlayer.io/buyer-sdk/seat-picker/) |
-| `/season` | `SeasonPicker` fixed-inclusion selection and returning-holder renewal intent | [Season picker](https://docs.seatlayer.io/buyer-sdk/seasons/) |
-| `/events` | An event list and one chart that swaps event, releasing any open hold first | [Events API](https://docs.seatlayer.io/server-api/events/) |
-| `/best-available` | A quantity chooser and optional category filter over `bestAvailable()` | [Best available seats](https://docs.seatlayer.io/buyer-sdk/best-available/) |
-| `/control-room` | `SeatManager` in read-only view mode behind an event-scoped manage token | [Embedded Control Room](https://docs.seatlayer.io/platform/embedded-control-room/) |
-
-## Developer questions this repo answers
-
-### How do I embed an interactive seating chart in React?
+The whole integration is one block of HTML. Replace `` with the
+key from your event's page in the dashboard.
-Install `@seatlayer/react` and render the `SeatingChart` component with your
-event key and publishable key. It creates the canvas once, keeps live
-availability up to date, and reports selection through `onSelectionChange`. Your
-own components own the cart, the totals and the buttons, so the interactive seat
-map never dictates your layout. Give the wrapping element a definite height,
-because the chart fills its box. See `src/routes/SingleEventRoute.tsx` and
-`src/components/SeatMap.tsx`.
-
-### How do I let buyers pick seats and hold them so two people cannot buy the same seat?
-
-A hold is a short, revocable claim on inventory, and every event processes holds
-through one serialized writer, so two competing buyers cannot both take the same
-seat. Call `hold()` on the chart handle, or let `SeatPicker` do it from its own
-checkout button. The result carries an opaque `holdId` and an absolute
-`expiresAt`, which is what the hold timer counts down from. When the seats were
-taken in between, the call resolves to null rather than throwing, so you can ask
-the buyer to choose again. See `src/lib/useSeatSelection.ts`,
-`src/components/HoldCountdown.tsx` and `src/routes/SeatPickerRoute.tsx`.
-
-### How do I sell season tickets or a multi-event package with seat selection?
-
-Use `SeasonPicker` with a published season key. The buyer chooses one seat
-package and keeps those exact seats for every performance in the plan, so
-availability is the intersection across the whole plan and the hold is all or
-nothing. The handoff it produces carries an operation id and no amount at all,
-because your server prices the package. A returning holder renews from an offer
-your server issued, and `createRenewalIntent(offerId)` records that intent
-without confirming a price or taking payment. See `src/routes/SeasonRoute.tsx`.
-
-### How do I show several events on one page and switch charts?
-
-Keep a list of event keys and render one chart for the selected one. Changing
-the `event` prop rebuilds the canvas, because a different event is different
-inventory. Release any open hold before switching, otherwise those seats stay
-off the market on the previous event until they expire on their own. The list in
-this repository is static configuration; a real catalogue is read on your server
-with a secret key and sent to the browser as plain data. See
-`src/routes/EventsRoute.tsx` and `src/lib/events.ts`.
+```html
+
+
+```
-### How do I offer best available seats for a group?
+It works on WordPress, Wix, Squarespace, Webflow and any HTML page. Copy-paste
+versions are in [`examples/hosted/`](./examples/hosted), and React components
+that render the same tags are in [`src/hosted/`](./src/hosted):
-Call `bestAvailable(quantity, categoryKey?)` on the chart handle. The seat map
-API asks the server to find an adjacent block and hold it in the same call, then
-fires `onSelectionChange` and `onHold` exactly as a manual pick would. It
-resolves to null when no block of that size fits, which is a different answer
-from sold out and deserves different wording. The buyer SDK exposes the category
-on each selected seat rather than a list of the chart's categories, so the
-optional filter takes the stable category key from your published chart. See
-`src/routes/BestAvailableRoute.tsx`.
+| What you want | HTML | React | Live demo |
+| --- | --- | --- | --- |
+| A seat map for one event | [`event.html`](./examples/hosted/event.html) | [`SeatLayerEvent`](./src/hosted/SeatLayerEvent.tsx) | [Paid event](https://seatlayer.io/demo/hosted/paid-event/), [Free event](https://seatlayer.io/demo/hosted/free-event/), [Standing + tables](https://seatlayer.io/demo/hosted/standing-and-tables/), [3D view](https://seatlayer.io/demo/hosted/3d-view/) |
+| Every upcoming date, then seats | [`collection.html`](./examples/hosted/collection.html) | [`SeatLayerCollection`](./src/hosted/SeatLayerCollection.tsx) | [Multi-date](https://seatlayer.io/demo/hosted/multi-date/) |
+| A season ticket | [`season-card.html`](./examples/hosted/season-card.html) | [`BookingCard`](./src/hosted/BookingCard.tsx) | [Season ticket](https://seatlayer.io/demo/hosted/season-ticket/) |
+| A run of shows | [`performance-card.html`](./examples/hosted/performance-card.html) | [`BookingCard`](./src/hosted/BookingCard.tsx) | [Performance groups](https://seatlayer.io/demo/hosted/performance-groups/) |
-### How do I hand a seat hold to my own checkout and payment gateway?
-
-The browser sends one thing to your backend: the opaque hold id. Your server
-reads the hold back from SeatLayer with your secret key, which is where the
-authoritative seats and prices come from, creates your own checkout session for
-that amount with your own payment gateway, and books the hold once payment has
-succeeded. Pass your own order id as the booking reference; repeating the same
-request with the same reference cannot create a second sale, so an unknown
-outcome is reconciled by repeating it rather than by generating a new reference.
-In this repository the browser half is `src/components/CheckoutBar.tsx`. Read
-[holds and checkout handoff](https://docs.seatlayer.io/buyer-sdk/holds-and-checkout/)
-and [idempotency and conflicts](https://docs.seatlayer.io/server-api/idempotency-and-conflicts/).
-
-### How do I connect the seat hold to my own checkout or hosted payment page?
-
-Both shapes use the same handoff. For your own checkout pages, take the hold id,
-price it on your server, and charge through your own payment gateway before
-booking. For a hosted payment page, create the session on your server from the
-inspected hold, redirect the buyer to it, and book the hold when the payment
-result arrives, still under your own order reference. Either way the browser
-never carries an amount and never books anything. The companion Next.js
-repository has the trusted half as a working route handler at
-`app/api/hold/route.ts`; the flow is documented in
-[holds and checkout handoff](https://docs.seatlayer.io/buyer-sdk/holds-and-checkout/)
-and the [Node server SDK](https://docs.seatlayer.io/server-sdk/node/).
-
-### What runs in the browser and what must run on my server?
-
-The browser renders the seat map, selects seats and creates holds, using a
-publishable key that is safe to ship. It has no booking authority and no pricing
-authority. Your server holds the secret key and owns hold inspection, booking,
-event and chart management, and any short-lived token it mints for a scoped
-buyer or a member of staff. The organizer board is the clearest case: it accepts
-an event-scoped `mse_` grant your backend mints after authenticating staff, and
-it refuses a secret key outright. See `src/routes/ControlRoomRoute.tsx`.
-
-### How do I render the seat map with Vite?
-
-Nothing special is required: the chart is a normal React component and Vite
-builds it as it builds anything else. The SDK loads its locale bundles with a
-dynamic import, so they arrive as separate chunks rather than in your main
-bundle. Give the chart container a height in CSS, as `.seatmap` does in
-`src/styles.css`. Read your keys from `import.meta.env` and keep only the
-publishable ones there, as `src/lib/config.ts` does.
-
-### Is there an open source or free seat map library for React?
-
-The example code in this repository is open source under the MIT license, so you
-can copy any route into your own product. The SDK packages themselves are free
-to install from npm and free to run in test mode, which is enough to build and
-verify a complete integration before any account exists. Live usage is priced
-per confirmed sold seat rather than by subscription, and every organization gets
-a monthly free allowance; the current numbers are on the
-[pricing page](https://seatlayer.io/pricing/). The seat map engine itself is not
-published as open source, so treat this repository as the open part and the
-packages as the hosted part.
-
-### Does the seat map work for a 100,000-seat stadium?
-
-Yes, and large venues are the headline case rather than the edge case. There is
-a public benchmark demo of a 200,000-seat stadium you can open and drive
-yourself: [century-stadium-200k](https://app.seatlayer.io/demo/play/century-stadium-200k).
-The demos are synthetic fixtures, timings vary by device and by run, and the
-renderer evidence says nothing about concurrent buyers.
-
-### How do I render 100,000 seats in the browser without lag?
-
-You do not build it yourself: the renderer already does the work. Big charts
-arrive progressively, so the venue's sections are drawn as soon as the chart
-lands and seats fill in a section at a time, nearest the viewport first, and a
-`viewport` reveal mode exists for the very largest charts. Selection, search and
-availability still cover every seat. The method, the fixtures, the run log and
-the limits of the published timings are written up in
-[renderer performance](https://docs.seatlayer.io/platform/renderer-performance/).
+```tsx
+import { SeatLayerEvent } from "./hosted/SeatLayerEvent";
-### Can I use this with Vue or Angular?
+;
+```
-Yes. The same seating engine ships as `@seatlayer/vue` and `@seatlayer/angular`
-alongside `@seatlayer/react`, with the same options, the same handoff and the
-same server boundary, so the routes in this repository translate almost line for
-line. Read the [Vue guide](https://docs.seatlayer.io/buyer-sdk/vue/) and the
-[Angular guide](https://docs.seatlayer.io/buyer-sdk/angular/). There is also a
-framework-agnostic `@seatlayer/js` package if you are not using a framework at
-all.
+The React components load each script once per page and render again after a
+client-side route change.
-### How is this different from seats.io?
+## SDK: the seat map in your app
-seats.io is a seating chart SDK where you bring the ticketing system. SeatLayer
-is the seating chart SDK plus a complete seated-event ticketing stack, so you
-can embed the picker with your own checkout, or sell on your own website with
-your own payment gateway. A longer comparison is at
-[seatlayer.io/vs/seats-io](https://seatlayer.io/vs/seats-io/).
+Each route in this app is one example. The same demos run live at
+[seatlayer.io/demo/sdk](https://seatlayer.io/demo/sdk).
-## Use in your app
+| Route | What it shows | Documentation |
+| --- | --- | --- |
+| `/` | The headless `SeatingChart` with your own cart, totals and hold button | [React seating chart](https://docs.seatlayer.io/buyer-sdk/react-seating-chart/) |
+| `/seat-picker` | The complete `SeatPicker` buyer flow and its `onCheckout` handoff | [SeatPicker reference](https://docs.seatlayer.io/buyer-sdk/seat-picker/) |
+| `/season` | `SeasonPicker`: one seat choice held for every performance in a season | [Season picker](https://docs.seatlayer.io/buyer-sdk/seasons/) |
+| `/events` | Several events on one page with one chart, releasing any open hold first | [Events API](https://docs.seatlayer.io/server-api/events/) |
+| `/best-available` | `bestAvailable()`: the best seats together for a party size | [Best available seats](https://docs.seatlayer.io/buyer-sdk/best-available/) |
+| `/control-room` | `SeatManager`, the staff board, behind an event-scoped `mse_` token | [Embedded Control Room](https://docs.seatlayer.io/platform/embedded-control-room/) |
```sh
npm install @seatlayer/react
```
```tsx
-import { SeatingChart } from "@seatlayer/react";
+import { SeatPicker } from "@seatlayer/react";
- startCheckout({ holdId: handoff.holdId })}
/>;
```
-Then call `chartRef.current.hold()` to hold the selection, and send the
-resulting `holdId` to your server. Prefer the ready-made `SeatPicker` component
-when you want SeatLayer's complete buyer flow including its own tray and
-countdown.
+Send only the `holdId` to your server. Your server reads the seats and prices
+back from SeatLayer with your secret key, charges through your own payment
+gateway, and books the hold with your own order id as the booking reference.
+This app has no server, so it stops at the handoff. The Next.js example shows
+the server half in `app/api/hold/route.ts`.
-## Documentation
+## Where to find your keys
-- [Install the Buyer SDK](https://docs.seatlayer.io/buyer-sdk/install/)
-- [Add a seat map to a React app](https://docs.seatlayer.io/buyer-sdk/react-seating-chart/)
-- [SeatPicker reference](https://docs.seatlayer.io/buyer-sdk/seat-picker/)
-- [Season picker](https://docs.seatlayer.io/buyer-sdk/seasons/)
-- [Holds and checkout handoff](https://docs.seatlayer.io/buyer-sdk/holds-and-checkout/)
-- [Best available seats](https://docs.seatlayer.io/buyer-sdk/best-available/)
-- [Vue](https://docs.seatlayer.io/buyer-sdk/vue/) and [Angular](https://docs.seatlayer.io/buyer-sdk/angular/)
-- [Server SDKs](https://docs.seatlayer.io/server-sdk/)
-- [seatlayer-sdk on GitHub](https://github.com/seatlayer/seatlayer-sdk)
+Sign in at [app.seatlayer.io](https://app.seatlayer.io). Test mode is free and
+needs no card.
-## Hosting on Cloudflare
+| Key | Where | Looks like | Used for |
+| --- | --- | --- | --- |
+| Publishable key | Dashboard → Developer | `pk_test_...` | Browser code (SDK) |
+| Secret key | Dashboard → Developer | `sk_test_...` | Your server only. Never in browser code |
+| Event key | The top of the event's page | `ev_...` or your own slug | Hosted tag and SDK |
+| Season key | The season's Sell tab, in its booking link | `sea_...` | Season tickets |
+| Workspace id | In your dashboard address (`/w/ws_.../`) and in the multiple events code on an event's publish page | `ws_...` | The hosted collection |
-The build output in `dist` is a static site, served on Cloudflare Workers as
-static assets. `wrangler.jsonc` sets it up:
+The event key is not the `pk_` key. If the seat map says the event was not
+found, check that you pasted the event key into the event field.
+
+## Run it locally
```sh
-npm run build
-npx wrangler deploy
+npm install
+cp .env.example .env.local # add your keys
+npm run dev
```
-Set the `VITE_` variables before `npm run build`, because Vite writes them into
-the bundle at build time.
+Then open http://localhost:5173.
-The routes are handled in the browser by React Router. `wrangler.jsonc` sets
-`not_found_handling` to `single-page-application`, so a deep link such as
-`/seat-picker` serves `index.html` and the app takes it from there.
+| Variable | Needed for |
+| --- | --- |
+| `VITE_SEATLAYER_EVENT_KEY` | Every SDK route |
+| `VITE_SEATLAYER_PUBLIC_KEY` | Every SDK route |
+| `VITE_SEATLAYER_EVENT_KEY_2`, `_3` | More events on `/events` (optional) |
+| `VITE_SEATLAYER_SEASON_KEY` | `/season` |
+| `VITE_SEATLAYER_CURRENCY` | Prices before a hold, for example `EUR` (defaults to `USD`) |
-`npx wrangler deploy` creates your own Worker named `seatlayer-react-example`,
-with no custom domain. Rename it in `wrangler.jsonc` if you like.
+Every route shows a setup notice instead of a chart until its keys are present,
+so the app runs the moment it is cloned. `npm run build` writes a static site to
+`dist/`. Serve it from any static host with a fallback to `index.html`, because
+the routes are handled in the browser.
-### Hosting our copy
+## Questions this repo answers
-The live demo at examples-react.seatlayer.io is this repository deployed with
-the `hosted` environment in `wrangler.jsonc`, which sets the Worker name
-`seatlayer-examples-react` and the custom domain:
+### How do I embed an interactive seating chart in React?
-```sh
-VITE_SEATLAYER_PUBLIC_KEY=pk_test_e5933c35b56d4f9997f89d50edca3aa3e610c6ff92c8480f \
-VITE_SEATLAYER_EVENT_KEY=ev_dd43d9250d37406a8d630158d57f1db6 \
-VITE_SEATLAYER_EVENT_KEY_2=ev_38327c4f7d8c433281fa330033f6376b \
-VITE_SEATLAYER_EVENT_KEY_3=ev_73621397e3c64d398d24e1b84f08c2d3 \
-VITE_SEATLAYER_SEASON_KEY=sea_030bee1677b247c1957c0f372d92d613 \
-VITE_SEATLAYER_CURRENCY=EUR \
-npm run build
-```
+Render `SeatPicker` or `SeatingChart` from `@seatlayer/react`. Give the wrapping
+element a definite height, because the chart fills its box. See
+`src/routes/SingleEventRoute.tsx` and `src/components/SeatMap.tsx`.
-```sh
-npx wrangler deploy --env hosted
-```
+### How do I stop two buyers taking the same seat?
+
+Hold the seats. A hold is a short claim on inventory, and each event processes
+holds one at a time, so two buyers cannot both hold one seat. `hold()` resolves
+to null when the seats were taken in between, so you can ask the buyer to
+choose again. See `src/lib/useSeatSelection.ts` and
+`src/components/HoldCountdown.tsx`.
+
+### How do I sell season tickets?
-These are the public demo keys (a test event, safe in browser code). Vite writes
-the `VITE_` values into the build, so they must be set when you run
-`npm run build`.
+Use `SeasonPicker` with a published season key. The buyer keeps the same seats
+for every performance, and the hold covers all of them or none. The handoff
+carries no price: your server prices the package. See
+`src/routes/SeasonRoute.tsx`.
-You do not need this environment for your own copy.
+### How do I offer best available seats for a group?
+
+Call `bestAvailable(quantity, categoryKey?)` on the chart handle. It finds the
+best block of seats together and holds it in the same call, or resolves to null
+when no block of that size is free, which is not the same as sold out. See
+`src/routes/BestAvailableRoute.tsx`.
+
+### Can I use this with Vue or Angular?
-Add `?embed=1` to any route to hide the navigation and the route heading, so
-the example sits cleanly in an iframe.
+Yes. The same engine ships as `@seatlayer/vue`, `@seatlayer/angular` and the
+framework-free `@seatlayer/js`. See the
+[Vue](https://docs.seatlayer.io/buyer-sdk/vue/) and
+[Angular](https://docs.seatlayer.io/buyer-sdk/angular/) guides.
-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.
+## Documentation
+
+- [Install the Buyer SDK](https://docs.seatlayer.io/buyer-sdk/install/)
+- [Add a seat map to a React app](https://docs.seatlayer.io/buyer-sdk/react-seating-chart/)
+- [SeatPicker reference](https://docs.seatlayer.io/buyer-sdk/seat-picker/)
+- [Season picker](https://docs.seatlayer.io/buyer-sdk/seasons/)
+- [Holds and checkout handoff](https://docs.seatlayer.io/buyer-sdk/holds-and-checkout/)
+- [Best available seats](https://docs.seatlayer.io/buyer-sdk/best-available/)
+- [Server SDKs](https://docs.seatlayer.io/server-sdk/)
+- [Pricing](https://seatlayer.io/pricing/)
## License
MIT. See [LICENSE](./LICENSE).
-
-Keywords: interactive seat map, seating chart, seat picker, seat selection, seat holds, best available seats, season tickets, reserved seating, event ticketing, React example