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 + + + +
+ View tickets on SeatLayer +
+ + + 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 + + + +
+ Book tickets on SeatLayer +
+ + + 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 + + + +
+ Your show: choose a performance +
+ + 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 + + + + +
+ Your season: book 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 ( +
+ + View tickets on SeatLayer + +
+ ); +} 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 ( +
+ Book tickets on SeatLayer +
+ ); +} 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 -[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/seatlayer/seatlayer-react-example) -[![Deploy to Netlify](https://www.netlify.com/img/deploy/button.svg)](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 +
+ Book tickets on SeatLayer +
+ +``` -### 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