@bookitive/widget-js 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -38,6 +38,8 @@ interface CheckoutInput {
38
38
  gaAreaId?: string;
39
39
  quantity: number;
40
40
  }[];
41
+ /** Promo code to redeem (case-insensitive); validated server-side. */
42
+ discountCode?: string;
41
43
  captchaToken?: string;
42
44
  }
43
45
  interface CheckoutResult {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["// @bookitive/widget-js — a thin, typed loader for the embeddable Bookitive\n// seat-map widget, in the spirit of @stripe/stripe-js.\n//\n// The widget itself is a UI runtime (Svelte + the seat-map renderer + live\n// availability) served from a versioned CDN bundle — you should NOT bundle it\n// into your app. This package injects that <script> for you and hands back a\n// fully typed `Bookitive.mount()`, so hosts get type-safety and clean imports\n// while the runtime keeps auto-updating from the CDN.\n//\n// import { loadBookitive } from '@bookitive/widget-js';\n// const bk = await loadBookitive();\n// const controller = bk.mount(el, { org: 'purples', event: 'purples-2026', legend: true });\n// el.addEventListener('bookitive:selectionchanged', (e) => {\n// const { selection, totalCents } = (e as BookitiveCustomEvent<'bookitive:selectionchanged'>).detail;\n// });\n\n// --- mount surface -----------------------------------------------------------\n// SINGLE SOURCE OF TRUTH: these mirror the widget's public API in\n// packages/widget/types/index.d.ts. They're intentionally re-declared here (not\n// imported) so this published loader has no dependency on the private runtime\n// package — the @stripe/stripe-js pattern. Keep the two in sync when the mount\n// options / controller change.\n\nexport interface WidgetOptions {\n /** API origin; defaults to the production API. */\n baseUrl?: string;\n /** Organization slug. */\n org: string;\n /** Event slug. */\n event: string;\n /** Floor id to render first (from bookitive:ready → event.floors); default first. */\n floor?: string;\n /** Show seat code labels on the map. */\n showLabels?: boolean;\n /** Render a legend (ticket-type colours + seat statuses) above the map. */\n legend?: boolean;\n /** Show the \"Powered by Bookitive\" credit below the map (default true). */\n credit?: boolean;\n /** Origin serving the hosted checkout/captcha frames (RFC 0002); defaults\n * to the production events origin. Override for local dev. */\n checkoutOrigin?: string;\n}\n\nexport interface SelectionItem {\n seatId: string;\n code: string;\n /** Pretty localized label, e.g. \"Stůl A7 · Místo 3\". */\n label: string;\n ticketTypeId: string;\n ticketTypeName: string;\n priceCents: number;\n}\n\nexport interface CheckoutInput {\n customer: { name: string; email: string; phone?: string };\n /** Standing/GA quantities the host sells outside the map. */\n extraItems?: { ticketTypeId: string; gaAreaId?: string; quantity: number }[];\n captchaToken?: string;\n}\n\nexport interface CheckoutResult {\n reference?: string;\n status?: string;\n totalCents?: number;\n currency?: string;\n expiresAt?: string;\n payment?: { bankAccount?: string; variableSymbol?: string };\n}\n\n/** The handle mount() returns. */\nexport interface WidgetController {\n setFloor: (floorId: string) => void;\n getSelection: () => SelectionItem[];\n clearSelection: () => void;\n /**\n * Mounts the Bookitive captcha bridge (a small iframe on the Bookitive\n * origin) into el — put it where your form's captcha would sit. Once\n * mounted, checkout() routes the order through that frame, so the captcha\n * is solved and verified on the Bookitive origin: your domain never needs\n * any captcha configuration.\n */\n mountCaptcha: (el: HTMLElement) => void;\n checkout: (input: CheckoutInput) => Promise<CheckoutResult>;\n destroy: () => void;\n}\n\n/** The global the CDN runtime installs on `window`. */\nexport interface Bookitive {\n mount: (el: HTMLElement, options: WidgetOptions) => WidgetController;\n}\n\n// --- CustomEvents the widget dispatches (bubble from the mount element) -------\n\nexport interface BookitiveEventDetail {\n \"bookitive:ready\": { event: unknown };\n \"bookitive:selectionchanged\": {\n selection: SelectionItem[];\n totalCents: number;\n };\n \"bookitive:seatselected\": { seatId: string; code?: string };\n \"bookitive:seatdeselected\": {\n seatId: string;\n code?: string;\n reason?: \"taken\";\n };\n \"bookitive:error\": { message: string };\n}\nexport type BookitiveEventName = keyof BookitiveEventDetail;\nexport type BookitiveCustomEvent<K extends BookitiveEventName> = CustomEvent<\n BookitiveEventDetail[K]\n>;\n\n// --- loader ------------------------------------------------------------------\n\nexport interface LoadBookitiveOptions {\n /** Origin serving the widget bundle. Default: https://events.bookitive.com */\n scriptOrigin?: string;\n /** Major version of the widget runtime. Default: \"v1\". */\n version?: string;\n}\n\nconst SCRIPT_ATTR = \"data-bookitive-widget\";\nlet pending: Promise<Bookitive> | null = null;\n\nfunction existingGlobal(): Bookitive | null {\n if (typeof window === \"undefined\") return null;\n return (window as unknown as { Bookitive?: Bookitive }).Bookitive ?? null;\n}\n\n/**\n * Inject the Bookitive widget runtime from the CDN (once) and resolve to the\n * typed global. Safe to call repeatedly — subsequent calls reuse the same\n * in-flight promise / already-loaded global. Rejects during SSR (no `window`).\n */\nexport function loadBookitive(\n options: LoadBookitiveOptions = {},\n): Promise<Bookitive> {\n if (typeof window === \"undefined\" || typeof document === \"undefined\") {\n return Promise.reject(new Error(\"loadBookitive() must run in the browser\"));\n }\n const already = existingGlobal();\n if (already) return Promise.resolve(already);\n if (pending) return pending;\n\n const origin = (\n options.scriptOrigin ?? \"https://events.bookitive.com\"\n ).replace(/\\/+$/, \"\");\n const version = options.version ?? \"v1\";\n const src = `${origin}/widget/${version}.js`;\n\n pending = new Promise<Bookitive>((resolve, reject) => {\n // Clear the cached promise on any rejection so a transient failure can be\n // retried by a later loadBookitive() call (rather than caching the error).\n const settle = () => {\n const bk = existingGlobal();\n if (bk) return resolve(bk);\n pending = null;\n reject(\n new Error(\"Bookitive global missing after the widget script loaded\"),\n );\n };\n const fail = () => {\n pending = null;\n reject(new Error(`Failed to load the Bookitive widget from ${src}`));\n };\n\n // Reuse a script tag another loadBookitive() (or a manual embed) added.\n const existing = document.querySelector<HTMLScriptElement>(\n `script[${SCRIPT_ATTR}]`,\n );\n if (existing) {\n if (existingGlobal()) return settle();\n existing.addEventListener(\"load\", settle, { once: true });\n existing.addEventListener(\"error\", fail, { once: true });\n return;\n }\n\n const s = document.createElement(\"script\");\n s.src = src;\n s.async = true;\n s.setAttribute(SCRIPT_ATTR, \"\");\n s.addEventListener(\"load\", settle, { once: true });\n s.addEventListener(\"error\", fail, { once: true });\n document.head.appendChild(s);\n });\n return pending;\n}\n"],"mappings":";AAyHA,IAAM,cAAc;AACpB,IAAI,UAAqC;AAEzC,SAAS,iBAAmC;AAC1C,MAAI,OAAO,WAAW,YAAa,QAAO;AAC1C,SAAQ,OAAgD,aAAa;AACvE;AAOO,SAAS,cACd,UAAgC,CAAC,GACb;AACpB,MAAI,OAAO,WAAW,eAAe,OAAO,aAAa,aAAa;AACpE,WAAO,QAAQ,OAAO,IAAI,MAAM,yCAAyC,CAAC;AAAA,EAC5E;AACA,QAAM,UAAU,eAAe;AAC/B,MAAI,QAAS,QAAO,QAAQ,QAAQ,OAAO;AAC3C,MAAI,QAAS,QAAO;AAEpB,QAAM,UACJ,QAAQ,gBAAgB,gCACxB,QAAQ,QAAQ,EAAE;AACpB,QAAM,UAAU,QAAQ,WAAW;AACnC,QAAM,MAAM,GAAG,MAAM,WAAW,OAAO;AAEvC,YAAU,IAAI,QAAmB,CAAC,SAAS,WAAW;AAGpD,UAAM,SAAS,MAAM;AACnB,YAAM,KAAK,eAAe;AAC1B,UAAI,GAAI,QAAO,QAAQ,EAAE;AACzB,gBAAU;AACV;AAAA,QACE,IAAI,MAAM,yDAAyD;AAAA,MACrE;AAAA,IACF;AACA,UAAM,OAAO,MAAM;AACjB,gBAAU;AACV,aAAO,IAAI,MAAM,4CAA4C,GAAG,EAAE,CAAC;AAAA,IACrE;AAGA,UAAM,WAAW,SAAS;AAAA,MACxB,UAAU,WAAW;AAAA,IACvB;AACA,QAAI,UAAU;AACZ,UAAI,eAAe,EAAG,QAAO,OAAO;AACpC,eAAS,iBAAiB,QAAQ,QAAQ,EAAE,MAAM,KAAK,CAAC;AACxD,eAAS,iBAAiB,SAAS,MAAM,EAAE,MAAM,KAAK,CAAC;AACvD;AAAA,IACF;AAEA,UAAM,IAAI,SAAS,cAAc,QAAQ;AACzC,MAAE,MAAM;AACR,MAAE,QAAQ;AACV,MAAE,aAAa,aAAa,EAAE;AAC9B,MAAE,iBAAiB,QAAQ,QAAQ,EAAE,MAAM,KAAK,CAAC;AACjD,MAAE,iBAAiB,SAAS,MAAM,EAAE,MAAM,KAAK,CAAC;AAChD,aAAS,KAAK,YAAY,CAAC;AAAA,EAC7B,CAAC;AACD,SAAO;AACT;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["// @bookitive/widget-js — a thin, typed loader for the embeddable Bookitive\n// seat-map widget, in the spirit of @stripe/stripe-js.\n//\n// The widget itself is a UI runtime (Svelte + the seat-map renderer + live\n// availability) served from a versioned CDN bundle — you should NOT bundle it\n// into your app. This package injects that <script> for you and hands back a\n// fully typed `Bookitive.mount()`, so hosts get type-safety and clean imports\n// while the runtime keeps auto-updating from the CDN.\n//\n// import { loadBookitive } from '@bookitive/widget-js';\n// const bk = await loadBookitive();\n// const controller = bk.mount(el, { org: 'purples', event: 'purples-2026', legend: true });\n// el.addEventListener('bookitive:selectionchanged', (e) => {\n// const { selection, totalCents } = (e as BookitiveCustomEvent<'bookitive:selectionchanged'>).detail;\n// });\n\n// --- mount surface -----------------------------------------------------------\n// SINGLE SOURCE OF TRUTH: these mirror the widget's public API in\n// packages/widget/types/index.d.ts. They're intentionally re-declared here (not\n// imported) so this published loader has no dependency on the private runtime\n// package — the @stripe/stripe-js pattern. Keep the two in sync when the mount\n// options / controller change.\n\nexport interface WidgetOptions {\n /** API origin; defaults to the production API. */\n baseUrl?: string;\n /** Organization slug. */\n org: string;\n /** Event slug. */\n event: string;\n /** Floor id to render first (from bookitive:ready → event.floors); default first. */\n floor?: string;\n /** Show seat code labels on the map. */\n showLabels?: boolean;\n /** Render a legend (ticket-type colours + seat statuses) above the map. */\n legend?: boolean;\n /** Show the \"Powered by Bookitive\" credit below the map (default true). */\n credit?: boolean;\n /** Origin serving the hosted checkout/captcha frames (RFC 0002); defaults\n * to the production events origin. Override for local dev. */\n checkoutOrigin?: string;\n}\n\nexport interface SelectionItem {\n seatId: string;\n code: string;\n /** Pretty localized label, e.g. \"Stůl A7 · Místo 3\". */\n label: string;\n ticketTypeId: string;\n ticketTypeName: string;\n priceCents: number;\n}\n\nexport interface CheckoutInput {\n customer: { name: string; email: string; phone?: string };\n /** Standing/GA quantities the host sells outside the map. */\n extraItems?: { ticketTypeId: string; gaAreaId?: string; quantity: number }[];\n /** Promo code to redeem (case-insensitive); validated server-side. */\n discountCode?: string;\n captchaToken?: string;\n}\n\nexport interface CheckoutResult {\n reference?: string;\n status?: string;\n totalCents?: number;\n currency?: string;\n expiresAt?: string;\n payment?: { bankAccount?: string; variableSymbol?: string };\n}\n\n/** The handle mount() returns. */\nexport interface WidgetController {\n setFloor: (floorId: string) => void;\n getSelection: () => SelectionItem[];\n clearSelection: () => void;\n /**\n * Mounts the Bookitive captcha bridge (a small iframe on the Bookitive\n * origin) into el — put it where your form's captcha would sit. Once\n * mounted, checkout() routes the order through that frame, so the captcha\n * is solved and verified on the Bookitive origin: your domain never needs\n * any captcha configuration.\n */\n mountCaptcha: (el: HTMLElement) => void;\n checkout: (input: CheckoutInput) => Promise<CheckoutResult>;\n destroy: () => void;\n}\n\n/** The global the CDN runtime installs on `window`. */\nexport interface Bookitive {\n mount: (el: HTMLElement, options: WidgetOptions) => WidgetController;\n}\n\n// --- CustomEvents the widget dispatches (bubble from the mount element) -------\n\nexport interface BookitiveEventDetail {\n \"bookitive:ready\": { event: unknown };\n \"bookitive:selectionchanged\": {\n selection: SelectionItem[];\n totalCents: number;\n };\n \"bookitive:seatselected\": { seatId: string; code?: string };\n \"bookitive:seatdeselected\": {\n seatId: string;\n code?: string;\n reason?: \"taken\";\n };\n \"bookitive:error\": { message: string };\n}\nexport type BookitiveEventName = keyof BookitiveEventDetail;\nexport type BookitiveCustomEvent<K extends BookitiveEventName> = CustomEvent<\n BookitiveEventDetail[K]\n>;\n\n// --- loader ------------------------------------------------------------------\n\nexport interface LoadBookitiveOptions {\n /** Origin serving the widget bundle. Default: https://events.bookitive.com */\n scriptOrigin?: string;\n /** Major version of the widget runtime. Default: \"v1\". */\n version?: string;\n}\n\nconst SCRIPT_ATTR = \"data-bookitive-widget\";\nlet pending: Promise<Bookitive> | null = null;\n\nfunction existingGlobal(): Bookitive | null {\n if (typeof window === \"undefined\") return null;\n return (window as unknown as { Bookitive?: Bookitive }).Bookitive ?? null;\n}\n\n/**\n * Inject the Bookitive widget runtime from the CDN (once) and resolve to the\n * typed global. Safe to call repeatedly — subsequent calls reuse the same\n * in-flight promise / already-loaded global. Rejects during SSR (no `window`).\n */\nexport function loadBookitive(\n options: LoadBookitiveOptions = {},\n): Promise<Bookitive> {\n if (typeof window === \"undefined\" || typeof document === \"undefined\") {\n return Promise.reject(new Error(\"loadBookitive() must run in the browser\"));\n }\n const already = existingGlobal();\n if (already) return Promise.resolve(already);\n if (pending) return pending;\n\n const origin = (\n options.scriptOrigin ?? \"https://events.bookitive.com\"\n ).replace(/\\/+$/, \"\");\n const version = options.version ?? \"v1\";\n const src = `${origin}/widget/${version}.js`;\n\n pending = new Promise<Bookitive>((resolve, reject) => {\n // Clear the cached promise on any rejection so a transient failure can be\n // retried by a later loadBookitive() call (rather than caching the error).\n const settle = () => {\n const bk = existingGlobal();\n if (bk) return resolve(bk);\n pending = null;\n reject(\n new Error(\"Bookitive global missing after the widget script loaded\"),\n );\n };\n const fail = () => {\n pending = null;\n reject(new Error(`Failed to load the Bookitive widget from ${src}`));\n };\n\n // Reuse a script tag another loadBookitive() (or a manual embed) added.\n const existing = document.querySelector<HTMLScriptElement>(\n `script[${SCRIPT_ATTR}]`,\n );\n if (existing) {\n if (existingGlobal()) return settle();\n existing.addEventListener(\"load\", settle, { once: true });\n existing.addEventListener(\"error\", fail, { once: true });\n return;\n }\n\n const s = document.createElement(\"script\");\n s.src = src;\n s.async = true;\n s.setAttribute(SCRIPT_ATTR, \"\");\n s.addEventListener(\"load\", settle, { once: true });\n s.addEventListener(\"error\", fail, { once: true });\n document.head.appendChild(s);\n });\n return pending;\n}\n"],"mappings":";AA2HA,IAAM,cAAc;AACpB,IAAI,UAAqC;AAEzC,SAAS,iBAAmC;AAC1C,MAAI,OAAO,WAAW,YAAa,QAAO;AAC1C,SAAQ,OAAgD,aAAa;AACvE;AAOO,SAAS,cACd,UAAgC,CAAC,GACb;AACpB,MAAI,OAAO,WAAW,eAAe,OAAO,aAAa,aAAa;AACpE,WAAO,QAAQ,OAAO,IAAI,MAAM,yCAAyC,CAAC;AAAA,EAC5E;AACA,QAAM,UAAU,eAAe;AAC/B,MAAI,QAAS,QAAO,QAAQ,QAAQ,OAAO;AAC3C,MAAI,QAAS,QAAO;AAEpB,QAAM,UACJ,QAAQ,gBAAgB,gCACxB,QAAQ,QAAQ,EAAE;AACpB,QAAM,UAAU,QAAQ,WAAW;AACnC,QAAM,MAAM,GAAG,MAAM,WAAW,OAAO;AAEvC,YAAU,IAAI,QAAmB,CAAC,SAAS,WAAW;AAGpD,UAAM,SAAS,MAAM;AACnB,YAAM,KAAK,eAAe;AAC1B,UAAI,GAAI,QAAO,QAAQ,EAAE;AACzB,gBAAU;AACV;AAAA,QACE,IAAI,MAAM,yDAAyD;AAAA,MACrE;AAAA,IACF;AACA,UAAM,OAAO,MAAM;AACjB,gBAAU;AACV,aAAO,IAAI,MAAM,4CAA4C,GAAG,EAAE,CAAC;AAAA,IACrE;AAGA,UAAM,WAAW,SAAS;AAAA,MACxB,UAAU,WAAW;AAAA,IACvB;AACA,QAAI,UAAU;AACZ,UAAI,eAAe,EAAG,QAAO,OAAO;AACpC,eAAS,iBAAiB,QAAQ,QAAQ,EAAE,MAAM,KAAK,CAAC;AACxD,eAAS,iBAAiB,SAAS,MAAM,EAAE,MAAM,KAAK,CAAC;AACvD;AAAA,IACF;AAEA,UAAM,IAAI,SAAS,cAAc,QAAQ;AACzC,MAAE,MAAM;AACR,MAAE,QAAQ;AACV,MAAE,aAAa,aAAa,EAAE;AAC9B,MAAE,iBAAiB,QAAQ,QAAQ,EAAE,MAAM,KAAK,CAAC;AACjD,MAAE,iBAAiB,SAAS,MAAM,EAAE,MAAM,KAAK,CAAC;AAChD,aAAS,KAAK,YAAY,CAAC;AAAA,EAC7B,CAAC;AACD,SAAO;AACT;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bookitive/widget-js",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Loader + TypeScript types for the embeddable Bookitive seat-map widget. Injects the CDN runtime (like @stripe/stripe-js) and returns a typed mount().",
5
5
  "license": "MIT",
6
6
  "type": "module",