@bookitive/js 0.2.1 → 0.2.3

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/README.md ADDED
@@ -0,0 +1,101 @@
1
+ # @bookitive/js
2
+
3
+ Typed client for the **Bookitive** public checkout API — event info, seat-map
4
+ geometry, live availability, discounts, phone verification, and orders.
5
+
6
+ This is the headless companion to
7
+ [`@bookitive/widget-js`](https://www.npmjs.com/package/@bookitive/widget-js):
8
+ the widget gives you our seat-map UI on your page, this package gives you the
9
+ raw typed API for building your own checkout. All request and response types
10
+ are generated from the backend's OpenAPI spec, so they cannot drift.
11
+
12
+ ```bash
13
+ npm i @bookitive/js
14
+ ```
15
+
16
+ ```ts
17
+ import { BookitiveClient } from "@bookitive/js";
18
+
19
+ const client = new BookitiveClient({
20
+ baseUrl: "https://api.bookitive.com",
21
+ org: "city-theatre", // the {org} in events.bookitive.com/{org}/{event}
22
+ event: "spring-gala-2027",
23
+ });
24
+
25
+ const event = await client.event(); // identity, branding, priced ticket types
26
+ const seating = await client.seating(); // floors → sections → tables, seats, GA areas
27
+ const availability = await client.availability(); // current sellability snapshot
28
+ ```
29
+
30
+ ### Placing an order
31
+
32
+ `checkout()` places a bank-transfer reservation: seats are held atomically for
33
+ the event's hold TTL and payment instructions are emailed to the buyer (and
34
+ returned to you).
35
+
36
+ ```ts
37
+ import { BookitiveError, checkoutErrorMessage } from "@bookitive/js";
38
+
39
+ try {
40
+ const order = await client.checkout({
41
+ customer: { name: "Jana Nováková", email: "jana@example.com" },
42
+ items: [
43
+ { ticketTypeId: "…", seatId: "…", quantity: 1 }, // a seat
44
+ { ticketTypeId: "…", gaAreaId: "…", quantity: 2 }, // standing
45
+ ],
46
+ // discountCode, verificationToken, captchaToken, …
47
+ });
48
+ } catch (err) {
49
+ if (err instanceof BookitiveError && err.isConflict) {
50
+ // a seat was taken by another buyer mid-checkout — refresh and reselect
51
+ }
52
+ // localized buyer-facing copy for known error codes ('cs' | anything → en)
53
+ showError(checkoutErrorMessage(err, navigator.language) ?? "Something went wrong.");
54
+ }
55
+ ```
56
+
57
+ Errors throw as `BookitiveError` with the backend's structured envelope
58
+ (`status`, `type` — a `DtoErrorCode` you can compare against, and `fields`
59
+ with per-code detail such as order limits). `checkoutErrorMessage()` turns
60
+ known codes into buyer-ready Czech or English copy; it returns `undefined`
61
+ for unknown errors so you can fall back to your own generic text.
62
+
63
+ ### Live availability
64
+
65
+ `live()` subscribes over websocket. `startstate` fires with the full snapshot
66
+ on every (re)connect — treat it as your resync point — then per-seat deltas
67
+ stream in. It reconnects automatically with jittered backoff.
68
+
69
+ ```ts
70
+ const sub = client.live({
71
+ onStartstate: (availability) => resetMap(availability),
72
+ onSeat: ({ seatId, status }) => paintSeat(seatId, status),
73
+ onConnectionChange: (connected) => setLiveDot(connected),
74
+ });
75
+
76
+ // later
77
+ sub.close();
78
+ ```
79
+
80
+ Prefer polling? `availability()` returns the same snapshot on demand.
81
+
82
+ ### Discounts and phone verification
83
+
84
+ - `discount(code)` previews a promo code before checkout (404 for unknown or
85
+ inactive codes, 409 when fully redeemed); the order endpoint re-validates
86
+ on submit.
87
+ - For events that require a verified phone (`event.protection.requireVerifiedPhone`):
88
+ `verifyStart(phone, captchaToken)` sends the SMS code and
89
+ `verifyCheck(phone, code)` returns a single-use `verificationToken` to pass
90
+ in the order.
91
+
92
+ ### SSR
93
+
94
+ The client works server-side — pass your framework's `fetch` when it provides
95
+ one (e.g. SvelteKit load functions):
96
+
97
+ ```ts
98
+ const client = new BookitiveClient({ baseUrl, org, event, fetch });
99
+ ```
100
+
101
+ `live()` needs a browser `WebSocket`, so subscribe from client code.
package/dist/index.d.ts CHANGED
@@ -37,6 +37,19 @@ interface DtoAPIError {
37
37
  interface DtoPublicAvailability {
38
38
  /** GARemaining maps a standing area id to its remaining capacity. */
39
39
  gaRemaining?: Record<string, number>;
40
+ /**
41
+ * LowStockThreshold is where "low stock" begins for this event — clients
42
+ * show the low-stock line when remaining ≤ threshold. 0 = signal off.
43
+ * @example 10
44
+ */
45
+ lowStockThreshold?: number;
46
+ /**
47
+ * RecentOrders counts orders placed in the last 30 minutes — a live
48
+ * activity signal for urgency UI. Always 0 when the event disabled the
49
+ * social-proof signal; clients choose their display threshold.
50
+ * @example 4
51
+ */
52
+ recentOrders?: number;
40
53
  /**
41
54
  * ReservedSeats marks which unavailable seats are merely reserved and may
42
55
  * free up again — present only when the event shares reserved status.
@@ -69,7 +82,7 @@ interface DtoPublicCheckoutTheme {
69
82
  * Mode is the colour scheme: "light" or "dark".
70
83
  * @example "light"
71
84
  */
72
- mode?: "light" | "dark";
85
+ mode?: 'light' | 'dark';
73
86
  /**
74
87
  * Radius is the corner radius for fields and buttons.
75
88
  * @example "12px"
@@ -87,7 +100,7 @@ interface DtoPublicDiscount {
87
100
  * "fixed" amount in the event currency's minor units.
88
101
  * @example "percent"
89
102
  */
90
- kind?: "percent" | "fixed";
103
+ kind?: 'percent' | 'fixed';
91
104
  /**
92
105
  * Value is 1-100 for percent, minor units for fixed.
93
106
  * @example 10
@@ -175,7 +188,7 @@ interface DtoPublicFloor {
175
188
  * Mode is "single" (one open space) or "sections" (subdivided blocks).
176
189
  * @example "sections"
177
190
  */
178
- mode?: "single" | "sections";
191
+ mode?: 'single' | 'sections';
179
192
  /**
180
193
  * Name is the floor's display label.
181
194
  * @example "Ground floor"
@@ -219,7 +232,7 @@ interface DtoPublicFloorInfo {
219
232
  * Mode is "single" (one open space) or "sections" (subdivided blocks).
220
233
  * @example "sections"
221
234
  */
222
- mode?: "single" | "sections";
235
+ mode?: 'single' | 'sections';
223
236
  /**
224
237
  * Name is the floor's display label.
225
238
  * @example "Ground floor"
@@ -308,10 +321,11 @@ interface DtoPublicOrder {
308
321
  */
309
322
  reference?: string;
310
323
  /**
311
- * Status is the order lifecycle state at creation time.
324
+ * Status is the order lifecycle state at creation time. pending_review means
325
+ * the order is held for organizer approval (RFC 0004 risk "hold" action).
312
326
  * @example "pending"
313
327
  */
314
- status?: "pending" | "pending_review" | "paid" | "cancelled" | "expired";
328
+ status?: 'pending' | 'pending_review' | 'paid' | 'cancelled' | 'expired';
315
329
  /**
316
330
  * TotalCents is the amount due in the event currency's minor units.
317
331
  * @example 149000
@@ -384,6 +398,13 @@ interface DtoPublicOrderRequest {
384
398
  * @example "EARLYBIRD"
385
399
  */
386
400
  discountCode?: string;
401
+ /**
402
+ * Fingerprint is a stable hash of the buyer's browser, computed client-side
403
+ * and forwarded for device-velocity risk scoring (RFC 0004); optional.
404
+ * @maxLength 128
405
+ * @example "a1b2c3d4e5f6"
406
+ */
407
+ fingerprint?: string;
387
408
  /**
388
409
  * Items are the order lines; at least one is required.
389
410
  * @minItems 1
@@ -416,11 +437,22 @@ interface DtoPublicOrg {
416
437
  * @example "Rockfest Productions"
417
438
  */
418
439
  name?: string;
440
+ /**
441
+ * PrivacyURL is the organizer's own privacy policy.
442
+ * @example "https://rockfest.cz/ochrana-osobnich-udaju"
443
+ */
444
+ privacyUrl?: string;
419
445
  /**
420
446
  * Slug is the organizer's URL identifier (the {orgSlug} path parameter).
421
447
  * @example "rockfest"
422
448
  */
423
449
  slug?: string;
450
+ /**
451
+ * TermsURL is the organizer's own terms of sale — the organizer is the
452
+ * seller, so the checkout links these next to Bookitive's buyer notice.
453
+ * @example "https://rockfest.cz/obchodni-podminky"
454
+ */
455
+ termsUrl?: string;
424
456
  }
425
457
  /** Payment carries the bank-transfer instructions for pending orders. */
426
458
  interface DtoPublicPaymentDetail {
@@ -590,7 +622,7 @@ interface DtoPublicTable {
590
622
  * Shape is the table outline: "round", "rect", or "row".
591
623
  * @example "round"
592
624
  */
593
- shape?: "round" | "rect" | "row";
625
+ shape?: 'round' | 'rect' | 'row';
594
626
  /**
595
627
  * Sides is the seat count per edge for rect tables [top,right,bottom,left].
596
628
  * @uniqueItems false
@@ -618,6 +650,12 @@ interface DtoPublicTable {
618
650
  y?: number;
619
651
  }
620
652
  interface DtoPublicTicketType {
653
+ /**
654
+ * BasePriceCents is the regular price, present only when a scheduled phase is
655
+ * currently active and differs from it — so the widget can show "was €X".
656
+ * @example 179000
657
+ */
658
+ basePriceCents?: number;
621
659
  /**
622
660
  * Color is the swatch used to tint seats/areas of this type, as hex.
623
661
  * @example "#E5482F"
@@ -643,7 +681,7 @@ interface DtoPublicTicketType {
643
681
  * Kind is "seated" (maps to a seat) or "standing" (sold by quantity).
644
682
  * @example "standing"
645
683
  */
646
- kind?: "seated" | "standing";
684
+ kind?: 'seated' | 'standing';
647
685
  /**
648
686
  * MaxPerCustomer caps how many of this type one verified buyer may hold
649
687
  * across orders (anti-scalping); absent when unlimited.
@@ -667,10 +705,36 @@ interface DtoPublicTicketType {
667
705
  */
668
706
  name?: string;
669
707
  /**
670
- * PriceCents is the unit price in the event's currency's minor units.
708
+ * PriceCents is the EFFECTIVE unit price right now, in the event currency's
709
+ * minor units — the base price, or an active scheduled phase's price (RFC
710
+ * 0005). This is what the buyer pays.
671
711
  * @example 149000
672
712
  */
673
713
  priceCents?: number;
714
+ /**
715
+ * PriceChangesAt is the next scheduled price boundary (RFC 3339) — the widget
716
+ * can count down "early bird ends in …"; absent when the price won't change.
717
+ * @example "2026-06-01T00:00:00Z"
718
+ */
719
+ priceChangesAt?: string;
720
+ /**
721
+ * PricePhaseDescription is the active phase's optional organizer-authored
722
+ * copy, shown in the widget's phase info popover.
723
+ * @example "Launch price for the first hundred fans."
724
+ */
725
+ pricePhaseDescription?: string;
726
+ /**
727
+ * PricePhaseName is the active phase's label (e.g. "Early bird"), if any.
728
+ * @example "Early bird"
729
+ */
730
+ pricePhaseName?: string;
731
+ /**
732
+ * PricePhaseRemaining is how many tickets are left at the active phase's
733
+ * price before its quantity cap (RFC 0005) — the "12 left at early bird"
734
+ * urgency; absent when the active phase has no quantity cap.
735
+ * @example 12
736
+ */
737
+ pricePhaseRemaining?: number;
674
738
  /**
675
739
  * Pros are short selling points for this ticket type, shown as a checklist.
676
740
  * @uniqueItems false
@@ -773,6 +837,9 @@ declare class BookitiveError extends Error {
773
837
  /** The backend error code (e.g. DtoErrorCode.ErrConflict), or "UNKNOWN" when
774
838
  * the response carried no JSON envelope. */
775
839
  readonly type: DtoErrorCode | 'UNKNOWN';
840
+ /** Structured detail for some codes (e.g. PER_ORDER_LIMITS carries
841
+ * ticketType/min/max/requested) — feeds checkoutErrorMessage(). */
842
+ readonly fields?: Record<string, string>;
776
843
  constructor(status: number, body: DtoAPIError | undefined, fallback: string);
777
844
  /** The selection raced another buyer — refresh availability and reselect. */
778
845
  get isConflict(): boolean;
@@ -822,4 +889,20 @@ declare class BookitiveClient {
822
889
  live(handlers: LiveHandlers): LiveSubscription;
823
890
  }
824
891
 
825
- export { BookitiveClient, type BookitiveClientOptions, BookitiveError, type DtoAPIError, DtoErrorCode, type DtoPublicAvailability, type DtoPublicCheckoutTheme, type DtoPublicDiscount, type DtoPublicEvent, type DtoPublicEventInfo, type DtoPublicFloor, type DtoPublicFloorInfo, type DtoPublicGAArea, type DtoPublicOrder, type DtoPublicOrderCustomer, type DtoPublicOrderItem, type DtoPublicOrderRequest, type DtoPublicOrg, type DtoPublicPaymentDetail, type DtoPublicProtection, type DtoPublicSeat, type DtoPublicSeating, type DtoPublicSection, type DtoPublicTable, type DtoPublicTicketType, type DtoPublicVerification, type DtoPublicVerifyCheckRequest, type DtoPublicVerifyStartRequest, type LiveHandlers, LiveSubscription, type ResponseAPIResponseEmpty, type SeatDelta };
892
+ /**
893
+ * Localized buyer-facing message for a checkout failure, or undefined when
894
+ * the error carries no known code (caller shows its own generic text).
895
+ * Accepts any BCP 47 locale; everything except Czech falls back to English.
896
+ */
897
+ declare function checkoutErrorMessage(err: unknown, locale?: string): string | undefined;
898
+
899
+ /**
900
+ * Compute a coarse device fingerprint for the current browser, or undefined when
901
+ * not in a browser / when the ambient traits can't be read. Combines a few stable
902
+ * traits (UA, language, platform, screen geometry, timezone, hardware hints) into
903
+ * one short hash. Deliberately best-effort — two different browsers on the same
904
+ * machine hash differently, which is fine: it only needs to cluster reuse.
905
+ */
906
+ declare function deviceFingerprint(): string | undefined;
907
+
908
+ export { BookitiveClient, type BookitiveClientOptions, BookitiveError, type DtoAPIError, DtoErrorCode, type DtoPublicAvailability, type DtoPublicCheckoutTheme, type DtoPublicDiscount, type DtoPublicEvent, type DtoPublicEventInfo, type DtoPublicFloor, type DtoPublicFloorInfo, type DtoPublicGAArea, type DtoPublicOrder, type DtoPublicOrderCustomer, type DtoPublicOrderItem, type DtoPublicOrderRequest, type DtoPublicOrg, type DtoPublicPaymentDetail, type DtoPublicProtection, type DtoPublicSeat, type DtoPublicSeating, type DtoPublicSection, type DtoPublicTable, type DtoPublicTicketType, type DtoPublicVerification, type DtoPublicVerifyCheckRequest, type DtoPublicVerifyStartRequest, type LiveHandlers, LiveSubscription, type ResponseAPIResponseEmpty, type SeatDelta, checkoutErrorMessage, deviceFingerprint };
package/dist/index.js CHANGED
@@ -61,17 +61,53 @@ var LiveSubscription = class {
61
61
  }
62
62
  };
63
63
 
64
+ // src/fingerprint.ts
65
+ function hash(input) {
66
+ let h = 5381;
67
+ for (let i = 0; i < input.length; i++) {
68
+ h = h * 33 ^ input.charCodeAt(i);
69
+ }
70
+ return (h >>> 0).toString(16).padStart(8, "0");
71
+ }
72
+ function deviceFingerprint() {
73
+ try {
74
+ if (typeof navigator === "undefined" || typeof screen === "undefined") return void 0;
75
+ const nav = navigator;
76
+ const traits = [
77
+ nav.userAgent,
78
+ nav.language,
79
+ (nav.languages ?? []).join(","),
80
+ nav.platform,
81
+ nav.hardwareConcurrency,
82
+ nav.deviceMemory,
83
+ screen.width,
84
+ screen.height,
85
+ screen.colorDepth,
86
+ // Timezone offset is stable per machine locale and cheap to read.
87
+ (/* @__PURE__ */ new Date()).getTimezoneOffset(),
88
+ Intl.DateTimeFormat().resolvedOptions().timeZone
89
+ ];
90
+ return hash(traits.map((t) => String(t ?? "")).join("|"));
91
+ } catch {
92
+ return void 0;
93
+ }
94
+ }
95
+
64
96
  // src/client.ts
65
97
  var BookitiveError = class extends Error {
66
98
  status;
67
99
  /** The backend error code (e.g. DtoErrorCode.ErrConflict), or "UNKNOWN" when
68
100
  * the response carried no JSON envelope. */
69
101
  type;
102
+ /** Structured detail for some codes (e.g. PER_ORDER_LIMITS carries
103
+ * ticketType/min/max/requested) — feeds checkoutErrorMessage(). */
104
+ fields;
70
105
  constructor(status, body, fallback) {
71
106
  super(body?.msg ?? fallback);
72
107
  this.name = "BookitiveError";
73
108
  this.status = status;
74
109
  this.type = body?.type ?? "UNKNOWN";
110
+ this.fields = body?.fields;
75
111
  }
76
112
  /** The selection raced another buyer — refresh availability and reselect. */
77
113
  get isConflict() {
@@ -115,10 +151,11 @@ var BookitiveClient = class {
115
151
  * reselect.
116
152
  */
117
153
  async checkout(order) {
154
+ const body = order.fingerprint === void 0 ? { ...order, fingerprint: deviceFingerprint() } : order;
118
155
  const res = await this.#fetch(`${this.#base}/orders`, {
119
156
  method: "POST",
120
157
  headers: { "Content-Type": "application/json" },
121
- body: JSON.stringify(order)
158
+ body: JSON.stringify(body)
122
159
  });
123
160
  return this.#parse(res, "checkout failed");
124
161
  }
@@ -181,10 +218,49 @@ var BookitiveClient = class {
181
218
  return text ? JSON.parse(text) : void 0;
182
219
  }
183
220
  };
221
+
222
+ // src/checkoutErrors.ts
223
+ var MESSAGES = {
224
+ cs: {
225
+ PER_ORDER_LIMITS: (f) => f.min ? `Vstupenek \u201E${f.ticketType}\u201C je pot\u0159eba objednat alespo\u0148 ${f.min}.` : f.max ? `Vstupenek \u201E${f.ticketType}\u201C lze objednat nejv\xFD\u0161e ${f.max} na jednu objedn\xE1vku.` : "Po\u010Det vstupenek je mimo povolen\xFD rozsah pro jednu objedn\xE1vku.",
226
+ PER_CUSTOMER_LIMIT: "P\u0159ekro\u010Dili jste maxim\xE1ln\xED po\u010Det vstupenek na jednoho z\xE1kazn\xEDka pro tuto akci.",
227
+ SOLD_OUT: "Vybran\xFD typ vstupenek je ji\u017E vyprodan\xFD.",
228
+ SEATS_TAKEN: "N\u011Bkter\xE9 z vybran\xFDch m\xEDst si pr\xE1v\u011B zabral n\u011Bkdo jin\xFD \u2014 vyberte pros\xEDm jin\xE1.",
229
+ CAPACITY_FULL: "Kapacita vybran\xE9 z\xF3ny je ji\u017E vy\u010Derpan\xE1.",
230
+ SALE_WINDOW: "Prodej tohoto typu vstupenek pr\xE1v\u011B neprob\xEDh\xE1.",
231
+ NOT_ON_SALE: "Prodej vstupenek na tuto akci pr\xE1v\u011B neprob\xEDh\xE1.",
232
+ DISCOUNT_INVALID: "Zadan\xFD slevov\xFD k\xF3d nen\xED platn\xFD.",
233
+ DISCOUNT_EXHAUSTED: "Zadan\xFD slevov\xFD k\xF3d u\u017E byl vy\u010Derp\xE1n.",
234
+ VERIFICATION_INVALID: "Ov\u011B\u0159en\xED telefonu vypr\u0161elo \u2014 ov\u011B\u0159te se pros\xEDm znovu.",
235
+ ATTENDEES_REQUIRED: "Vypl\u0148te pros\xEDm jm\xE9no pro ka\u017Edou vstupenku."
236
+ },
237
+ en: {
238
+ PER_ORDER_LIMITS: (f) => f.min ? `You need to order at least ${f.min} \u201C${f.ticketType}\u201D tickets.` : f.max ? `You can order at most ${f.max} \u201C${f.ticketType}\u201D tickets per order.` : "The ticket quantity is outside the allowed per-order range.",
239
+ PER_CUSTOMER_LIMIT: "You have reached the maximum number of tickets per customer for this event.",
240
+ SOLD_OUT: "The selected ticket type is sold out.",
241
+ SEATS_TAKEN: "One of your seats was just taken \u2014 please pick another.",
242
+ CAPACITY_FULL: "The selected area is at full capacity.",
243
+ SALE_WINDOW: "This ticket type is not on sale right now.",
244
+ NOT_ON_SALE: "Tickets for this event are not on sale right now.",
245
+ DISCOUNT_INVALID: "That discount code is not valid.",
246
+ DISCOUNT_EXHAUSTED: "That discount code has been fully redeemed.",
247
+ VERIFICATION_INVALID: "Your phone verification expired \u2014 please verify again.",
248
+ ATTENDEES_REQUIRED: "Please fill in a name for every ticket."
249
+ }
250
+ };
251
+ function checkoutErrorMessage(err, locale) {
252
+ if (!(err instanceof BookitiveError)) return void 0;
253
+ const lang = locale?.toLowerCase().startsWith("cs") ? "cs" : "en";
254
+ const entry = MESSAGES[lang][err.type];
255
+ if (!entry) return void 0;
256
+ return typeof entry === "function" ? entry(err.fields ?? {}) : entry;
257
+ }
184
258
  export {
185
259
  BookitiveClient,
186
260
  BookitiveError,
187
261
  DtoErrorCode,
188
- LiveSubscription
262
+ LiveSubscription,
263
+ checkoutErrorMessage,
264
+ deviceFingerprint
189
265
  };
190
266
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/generated/api.ts","../src/live.ts","../src/client.ts"],"sourcesContent":["/* eslint-disable */\n/* tslint:disable */\n// @ts-nocheck\n/*\n * ---------------------------------------------------------------\n * ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API ##\n * ## ##\n * ## AUTHOR: acacode ##\n * ## SOURCE: https://github.com/acacode/swagger-typescript-api ##\n * ---------------------------------------------------------------\n */\n\n/**\n * Type is the stable, machine-readable error code.\n * @example \"CONFLICT\"\n */\nexport enum DtoErrorCode {\n ErrInvalidPayload = \"INVALID_PAYLOAD\",\n ErrValidationFailed = \"VALIDATION_FAILED\",\n ErrNotFound = \"NOT_FOUND\",\n ErrConflict = \"CONFLICT\",\n ErrForbidden = \"FORBIDDEN\",\n ErrVerificationRequired = \"VERIFICATION_REQUIRED\",\n ErrInternal = \"INTERNAL_ERROR\",\n}\n\nexport interface DtoAPIError {\n /** Data carries optional structured context for the error. */\n data?: any;\n /** Fields maps a request field to a validation message, when applicable. */\n fields?: Record<string, string>;\n /**\n * Msg is a human-readable description of what went wrong.\n * @example \"seat is no longer available\"\n */\n msg?: string;\n /**\n * Timestamp is when the error was produced (RFC 3339).\n * @example \"2026-06-20T18:00:00Z\"\n */\n timestamp?: string;\n /**\n * TraceID identifies this request in our logs; include it in support asks.\n * @example \"01JABCDoftraceZ\"\n */\n traceId?: string;\n /** Type is the stable, machine-readable error code. */\n type?: DtoErrorCode;\n}\n\nexport interface DtoPublicAvailability {\n /** GARemaining maps a standing area id to its remaining capacity. */\n gaRemaining?: Record<string, number>;\n /**\n * ReservedSeats marks which unavailable seats are merely reserved and may\n * free up again — present only when the event shares reserved status.\n * @uniqueItems false\n * @example [\"1810000000000000502\"]\n */\n reservedSeats?: string[];\n /** TicketTypeRemaining maps a ticket type id to its remaining capacity. */\n ticketTypeRemaining?: Record<string, number>;\n /**\n * UnavailableSeats lists the ids of seats that cannot currently be bought.\n * @uniqueItems false\n * @example [\"1810000000000000501\",\"1810000000000000502\"]\n */\n unavailableSeats?: string[];\n}\n\n/** CheckoutTheme customises the hosted checkout; null = Bookitive defaults. */\nexport interface DtoPublicCheckoutTheme {\n /**\n * ButtonLabel overrides the submit button text.\n * @example \"Objednat\"\n */\n buttonLabel?: string;\n /**\n * FontFamily is an allowlisted font key (self-hosted by Bookitive).\n * @example \"inter\"\n */\n fontFamily?: string;\n /**\n * Mode is the colour scheme: \"light\" or \"dark\".\n * @example \"light\"\n */\n mode?: \"light\" | \"dark\";\n /**\n * Radius is the corner radius for fields and buttons.\n * @example \"12px\"\n */\n radius?: string;\n}\n\nexport interface DtoPublicDiscount {\n /**\n * Code is the normalized (uppercase) promo code.\n * @example \"EARLYBIRD\"\n */\n code?: string;\n /**\n * Kind is how the discount applies: \"percent\" of the items subtotal, or a\n * \"fixed\" amount in the event currency's minor units.\n * @example \"percent\"\n */\n kind?: \"percent\" | \"fixed\";\n /**\n * Value is 1-100 for percent, minor units for fixed.\n * @example 10\n */\n value?: number;\n}\n\nexport interface DtoPublicEvent {\n /** Event is the event's public identity: name, times, venue, currency. */\n event?: DtoPublicEventInfo;\n /**\n * Floors lets a host page render its floor tabs without loading the full\n * seating payload; ids match the seating endpoint's floors.\n * @uniqueItems false\n */\n floors?: DtoPublicFloorInfo[];\n /** Org is the organizer's public branding (name, logo, accent colour). */\n org?: DtoPublicOrg;\n /**\n * Protection describes the anti-scalping requirements a buyer must satisfy\n * at checkout (verified phone, named tickets). All-false when unprotected.\n */\n protection?: DtoPublicProtection;\n /**\n * TicketTypes are the sellable ticket categories with prices and limits.\n * @uniqueItems false\n */\n ticketTypes?: DtoPublicTicketType[];\n}\n\n/** Event is the event's public identity: name, times, venue, currency. */\nexport interface DtoPublicEventInfo {\n /**\n * Currency is the ISO 4217 code all prices are quoted in.\n * @example \"CZK\"\n */\n currency?: string;\n /**\n * EndTime is when the event ends (RFC 3339), when known.\n * @example \"2026-06-20T23:00:00Z\"\n */\n endTime?: string;\n /**\n * HoldTTLSeconds is how long a checkout holds selected seats before expiry.\n * @example 900\n */\n holdTtlSeconds?: number;\n /**\n * LocationName is the venue's display name, when set.\n * @example \"Výstaviště Praha\"\n */\n locationName?: string;\n /**\n * Name is the event's public display name.\n * @example \"Rockfest 2026\"\n */\n name?: string;\n /**\n * Slug is the event's URL identifier (the {eventSlug} path parameter).\n * @example \"rockfest-2026\"\n */\n slug?: string;\n /**\n * StartTime is when doors/the event begins (RFC 3339).\n * @example \"2026-06-20T18:00:00Z\"\n */\n startTime?: string;\n}\n\nexport interface DtoPublicFloor {\n /**\n * Cons are short caveats for this floor, shown alongside the pros.\n * @uniqueItems false\n * @example [\"Standing only\"]\n */\n cons?: string[];\n /**\n * Description is optional customer-facing copy shown on the floor's tab.\n * @example \"Standing area in front of the main stage\"\n */\n description?: string;\n /**\n * ID is the floor's stable id; it matches PublicFloorInfo.id.\n * @example \"1810000000000000101\"\n */\n id?: string;\n /**\n * Mode is \"single\" (one open space) or \"sections\" (subdivided blocks).\n * @example \"sections\"\n */\n mode?: \"single\" | \"sections\";\n /**\n * Name is the floor's display label.\n * @example \"Ground floor\"\n */\n name?: string;\n /**\n * Position is the 1-based tab order among the event's floors.\n * @example 1\n */\n position?: number;\n /**\n * Pros are short selling points for this floor, shown as a checklist.\n * @uniqueItems false\n * @example [\"Closest to the stage\",\"Great atmosphere\"]\n */\n pros?: string[];\n /**\n * Sections are the floor's blocks; a single-mode floor has exactly one.\n * @uniqueItems false\n */\n sections?: DtoPublicSection[];\n}\n\nexport interface DtoPublicFloorInfo {\n /**\n * Cons are short caveats for this floor, shown alongside the pros.\n * @uniqueItems false\n * @example [\"Standing only\"]\n */\n cons?: string[];\n /**\n * Description is optional customer-facing copy shown on the floor's tab.\n * @example \"Standing area in front of the main stage\"\n */\n description?: string;\n /**\n * ID is the floor's stable id; it matches a floor in the seating endpoint.\n * @example \"1810000000000000101\"\n */\n id?: string;\n /**\n * Mode is \"single\" (one open space) or \"sections\" (subdivided blocks).\n * @example \"sections\"\n */\n mode?: \"single\" | \"sections\";\n /**\n * Name is the floor's display label.\n * @example \"Ground floor\"\n */\n name?: string;\n /**\n * Position is the 1-based tab order among the event's floors.\n * @example 1\n */\n position?: number;\n /**\n * Pros are short selling points for this floor, shown as a checklist.\n * @uniqueItems false\n * @example [\"Closest to the stage\",\"Great atmosphere\"]\n */\n pros?: string[];\n}\n\nexport interface DtoPublicGAArea {\n /**\n * Capacity is the total number of standing tickets this area can sell.\n * @example 500\n */\n capacity?: number;\n /**\n * H is the area's height on the floor canvas.\n * @example 200\n */\n h?: number;\n /**\n * ID is the standing area's stable id, referenced by availability/orders.\n * @example \"1810000000000000601\"\n */\n id?: string;\n /**\n * Label is the standing area's display name.\n * @example \"Pit\"\n */\n label?: string;\n /**\n * Rotation is the area's rotation in degrees.\n * @example 0\n */\n rotation?: number;\n /**\n * TicketTypeID is the standing ticket type mapped to this area, when priced.\n * @example \"1810000000000000201\"\n */\n ticketTypeId?: string;\n /**\n * W is the area's width on the floor canvas.\n * @example 600\n */\n w?: number;\n /**\n * X is the area's left offset on the floor canvas.\n * @example 100\n */\n x?: number;\n /**\n * Y is the area's top offset on the floor canvas.\n * @example 400\n */\n y?: number;\n}\n\nexport interface DtoPublicOrder {\n /**\n * Currency is the ISO 4217 code the total is quoted in.\n * @example \"CZK\"\n */\n currency?: string;\n /**\n * DiscountCents is what the redeemed promo code took off the total.\n * @example 14900\n */\n discountCents?: number;\n /**\n * ExpiresAt is when the held seats and this order expire (RFC 3339).\n * @example \"2026-06-20T18:15:00Z\"\n */\n expiresAt?: string;\n /** Payment carries the bank-transfer instructions for pending orders. */\n payment?: DtoPublicPaymentDetail;\n /**\n * Reference is the order's human-facing reference (also the payment symbol).\n * @example \"RF26-000142\"\n */\n reference?: string;\n /**\n * Status is the order lifecycle state at creation time.\n * @example \"pending\"\n */\n status?: \"pending\" | \"pending_review\" | \"paid\" | \"cancelled\" | \"expired\";\n /**\n * TotalCents is the amount due in the event currency's minor units.\n * @example 149000\n */\n totalCents?: number;\n}\n\n/** Customer is the buyer's contact details. */\nexport interface DtoPublicOrderCustomer {\n /**\n * Email is where the payment instructions and tickets are sent.\n * @example \"jana@example.com\"\n */\n email: string;\n /**\n * Name is the buyer's full name.\n * @maxLength 200\n * @example \"Jana Nováková\"\n */\n name: string;\n /**\n * Phone is an optional contact number.\n * @maxLength 50\n * @example \"+420123456789\"\n */\n phone?: string;\n}\n\nexport interface DtoPublicOrderItem {\n /**\n * Attendees carries one full name per admitted person when the event\n * requires named tickets — exactly Quantity names, in ticket order.\n * @uniqueItems false\n * @example [\"Jana Nováková\"]\n */\n attendees?: string[];\n /**\n * GAAreaID is the standing area's id for standing ticket types.\n * @example \"1810000000000000601\"\n */\n gaAreaId?: string;\n /**\n * Quantity is how many to buy — always 1 for a seat, 1+ for standing.\n * @min 1\n * @example 1\n */\n quantity: number;\n /**\n * SeatID is the chosen seat's id for seated ticket types.\n * @example \"1810000000000000501\"\n */\n seatId?: string;\n /**\n * TicketTypeID is the ticket type being bought (from the event endpoint).\n * @example \"1810000000000000201\"\n */\n ticketTypeId: string;\n}\n\nexport interface DtoPublicOrderRequest {\n /**\n * CaptchaToken is the client-side captcha solution, verified server-side.\n * @example \"03AGdBq26…\"\n */\n captchaToken?: string;\n /** Customer is the buyer's contact details. */\n customer: DtoPublicOrderCustomer;\n /**\n * DiscountCode redeems a promo code (case-insensitive); the discount is\n * applied to the items subtotal and enforced against the code's limits.\n * @minLength 2\n * @maxLength 40\n * @example \"EARLYBIRD\"\n */\n discountCode?: string;\n /**\n * Items are the order lines; at least one is required.\n * @minItems 1\n * @uniqueItems false\n */\n items: DtoPublicOrderItem[];\n /**\n * VerificationToken is the single-use token from the verify/check endpoint,\n * required when the event demands a verified phone number.\n * @example \"3q2p8Zk1…\"\n */\n verificationToken?: string;\n}\n\n/** Org is the organizer's public branding (name, logo, accent colour). */\nexport interface DtoPublicOrg {\n /**\n * AccentColor is the organizer's brand colour as a hex string, if set.\n * @example \"#E5482F\"\n */\n accentColor?: string;\n /** CheckoutTheme customises the hosted checkout; null = Bookitive defaults. */\n checkoutTheme?: DtoPublicCheckoutTheme;\n /**\n * LogoURL is an absolute URL to the organizer's logo, if set.\n * @example \"https://cdn.bookitive.com/orgs/rockfest/logo.png\"\n */\n logoUrl?: string;\n /**\n * Name is the organizer's public display name.\n * @example \"Rockfest Productions\"\n */\n name?: string;\n /**\n * Slug is the organizer's URL identifier (the {orgSlug} path parameter).\n * @example \"rockfest\"\n */\n slug?: string;\n}\n\n/** Payment carries the bank-transfer instructions for pending orders. */\nexport interface DtoPublicPaymentDetail {\n /**\n * BankAccount is the organizer's free-form payment destination.\n * @example \"123456789/0100\"\n */\n bankAccount?: string;\n /**\n * VariableSymbol identifies the payment — it equals the order reference.\n * @example \"26000142\"\n */\n variableSymbol?: string;\n}\n\n/**\n * Protection describes the anti-scalping requirements a buyer must satisfy\n * at checkout (verified phone, named tickets). All-false when unprotected.\n */\nexport interface DtoPublicProtection {\n /**\n * PhonePrefixes is the allowlist of accepted international dialling codes\n * (e.g. \"+420\"). Only meaningful when RequireVerifiedPhone is set.\n * @uniqueItems false\n * @example [\"+420\",\"+421\"]\n */\n phonePrefixes?: string[];\n /**\n * RequireAttendeeNames demands one attendee name per admitted person.\n * @example false\n */\n requireAttendeeNames?: boolean;\n /**\n * RequireVerifiedPhone gates checkout behind SMS phone verification.\n * @example true\n */\n requireVerifiedPhone?: boolean;\n}\n\nexport interface DtoPublicSeat {\n /**\n * Code is the human-facing seat label (section prefix + table + seat).\n * @example \"L12-3\"\n */\n code?: string;\n /**\n * ID is the seat's stable id; availability and orders reference it.\n * @example \"1810000000000000501\"\n */\n id?: string;\n /**\n * TableID is the owning table's id, when the seat belongs to a table.\n * @example \"1810000000000000401\"\n */\n tableId?: string;\n /**\n * TicketTypeID is the ticket type mapped to this seat, when priced.\n * @example \"1810000000000000201\"\n */\n ticketTypeId?: string;\n /**\n * X is the seat centre's x on the floor canvas.\n * @example 245\n */\n x?: number;\n /**\n * Y is the seat centre's y on the floor canvas.\n * @example 158\n */\n y?: number;\n}\n\nexport interface DtoPublicSeating {\n /**\n * Floors are the chart's floors in tab order, each with its geometry.\n * @uniqueItems false\n */\n floors?: DtoPublicFloor[];\n}\n\nexport interface DtoPublicSection {\n /** Decorations is opaque visual scenery (stage, walls, doors) — never sold. */\n decorations?: object;\n /**\n * GAAreas are the standing areas (sold by quantity) in this section.\n * @uniqueItems false\n */\n gaAreas?: DtoPublicGAArea[];\n /**\n * H is the section's height on the floor canvas, if sized.\n * @example 300\n */\n h?: number;\n /**\n * ID is the section's stable id.\n * @example \"1810000000000000301\"\n */\n id?: string;\n /**\n * Name is the section's display label.\n * @example \"Left block\"\n */\n name?: string;\n /** Polygon is the section's outline as an array of {x,y} points, if shaped. */\n polygon?: object;\n /**\n * Prefix is prepended to seat codes in this section (e.g. \"L\" → \"L12-3\").\n * @example \"L\"\n */\n prefix?: string;\n /**\n * Seats are the individually sellable seats in this section.\n * @uniqueItems false\n */\n seats?: DtoPublicSeat[];\n /** Settings is opaque render config (chair gap/size, label typography). */\n settings?: object;\n /**\n * Tables are the seated tables in this section.\n * @uniqueItems false\n */\n tables?: DtoPublicTable[];\n /**\n * W is the section's width on the floor canvas, if sized.\n * @example 400\n */\n w?: number;\n /**\n * X is the section's left offset on the floor canvas, if positioned.\n * @example 120\n */\n x?: number;\n /**\n * Y is the section's top offset on the floor canvas, if positioned.\n * @example 80\n */\n y?: number;\n}\n\nexport interface DtoPublicTable {\n /**\n * Curve is the arc for row tables, in degrees (0 = straight row).\n * @example 0\n */\n curve?: number;\n /**\n * H is the height for rect tables, in canvas units.\n * @example 60\n */\n h?: number;\n /**\n * ID is the table's stable id, referenced by PublicSeat.tableId.\n * @example \"1810000000000000401\"\n */\n id?: string;\n /**\n * Number is the table's display number within its section.\n * @example 12\n */\n number?: number;\n /**\n * Radius is the radius for round tables, in canvas units.\n * @example 45\n */\n radius?: number;\n /**\n * Rotation is the table's rotation in degrees.\n * @example 0\n */\n rotation?: number;\n /**\n * Shape is the table outline: \"round\", \"rect\", or \"row\".\n * @example \"round\"\n */\n shape?: \"round\" | \"rect\" | \"row\";\n /**\n * Sides is the seat count per edge for rect tables [top,right,bottom,left].\n * @uniqueItems false\n */\n sides?: number[];\n /**\n * Spacing is the gap between seats on a row table, in canvas units.\n * @example 24\n */\n spacing?: number;\n /**\n * W is the width for rect tables, in canvas units.\n * @example 120\n */\n w?: number;\n /**\n * X is the table centre's x on the floor canvas.\n * @example 240.5\n */\n x?: number;\n /**\n * Y is the table centre's y on the floor canvas.\n * @example 160\n */\n y?: number;\n}\n\nexport interface DtoPublicTicketType {\n /**\n * Color is the swatch used to tint seats/areas of this type, as hex.\n * @example \"#E5482F\"\n */\n color?: string;\n /**\n * Cons are short caveats for this ticket type, shown alongside the pros.\n * @uniqueItems false\n * @example [\"No seating\"]\n */\n cons?: string[];\n /**\n * Description is optional customer-facing copy for this ticket type.\n * @example \"Front-stage standing with a dedicated bar\"\n */\n description?: string;\n /**\n * ID is the ticket type's stable id, referenced by order items and seats.\n * @example \"1810000000000000201\"\n */\n id?: string;\n /**\n * Kind is \"seated\" (maps to a seat) or \"standing\" (sold by quantity).\n * @example \"standing\"\n */\n kind?: \"seated\" | \"standing\";\n /**\n * MaxPerCustomer caps how many of this type one verified buyer may hold\n * across orders (anti-scalping); absent when unlimited.\n * @example 4\n */\n maxPerCustomer?: number;\n /**\n * MaxPerOrder caps how many of this type a single order may contain.\n * @example 6\n */\n maxPerOrder?: number;\n /**\n * MinPerOrder appears only when it is a real constraint (> 1) — the\n * limit applies to orders that include this type at all.\n * @example 1\n */\n minPerOrder?: number;\n /**\n * Name is the ticket type's display label.\n * @example \"VIP\"\n */\n name?: string;\n /**\n * PriceCents is the unit price in the event's currency's minor units.\n * @example 149000\n */\n priceCents?: number;\n /**\n * Pros are short selling points for this ticket type, shown as a checklist.\n * @uniqueItems false\n * @example [\"Best view\",\"Dedicated bar\",\"Fast-track entry\"]\n */\n pros?: string[];\n /**\n * SaleEndTime is when this type stops selling (RFC 3339), if scheduled.\n * @example \"2026-06-20T16:00:00Z\"\n */\n saleEndTime?: string;\n /**\n * SaleStartTime is when this type goes on sale (RFC 3339), if scheduled.\n * @example \"2026-01-15T10:00:00Z\"\n */\n saleStartTime?: string;\n}\n\nexport interface DtoPublicVerification {\n /**\n * ExpiresAt is when the token stops being accepted (RFC 3339).\n * @example \"2026-06-20T18:30:00Z\"\n */\n expiresAt?: string;\n /**\n * Phone is the normalized number the token certifies.\n * @example \"+420777123456\"\n */\n phone?: string;\n /**\n * VerificationToken is consumed by exactly one order for this event.\n * @example \"5Yx…\"\n */\n verificationToken?: string;\n}\n\nexport interface DtoPublicVerifyCheckRequest {\n /**\n * Code is the OTP from the SMS.\n * @minLength 4\n * @maxLength 10\n * @example \"123456\"\n */\n code: string;\n /**\n * @minLength 8\n * @maxLength 20\n * @example \"+420777123456\"\n */\n phone: string;\n}\n\nexport interface DtoPublicVerifyStartRequest {\n /**\n * CaptchaToken is the client-side captcha solution, verified server-side.\n * @example \"03AGdBq26…\"\n */\n captchaToken?: string;\n /**\n * Phone in international E.164 format.\n * @minLength 8\n * @maxLength 20\n * @example \"+420777123456\"\n */\n phone: string;\n}\n\nexport type ResponseAPIResponseEmpty = object;\n","// Live availability over websocket: {\"event\":\"startstate\"|\"seat\", data}.\n// Startstate re-fires on every reconnect, so consumers treat it as the\n// resync point and apply deltas on top.\nimport type { DtoPublicAvailability } from './generated/api.js';\n\n/** One seat changing state. \"reserved\" appears only when the event shares\n * reserved status publicly — a reserved seat may free up again. */\nexport interface SeatDelta {\n\tseatId: string;\n\ttaken: boolean;\n\tstatus: 'free' | 'reserved' | 'taken';\n}\n\nexport interface LiveHandlers {\n\t/** Full snapshot — on connect and on every reconnect. */\n\tonStartstate?: (availability: DtoPublicAvailability) => void;\n\t/** One seat changed. */\n\tonSeat?: (delta: SeatDelta) => void;\n\t/** Connection state changes (drive a \"live\" indicator if you like). */\n\tonConnectionChange?: (connected: boolean) => void;\n}\n\nconst INITIAL_BACKOFF_MS = 1000;\nconst MAX_BACKOFF_MS = 15000;\n\nexport class LiveSubscription {\n\t#url: string;\n\t#handlers: LiveHandlers;\n\t#ws: WebSocket | null = null;\n\t#backoff = INITIAL_BACKOFF_MS;\n\t#retry: ReturnType<typeof setTimeout> | null = null;\n\t#closed = false;\n\n\tconstructor(url: string, handlers: LiveHandlers) {\n\t\tthis.#url = url.replace(/^http/, 'ws');\n\t\tthis.#handlers = handlers;\n\t\tthis.#connect();\n\t}\n\n\tclose(): void {\n\t\tthis.#closed = true;\n\t\tif (this.#retry) clearTimeout(this.#retry);\n\t\tthis.#ws?.close();\n\t}\n\n\t#connect(): void {\n\t\tif (this.#closed) return;\n\t\tconst ws = new WebSocket(this.#url);\n\t\tthis.#ws = ws;\n\t\tws.onopen = () => {\n\t\t\tthis.#backoff = INITIAL_BACKOFF_MS;\n\t\t\tthis.#handlers.onConnectionChange?.(true);\n\t\t};\n\t\tws.onmessage = (event) => {\n\t\t\tlet message: { event?: string; data?: unknown };\n\t\t\ttry {\n\t\t\t\tmessage = JSON.parse(event.data as string);\n\t\t\t} catch {\n\t\t\t\treturn; // ignore malformed / non-JSON frames rather than throwing\n\t\t\t}\n\t\t\tif (message.event === 'startstate') {\n\t\t\t\tthis.#handlers.onStartstate?.(message.data as DtoPublicAvailability);\n\t\t\t} else if (message.event === 'seat') {\n\t\t\t\tthis.#handlers.onSeat?.(message.data as SeatDelta);\n\t\t\t}\n\t\t};\n\t\tws.onclose = () => {\n\t\t\tthis.#handlers.onConnectionChange?.(false);\n\t\t\tif (this.#closed) return;\n\t\t\t// Equal-jitter backoff: reconnect somewhere in [backoff/2, backoff) so a\n\t\t\t// mass disconnect doesn't produce a synchronized reconnect thundering herd.\n\t\t\tconst delay = this.#backoff / 2 + Math.random() * (this.#backoff / 2);\n\t\t\tthis.#retry = setTimeout(() => this.#connect(), delay);\n\t\t\tthis.#backoff = Math.min(this.#backoff * 2, MAX_BACKOFF_MS);\n\t\t};\n\t}\n}\n","// The typed core of @bookitive/js: a thin fetch wrapper over the public\n// checkout API, addressed by org + event slug. All response and payload types\n// are generated from the backend's public swagger — they cannot drift.\nimport { DtoErrorCode } from './generated/api.js';\nimport type {\n\tDtoAPIError,\n\tDtoPublicAvailability,\n\tDtoPublicDiscount,\n\tDtoPublicEvent,\n\tDtoPublicOrder,\n\tDtoPublicOrderRequest,\n\tDtoPublicSeating,\n\tDtoPublicVerification,\n} from './generated/api.js';\nimport { LiveSubscription, type LiveHandlers } from './live.js';\n\nexport interface BookitiveClientOptions {\n\t/** API origin, e.g. \"https://api.bookitive.com\" (no trailing slash). */\n\tbaseUrl: string;\n\t/** Organization slug — the {org} in events.bookitive.com/{org}/{event}. */\n\torg: string;\n\t/** Event slug. */\n\tevent: string;\n\t/** Custom fetch (SSR frameworks pass their own). Defaults to global fetch. */\n\tfetch?: typeof fetch;\n}\n\n/** API failure with the backend's error envelope attached. */\nexport class BookitiveError extends Error {\n\treadonly status: number;\n\t/** The backend error code (e.g. DtoErrorCode.ErrConflict), or \"UNKNOWN\" when\n\t * the response carried no JSON envelope. */\n\treadonly type: DtoErrorCode | 'UNKNOWN';\n\n\tconstructor(status: number, body: DtoAPIError | undefined, fallback: string) {\n\t\tsuper(body?.msg ?? fallback);\n\t\tthis.name = 'BookitiveError';\n\t\tthis.status = status;\n\t\tthis.type = body?.type ?? 'UNKNOWN';\n\t}\n\n\t/** The selection raced another buyer — refresh availability and reselect. */\n\tget isConflict(): boolean {\n\t\treturn this.type === DtoErrorCode.ErrConflict || this.status === 409;\n\t}\n}\n\nexport class BookitiveClient {\n\treadonly #base: string;\n\treadonly #fetch: typeof fetch;\n\n\tconstructor(options: BookitiveClientOptions) {\n\t\tthis.#base = `${options.baseUrl.replace(/\\/$/, '')}/public/v1/orgs/${encodeURIComponent(\n\t\t\toptions.org\n\t\t)}/events/${encodeURIComponent(options.event)}`;\n\t\t// bind, or a browser's fetch throws \"Illegal invocation\" when called\n\t\t// detached from window\n\t\tthis.#fetch = options.fetch ?? ((...args) => fetch(...args));\n\t}\n\n\t/** Event identity, org branding, priced ticket types, and floor tabs. */\n\tevent(): Promise<DtoPublicEvent> {\n\t\treturn this.#get<DtoPublicEvent>('');\n\t}\n\n\t/** Static chart geometry: floors → sections → tables, seats, GA areas. */\n\tseating(): Promise<DtoPublicSeating> {\n\t\treturn this.#get<DtoPublicSeating>('/seating');\n\t}\n\n\t/** Live sellability snapshot (poll it, or use live() for push updates). */\n\tavailability(): Promise<DtoPublicAvailability> {\n\t\treturn this.#get<DtoPublicAvailability>('/availability');\n\t}\n\n\t/**\n\t * Previews a promo code: what applying it would do. Throws a\n\t * BookitiveError with status 404 for unknown/paused/out-of-window codes\n\t * and 409 (error.isConflict) when the code is fully redeemed. The order\n\t * endpoint re-validates on submit.\n\t */\n\tdiscount(code: string): Promise<DtoPublicDiscount> {\n\t\treturn this.#get<DtoPublicDiscount>(`/discount?code=${encodeURIComponent(code)}`);\n\t}\n\n\t/**\n\t * Places a bank-transfer reservation: seats are held atomically for the\n\t * event's hold TTL and payment instructions are emailed (and returned).\n\t * A 409 (error.isConflict) means a seat was just taken — refresh and\n\t * reselect.\n\t */\n\tasync checkout(order: DtoPublicOrderRequest): Promise<DtoPublicOrder> {\n\t\tconst res = await this.#fetch(`${this.#base}/orders`, {\n\t\t\tmethod: 'POST',\n\t\t\theaders: { 'Content-Type': 'application/json' },\n\t\t\tbody: JSON.stringify(order),\n\t\t});\n\t\treturn this.#parse<DtoPublicOrder>(res, 'checkout failed');\n\t}\n\n\t/**\n\t * Sends a one-time code by SMS for events that require a verified phone\n\t * (event.protection.requireVerifiedPhone). Captcha-gated like checkout, so\n\t * pass a captcha token minted on the Bookitive origin. Throws a\n\t * BookitiveError: 400 (bad/disallowed number), 403 (captcha/blocked), or\n\t * 429 (cooldown/rate limit — back off and let the buyer retry).\n\t */\n\tasync verifyStart(phone: string, captchaToken?: string): Promise<void> {\n\t\tconst res = await this.#fetch(`${this.#base}/verify/start`, {\n\t\t\tmethod: 'POST',\n\t\t\theaders: { 'Content-Type': 'application/json' },\n\t\t\tbody: JSON.stringify({ phone, captchaToken }),\n\t\t});\n\t\tawait this.#parse<void>(res, 'failed to send verification code');\n\t}\n\n\t/**\n\t * Validates the SMS code and returns a single-use verificationToken (valid\n\t * ~30 min) to pass as order.verificationToken. Throws a BookitiveError:\n\t * 400 (wrong code), 410 (expired — resend), or 429 (too many attempts).\n\t */\n\tverifyCheck(phone: string, code: string): Promise<DtoPublicVerification> {\n\t\treturn this.#post<DtoPublicVerification>('/verify/check', { phone, code });\n\t}\n\n\t/**\n\t * Subscribes to live availability over websocket: `startstate` fires with\n\t * the full snapshot on every (re)connect, then `seat` deltas stream in.\n\t * Reconnects automatically with backoff; call close() when done.\n\t */\n\tlive(handlers: LiveHandlers): LiveSubscription {\n\t\treturn new LiveSubscription(`${this.#base}/live`, handlers);\n\t}\n\n\tasync #get<T>(path: string): Promise<T> {\n\t\tconst res = await this.#fetch(`${this.#base}${path}`);\n\t\treturn this.#parse<T>(res, `request failed: ${path || '/'}`);\n\t}\n\n\tasync #post<T>(path: string, body: unknown): Promise<T> {\n\t\tconst res = await this.#fetch(`${this.#base}${path}`, {\n\t\t\tmethod: 'POST',\n\t\t\theaders: { 'Content-Type': 'application/json' },\n\t\t\tbody: JSON.stringify(body),\n\t\t});\n\t\treturn this.#parse<T>(res, `request failed: ${path}`);\n\t}\n\n\tasync #parse<T>(res: Response, fallback: string): Promise<T> {\n\t\tif (!res.ok) {\n\t\t\tlet body: DtoAPIError | undefined;\n\t\t\ttry {\n\t\t\t\tbody = (await res.json()) as DtoAPIError;\n\t\t\t} catch {\n\t\t\t\t// non-JSON error body — the fallback message covers it\n\t\t\t}\n\t\t\tthrow new BookitiveError(res.status, body, fallback);\n\t\t}\n\t\t// 204/205 never carry a body. Otherwise read the text and parse only\n\t\t// when it's non-empty — this avoids res.headers.get('content-length'),\n\t\t// which throws under SvelteKit's SSR fetch (response headers aren't\n\t\t// serialized by default) and would otherwise 500 any server-side load.\n\t\tif (res.status === 204 || res.status === 205) {\n\t\t\treturn undefined as T;\n\t\t}\n\t\tconst text = await res.text();\n\t\treturn (text ? JSON.parse(text) : undefined) as T;\n\t}\n}\n"],"mappings":"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","names":["DtoErrorCode"]}
1
+ {"version":3,"sources":["../src/generated/api.ts","../src/live.ts","../src/fingerprint.ts","../src/client.ts","../src/checkoutErrors.ts"],"sourcesContent":["/* eslint-disable */\n/* tslint:disable */\n// @ts-nocheck\n/*\n * ---------------------------------------------------------------\n * ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API ##\n * ## ##\n * ## AUTHOR: acacode ##\n * ## SOURCE: https://github.com/acacode/swagger-typescript-api ##\n * ---------------------------------------------------------------\n */\n\n/**\n * Type is the stable, machine-readable error code.\n * @example \"CONFLICT\"\n */\nexport enum DtoErrorCode {\n\tErrInvalidPayload = 'INVALID_PAYLOAD',\n\tErrValidationFailed = 'VALIDATION_FAILED',\n\tErrNotFound = 'NOT_FOUND',\n\tErrConflict = 'CONFLICT',\n\tErrForbidden = 'FORBIDDEN',\n\tErrVerificationRequired = 'VERIFICATION_REQUIRED',\n\tErrInternal = 'INTERNAL_ERROR',\n}\n\nexport interface DtoAPIError {\n\t/** Data carries optional structured context for the error. */\n\tdata?: any;\n\t/** Fields maps a request field to a validation message, when applicable. */\n\tfields?: Record<string, string>;\n\t/**\n\t * Msg is a human-readable description of what went wrong.\n\t * @example \"seat is no longer available\"\n\t */\n\tmsg?: string;\n\t/**\n\t * Timestamp is when the error was produced (RFC 3339).\n\t * @example \"2026-06-20T18:00:00Z\"\n\t */\n\ttimestamp?: string;\n\t/**\n\t * TraceID identifies this request in our logs; include it in support asks.\n\t * @example \"01JABCDoftraceZ\"\n\t */\n\ttraceId?: string;\n\t/** Type is the stable, machine-readable error code. */\n\ttype?: DtoErrorCode;\n}\n\nexport interface DtoPublicAvailability {\n\t/** GARemaining maps a standing area id to its remaining capacity. */\n\tgaRemaining?: Record<string, number>;\n\t/**\n\t * LowStockThreshold is where \"low stock\" begins for this event — clients\n\t * show the low-stock line when remaining ≤ threshold. 0 = signal off.\n\t * @example 10\n\t */\n\tlowStockThreshold?: number;\n\t/**\n\t * RecentOrders counts orders placed in the last 30 minutes — a live\n\t * activity signal for urgency UI. Always 0 when the event disabled the\n\t * social-proof signal; clients choose their display threshold.\n\t * @example 4\n\t */\n\trecentOrders?: number;\n\t/**\n\t * ReservedSeats marks which unavailable seats are merely reserved and may\n\t * free up again — present only when the event shares reserved status.\n\t * @uniqueItems false\n\t * @example [\"1810000000000000502\"]\n\t */\n\treservedSeats?: string[];\n\t/** TicketTypeRemaining maps a ticket type id to its remaining capacity. */\n\tticketTypeRemaining?: Record<string, number>;\n\t/**\n\t * UnavailableSeats lists the ids of seats that cannot currently be bought.\n\t * @uniqueItems false\n\t * @example [\"1810000000000000501\",\"1810000000000000502\"]\n\t */\n\tunavailableSeats?: string[];\n}\n\n/** CheckoutTheme customises the hosted checkout; null = Bookitive defaults. */\nexport interface DtoPublicCheckoutTheme {\n\t/**\n\t * ButtonLabel overrides the submit button text.\n\t * @example \"Objednat\"\n\t */\n\tbuttonLabel?: string;\n\t/**\n\t * FontFamily is an allowlisted font key (self-hosted by Bookitive).\n\t * @example \"inter\"\n\t */\n\tfontFamily?: string;\n\t/**\n\t * Mode is the colour scheme: \"light\" or \"dark\".\n\t * @example \"light\"\n\t */\n\tmode?: 'light' | 'dark';\n\t/**\n\t * Radius is the corner radius for fields and buttons.\n\t * @example \"12px\"\n\t */\n\tradius?: string;\n}\n\nexport interface DtoPublicDiscount {\n\t/**\n\t * Code is the normalized (uppercase) promo code.\n\t * @example \"EARLYBIRD\"\n\t */\n\tcode?: string;\n\t/**\n\t * Kind is how the discount applies: \"percent\" of the items subtotal, or a\n\t * \"fixed\" amount in the event currency's minor units.\n\t * @example \"percent\"\n\t */\n\tkind?: 'percent' | 'fixed';\n\t/**\n\t * Value is 1-100 for percent, minor units for fixed.\n\t * @example 10\n\t */\n\tvalue?: number;\n}\n\nexport interface DtoPublicEvent {\n\t/** Event is the event's public identity: name, times, venue, currency. */\n\tevent?: DtoPublicEventInfo;\n\t/**\n\t * Floors lets a host page render its floor tabs without loading the full\n\t * seating payload; ids match the seating endpoint's floors.\n\t * @uniqueItems false\n\t */\n\tfloors?: DtoPublicFloorInfo[];\n\t/** Org is the organizer's public branding (name, logo, accent colour). */\n\torg?: DtoPublicOrg;\n\t/**\n\t * Protection describes the anti-scalping requirements a buyer must satisfy\n\t * at checkout (verified phone, named tickets). All-false when unprotected.\n\t */\n\tprotection?: DtoPublicProtection;\n\t/**\n\t * TicketTypes are the sellable ticket categories with prices and limits.\n\t * @uniqueItems false\n\t */\n\tticketTypes?: DtoPublicTicketType[];\n}\n\n/** Event is the event's public identity: name, times, venue, currency. */\nexport interface DtoPublicEventInfo {\n\t/**\n\t * Currency is the ISO 4217 code all prices are quoted in.\n\t * @example \"CZK\"\n\t */\n\tcurrency?: string;\n\t/**\n\t * EndTime is when the event ends (RFC 3339), when known.\n\t * @example \"2026-06-20T23:00:00Z\"\n\t */\n\tendTime?: string;\n\t/**\n\t * HoldTTLSeconds is how long a checkout holds selected seats before expiry.\n\t * @example 900\n\t */\n\tholdTtlSeconds?: number;\n\t/**\n\t * LocationName is the venue's display name, when set.\n\t * @example \"Výstaviště Praha\"\n\t */\n\tlocationName?: string;\n\t/**\n\t * Name is the event's public display name.\n\t * @example \"Rockfest 2026\"\n\t */\n\tname?: string;\n\t/**\n\t * Slug is the event's URL identifier (the {eventSlug} path parameter).\n\t * @example \"rockfest-2026\"\n\t */\n\tslug?: string;\n\t/**\n\t * StartTime is when doors/the event begins (RFC 3339).\n\t * @example \"2026-06-20T18:00:00Z\"\n\t */\n\tstartTime?: string;\n}\n\nexport interface DtoPublicFloor {\n\t/**\n\t * Cons are short caveats for this floor, shown alongside the pros.\n\t * @uniqueItems false\n\t * @example [\"Standing only\"]\n\t */\n\tcons?: string[];\n\t/**\n\t * Description is optional customer-facing copy shown on the floor's tab.\n\t * @example \"Standing area in front of the main stage\"\n\t */\n\tdescription?: string;\n\t/**\n\t * ID is the floor's stable id; it matches PublicFloorInfo.id.\n\t * @example \"1810000000000000101\"\n\t */\n\tid?: string;\n\t/**\n\t * Mode is \"single\" (one open space) or \"sections\" (subdivided blocks).\n\t * @example \"sections\"\n\t */\n\tmode?: 'single' | 'sections';\n\t/**\n\t * Name is the floor's display label.\n\t * @example \"Ground floor\"\n\t */\n\tname?: string;\n\t/**\n\t * Position is the 1-based tab order among the event's floors.\n\t * @example 1\n\t */\n\tposition?: number;\n\t/**\n\t * Pros are short selling points for this floor, shown as a checklist.\n\t * @uniqueItems false\n\t * @example [\"Closest to the stage\",\"Great atmosphere\"]\n\t */\n\tpros?: string[];\n\t/**\n\t * Sections are the floor's blocks; a single-mode floor has exactly one.\n\t * @uniqueItems false\n\t */\n\tsections?: DtoPublicSection[];\n}\n\nexport interface DtoPublicFloorInfo {\n\t/**\n\t * Cons are short caveats for this floor, shown alongside the pros.\n\t * @uniqueItems false\n\t * @example [\"Standing only\"]\n\t */\n\tcons?: string[];\n\t/**\n\t * Description is optional customer-facing copy shown on the floor's tab.\n\t * @example \"Standing area in front of the main stage\"\n\t */\n\tdescription?: string;\n\t/**\n\t * ID is the floor's stable id; it matches a floor in the seating endpoint.\n\t * @example \"1810000000000000101\"\n\t */\n\tid?: string;\n\t/**\n\t * Mode is \"single\" (one open space) or \"sections\" (subdivided blocks).\n\t * @example \"sections\"\n\t */\n\tmode?: 'single' | 'sections';\n\t/**\n\t * Name is the floor's display label.\n\t * @example \"Ground floor\"\n\t */\n\tname?: string;\n\t/**\n\t * Position is the 1-based tab order among the event's floors.\n\t * @example 1\n\t */\n\tposition?: number;\n\t/**\n\t * Pros are short selling points for this floor, shown as a checklist.\n\t * @uniqueItems false\n\t * @example [\"Closest to the stage\",\"Great atmosphere\"]\n\t */\n\tpros?: string[];\n}\n\nexport interface DtoPublicGAArea {\n\t/**\n\t * Capacity is the total number of standing tickets this area can sell.\n\t * @example 500\n\t */\n\tcapacity?: number;\n\t/**\n\t * H is the area's height on the floor canvas.\n\t * @example 200\n\t */\n\th?: number;\n\t/**\n\t * ID is the standing area's stable id, referenced by availability/orders.\n\t * @example \"1810000000000000601\"\n\t */\n\tid?: string;\n\t/**\n\t * Label is the standing area's display name.\n\t * @example \"Pit\"\n\t */\n\tlabel?: string;\n\t/**\n\t * Rotation is the area's rotation in degrees.\n\t * @example 0\n\t */\n\trotation?: number;\n\t/**\n\t * TicketTypeID is the standing ticket type mapped to this area, when priced.\n\t * @example \"1810000000000000201\"\n\t */\n\tticketTypeId?: string;\n\t/**\n\t * W is the area's width on the floor canvas.\n\t * @example 600\n\t */\n\tw?: number;\n\t/**\n\t * X is the area's left offset on the floor canvas.\n\t * @example 100\n\t */\n\tx?: number;\n\t/**\n\t * Y is the area's top offset on the floor canvas.\n\t * @example 400\n\t */\n\ty?: number;\n}\n\nexport interface DtoPublicOrder {\n\t/**\n\t * Currency is the ISO 4217 code the total is quoted in.\n\t * @example \"CZK\"\n\t */\n\tcurrency?: string;\n\t/**\n\t * DiscountCents is what the redeemed promo code took off the total.\n\t * @example 14900\n\t */\n\tdiscountCents?: number;\n\t/**\n\t * ExpiresAt is when the held seats and this order expire (RFC 3339).\n\t * @example \"2026-06-20T18:15:00Z\"\n\t */\n\texpiresAt?: string;\n\t/** Payment carries the bank-transfer instructions for pending orders. */\n\tpayment?: DtoPublicPaymentDetail;\n\t/**\n\t * Reference is the order's human-facing reference (also the payment symbol).\n\t * @example \"RF26-000142\"\n\t */\n\treference?: string;\n\t/**\n\t * Status is the order lifecycle state at creation time. pending_review means\n\t * the order is held for organizer approval (RFC 0004 risk \"hold\" action).\n\t * @example \"pending\"\n\t */\n\tstatus?: 'pending' | 'pending_review' | 'paid' | 'cancelled' | 'expired';\n\t/**\n\t * TotalCents is the amount due in the event currency's minor units.\n\t * @example 149000\n\t */\n\ttotalCents?: number;\n}\n\n/** Customer is the buyer's contact details. */\nexport interface DtoPublicOrderCustomer {\n\t/**\n\t * Email is where the payment instructions and tickets are sent.\n\t * @example \"jana@example.com\"\n\t */\n\temail: string;\n\t/**\n\t * Name is the buyer's full name.\n\t * @maxLength 200\n\t * @example \"Jana Nováková\"\n\t */\n\tname: string;\n\t/**\n\t * Phone is an optional contact number.\n\t * @maxLength 50\n\t * @example \"+420123456789\"\n\t */\n\tphone?: string;\n}\n\nexport interface DtoPublicOrderItem {\n\t/**\n\t * Attendees carries one full name per admitted person when the event\n\t * requires named tickets — exactly Quantity names, in ticket order.\n\t * @uniqueItems false\n\t * @example [\"Jana Nováková\"]\n\t */\n\tattendees?: string[];\n\t/**\n\t * GAAreaID is the standing area's id for standing ticket types.\n\t * @example \"1810000000000000601\"\n\t */\n\tgaAreaId?: string;\n\t/**\n\t * Quantity is how many to buy — always 1 for a seat, 1+ for standing.\n\t * @min 1\n\t * @example 1\n\t */\n\tquantity: number;\n\t/**\n\t * SeatID is the chosen seat's id for seated ticket types.\n\t * @example \"1810000000000000501\"\n\t */\n\tseatId?: string;\n\t/**\n\t * TicketTypeID is the ticket type being bought (from the event endpoint).\n\t * @example \"1810000000000000201\"\n\t */\n\tticketTypeId: string;\n}\n\nexport interface DtoPublicOrderRequest {\n\t/**\n\t * CaptchaToken is the client-side captcha solution, verified server-side.\n\t * @example \"03AGdBq26…\"\n\t */\n\tcaptchaToken?: string;\n\t/** Customer is the buyer's contact details. */\n\tcustomer: DtoPublicOrderCustomer;\n\t/**\n\t * DiscountCode redeems a promo code (case-insensitive); the discount is\n\t * applied to the items subtotal and enforced against the code's limits.\n\t * @minLength 2\n\t * @maxLength 40\n\t * @example \"EARLYBIRD\"\n\t */\n\tdiscountCode?: string;\n\t/**\n\t * Fingerprint is a stable hash of the buyer's browser, computed client-side\n\t * and forwarded for device-velocity risk scoring (RFC 0004); optional.\n\t * @maxLength 128\n\t * @example \"a1b2c3d4e5f6\"\n\t */\n\tfingerprint?: string;\n\t/**\n\t * Items are the order lines; at least one is required.\n\t * @minItems 1\n\t * @uniqueItems false\n\t */\n\titems: DtoPublicOrderItem[];\n\t/**\n\t * VerificationToken is the single-use token from the verify/check endpoint,\n\t * required when the event demands a verified phone number.\n\t * @example \"3q2p8Zk1…\"\n\t */\n\tverificationToken?: string;\n}\n\n/** Org is the organizer's public branding (name, logo, accent colour). */\nexport interface DtoPublicOrg {\n\t/**\n\t * AccentColor is the organizer's brand colour as a hex string, if set.\n\t * @example \"#E5482F\"\n\t */\n\taccentColor?: string;\n\t/** CheckoutTheme customises the hosted checkout; null = Bookitive defaults. */\n\tcheckoutTheme?: DtoPublicCheckoutTheme;\n\t/**\n\t * LogoURL is an absolute URL to the organizer's logo, if set.\n\t * @example \"https://cdn.bookitive.com/orgs/rockfest/logo.png\"\n\t */\n\tlogoUrl?: string;\n\t/**\n\t * Name is the organizer's public display name.\n\t * @example \"Rockfest Productions\"\n\t */\n\tname?: string;\n\t/**\n\t * PrivacyURL is the organizer's own privacy policy.\n\t * @example \"https://rockfest.cz/ochrana-osobnich-udaju\"\n\t */\n\tprivacyUrl?: string;\n\t/**\n\t * Slug is the organizer's URL identifier (the {orgSlug} path parameter).\n\t * @example \"rockfest\"\n\t */\n\tslug?: string;\n\t/**\n\t * TermsURL is the organizer's own terms of sale — the organizer is the\n\t * seller, so the checkout links these next to Bookitive's buyer notice.\n\t * @example \"https://rockfest.cz/obchodni-podminky\"\n\t */\n\ttermsUrl?: string;\n}\n\n/** Payment carries the bank-transfer instructions for pending orders. */\nexport interface DtoPublicPaymentDetail {\n\t/**\n\t * BankAccount is the organizer's free-form payment destination.\n\t * @example \"123456789/0100\"\n\t */\n\tbankAccount?: string;\n\t/**\n\t * VariableSymbol identifies the payment — it equals the order reference.\n\t * @example \"26000142\"\n\t */\n\tvariableSymbol?: string;\n}\n\n/**\n * Protection describes the anti-scalping requirements a buyer must satisfy\n * at checkout (verified phone, named tickets). All-false when unprotected.\n */\nexport interface DtoPublicProtection {\n\t/**\n\t * PhonePrefixes is the allowlist of accepted international dialling codes\n\t * (e.g. \"+420\"). Only meaningful when RequireVerifiedPhone is set.\n\t * @uniqueItems false\n\t * @example [\"+420\",\"+421\"]\n\t */\n\tphonePrefixes?: string[];\n\t/**\n\t * RequireAttendeeNames demands one attendee name per admitted person.\n\t * @example false\n\t */\n\trequireAttendeeNames?: boolean;\n\t/**\n\t * RequireVerifiedPhone gates checkout behind SMS phone verification.\n\t * @example true\n\t */\n\trequireVerifiedPhone?: boolean;\n}\n\nexport interface DtoPublicSeat {\n\t/**\n\t * Code is the human-facing seat label (section prefix + table + seat).\n\t * @example \"L12-3\"\n\t */\n\tcode?: string;\n\t/**\n\t * ID is the seat's stable id; availability and orders reference it.\n\t * @example \"1810000000000000501\"\n\t */\n\tid?: string;\n\t/**\n\t * TableID is the owning table's id, when the seat belongs to a table.\n\t * @example \"1810000000000000401\"\n\t */\n\ttableId?: string;\n\t/**\n\t * TicketTypeID is the ticket type mapped to this seat, when priced.\n\t * @example \"1810000000000000201\"\n\t */\n\tticketTypeId?: string;\n\t/**\n\t * X is the seat centre's x on the floor canvas.\n\t * @example 245\n\t */\n\tx?: number;\n\t/**\n\t * Y is the seat centre's y on the floor canvas.\n\t * @example 158\n\t */\n\ty?: number;\n}\n\nexport interface DtoPublicSeating {\n\t/**\n\t * Floors are the chart's floors in tab order, each with its geometry.\n\t * @uniqueItems false\n\t */\n\tfloors?: DtoPublicFloor[];\n}\n\nexport interface DtoPublicSection {\n\t/** Decorations is opaque visual scenery (stage, walls, doors) — never sold. */\n\tdecorations?: object;\n\t/**\n\t * GAAreas are the standing areas (sold by quantity) in this section.\n\t * @uniqueItems false\n\t */\n\tgaAreas?: DtoPublicGAArea[];\n\t/**\n\t * H is the section's height on the floor canvas, if sized.\n\t * @example 300\n\t */\n\th?: number;\n\t/**\n\t * ID is the section's stable id.\n\t * @example \"1810000000000000301\"\n\t */\n\tid?: string;\n\t/**\n\t * Name is the section's display label.\n\t * @example \"Left block\"\n\t */\n\tname?: string;\n\t/** Polygon is the section's outline as an array of {x,y} points, if shaped. */\n\tpolygon?: object;\n\t/**\n\t * Prefix is prepended to seat codes in this section (e.g. \"L\" → \"L12-3\").\n\t * @example \"L\"\n\t */\n\tprefix?: string;\n\t/**\n\t * Seats are the individually sellable seats in this section.\n\t * @uniqueItems false\n\t */\n\tseats?: DtoPublicSeat[];\n\t/** Settings is opaque render config (chair gap/size, label typography). */\n\tsettings?: object;\n\t/**\n\t * Tables are the seated tables in this section.\n\t * @uniqueItems false\n\t */\n\ttables?: DtoPublicTable[];\n\t/**\n\t * W is the section's width on the floor canvas, if sized.\n\t * @example 400\n\t */\n\tw?: number;\n\t/**\n\t * X is the section's left offset on the floor canvas, if positioned.\n\t * @example 120\n\t */\n\tx?: number;\n\t/**\n\t * Y is the section's top offset on the floor canvas, if positioned.\n\t * @example 80\n\t */\n\ty?: number;\n}\n\nexport interface DtoPublicTable {\n\t/**\n\t * Curve is the arc for row tables, in degrees (0 = straight row).\n\t * @example 0\n\t */\n\tcurve?: number;\n\t/**\n\t * H is the height for rect tables, in canvas units.\n\t * @example 60\n\t */\n\th?: number;\n\t/**\n\t * ID is the table's stable id, referenced by PublicSeat.tableId.\n\t * @example \"1810000000000000401\"\n\t */\n\tid?: string;\n\t/**\n\t * Number is the table's display number within its section.\n\t * @example 12\n\t */\n\tnumber?: number;\n\t/**\n\t * Radius is the radius for round tables, in canvas units.\n\t * @example 45\n\t */\n\tradius?: number;\n\t/**\n\t * Rotation is the table's rotation in degrees.\n\t * @example 0\n\t */\n\trotation?: number;\n\t/**\n\t * Shape is the table outline: \"round\", \"rect\", or \"row\".\n\t * @example \"round\"\n\t */\n\tshape?: 'round' | 'rect' | 'row';\n\t/**\n\t * Sides is the seat count per edge for rect tables [top,right,bottom,left].\n\t * @uniqueItems false\n\t */\n\tsides?: number[];\n\t/**\n\t * Spacing is the gap between seats on a row table, in canvas units.\n\t * @example 24\n\t */\n\tspacing?: number;\n\t/**\n\t * W is the width for rect tables, in canvas units.\n\t * @example 120\n\t */\n\tw?: number;\n\t/**\n\t * X is the table centre's x on the floor canvas.\n\t * @example 240.5\n\t */\n\tx?: number;\n\t/**\n\t * Y is the table centre's y on the floor canvas.\n\t * @example 160\n\t */\n\ty?: number;\n}\n\nexport interface DtoPublicTicketType {\n\t/**\n\t * BasePriceCents is the regular price, present only when a scheduled phase is\n\t * currently active and differs from it — so the widget can show \"was €X\".\n\t * @example 179000\n\t */\n\tbasePriceCents?: number;\n\t/**\n\t * Color is the swatch used to tint seats/areas of this type, as hex.\n\t * @example \"#E5482F\"\n\t */\n\tcolor?: string;\n\t/**\n\t * Cons are short caveats for this ticket type, shown alongside the pros.\n\t * @uniqueItems false\n\t * @example [\"No seating\"]\n\t */\n\tcons?: string[];\n\t/**\n\t * Description is optional customer-facing copy for this ticket type.\n\t * @example \"Front-stage standing with a dedicated bar\"\n\t */\n\tdescription?: string;\n\t/**\n\t * ID is the ticket type's stable id, referenced by order items and seats.\n\t * @example \"1810000000000000201\"\n\t */\n\tid?: string;\n\t/**\n\t * Kind is \"seated\" (maps to a seat) or \"standing\" (sold by quantity).\n\t * @example \"standing\"\n\t */\n\tkind?: 'seated' | 'standing';\n\t/**\n\t * MaxPerCustomer caps how many of this type one verified buyer may hold\n\t * across orders (anti-scalping); absent when unlimited.\n\t * @example 4\n\t */\n\tmaxPerCustomer?: number;\n\t/**\n\t * MaxPerOrder caps how many of this type a single order may contain.\n\t * @example 6\n\t */\n\tmaxPerOrder?: number;\n\t/**\n\t * MinPerOrder appears only when it is a real constraint (> 1) — the\n\t * limit applies to orders that include this type at all.\n\t * @example 1\n\t */\n\tminPerOrder?: number;\n\t/**\n\t * Name is the ticket type's display label.\n\t * @example \"VIP\"\n\t */\n\tname?: string;\n\t/**\n\t * PriceCents is the EFFECTIVE unit price right now, in the event currency's\n\t * minor units — the base price, or an active scheduled phase's price (RFC\n\t * 0005). This is what the buyer pays.\n\t * @example 149000\n\t */\n\tpriceCents?: number;\n\t/**\n\t * PriceChangesAt is the next scheduled price boundary (RFC 3339) — the widget\n\t * can count down \"early bird ends in …\"; absent when the price won't change.\n\t * @example \"2026-06-01T00:00:00Z\"\n\t */\n\tpriceChangesAt?: string;\n\t/**\n\t * PricePhaseDescription is the active phase's optional organizer-authored\n\t * copy, shown in the widget's phase info popover.\n\t * @example \"Launch price for the first hundred fans.\"\n\t */\n\tpricePhaseDescription?: string;\n\t/**\n\t * PricePhaseName is the active phase's label (e.g. \"Early bird\"), if any.\n\t * @example \"Early bird\"\n\t */\n\tpricePhaseName?: string;\n\t/**\n\t * PricePhaseRemaining is how many tickets are left at the active phase's\n\t * price before its quantity cap (RFC 0005) — the \"12 left at early bird\"\n\t * urgency; absent when the active phase has no quantity cap.\n\t * @example 12\n\t */\n\tpricePhaseRemaining?: number;\n\t/**\n\t * Pros are short selling points for this ticket type, shown as a checklist.\n\t * @uniqueItems false\n\t * @example [\"Best view\",\"Dedicated bar\",\"Fast-track entry\"]\n\t */\n\tpros?: string[];\n\t/**\n\t * SaleEndTime is when this type stops selling (RFC 3339), if scheduled.\n\t * @example \"2026-06-20T16:00:00Z\"\n\t */\n\tsaleEndTime?: string;\n\t/**\n\t * SaleStartTime is when this type goes on sale (RFC 3339), if scheduled.\n\t * @example \"2026-01-15T10:00:00Z\"\n\t */\n\tsaleStartTime?: string;\n}\n\nexport interface DtoPublicVerification {\n\t/**\n\t * ExpiresAt is when the token stops being accepted (RFC 3339).\n\t * @example \"2026-06-20T18:30:00Z\"\n\t */\n\texpiresAt?: string;\n\t/**\n\t * Phone is the normalized number the token certifies.\n\t * @example \"+420777123456\"\n\t */\n\tphone?: string;\n\t/**\n\t * VerificationToken is consumed by exactly one order for this event.\n\t * @example \"5Yx…\"\n\t */\n\tverificationToken?: string;\n}\n\nexport interface DtoPublicVerifyCheckRequest {\n\t/**\n\t * Code is the OTP from the SMS.\n\t * @minLength 4\n\t * @maxLength 10\n\t * @example \"123456\"\n\t */\n\tcode: string;\n\t/**\n\t * @minLength 8\n\t * @maxLength 20\n\t * @example \"+420777123456\"\n\t */\n\tphone: string;\n}\n\nexport interface DtoPublicVerifyStartRequest {\n\t/**\n\t * CaptchaToken is the client-side captcha solution, verified server-side.\n\t * @example \"03AGdBq26…\"\n\t */\n\tcaptchaToken?: string;\n\t/**\n\t * Phone in international E.164 format.\n\t * @minLength 8\n\t * @maxLength 20\n\t * @example \"+420777123456\"\n\t */\n\tphone: string;\n}\n\nexport type ResponseAPIResponseEmpty = object;\n","// Live availability over websocket: {\"event\":\"startstate\"|\"seat\", data}.\n// Startstate re-fires on every reconnect, so consumers treat it as the\n// resync point and apply deltas on top.\nimport type { DtoPublicAvailability } from './generated/api';\n\n/** One seat changing state. \"reserved\" appears only when the event shares\n * reserved status publicly — a reserved seat may free up again. */\nexport interface SeatDelta {\n\tseatId: string;\n\ttaken: boolean;\n\tstatus: 'free' | 'reserved' | 'taken';\n}\n\nexport interface LiveHandlers {\n\t/** Full snapshot — on connect and on every reconnect. */\n\tonStartstate?: (availability: DtoPublicAvailability) => void;\n\t/** One seat changed. */\n\tonSeat?: (delta: SeatDelta) => void;\n\t/** Connection state changes (drive a \"live\" indicator if you like). */\n\tonConnectionChange?: (connected: boolean) => void;\n}\n\nconst INITIAL_BACKOFF_MS = 1000;\nconst MAX_BACKOFF_MS = 15000;\n\nexport class LiveSubscription {\n\t#url: string;\n\t#handlers: LiveHandlers;\n\t#ws: WebSocket | null = null;\n\t#backoff = INITIAL_BACKOFF_MS;\n\t#retry: ReturnType<typeof setTimeout> | null = null;\n\t#closed = false;\n\n\tconstructor(url: string, handlers: LiveHandlers) {\n\t\tthis.#url = url.replace(/^http/, 'ws');\n\t\tthis.#handlers = handlers;\n\t\tthis.#connect();\n\t}\n\n\tclose(): void {\n\t\tthis.#closed = true;\n\t\tif (this.#retry) clearTimeout(this.#retry);\n\t\tthis.#ws?.close();\n\t}\n\n\t#connect(): void {\n\t\tif (this.#closed) return;\n\t\tconst ws = new WebSocket(this.#url);\n\t\tthis.#ws = ws;\n\t\tws.onopen = () => {\n\t\t\tthis.#backoff = INITIAL_BACKOFF_MS;\n\t\t\tthis.#handlers.onConnectionChange?.(true);\n\t\t};\n\t\tws.onmessage = (event) => {\n\t\t\tlet message: { event?: string; data?: unknown };\n\t\t\ttry {\n\t\t\t\tmessage = JSON.parse(event.data as string);\n\t\t\t} catch {\n\t\t\t\treturn; // ignore malformed / non-JSON frames rather than throwing\n\t\t\t}\n\t\t\tif (message.event === 'startstate') {\n\t\t\t\tthis.#handlers.onStartstate?.(message.data as DtoPublicAvailability);\n\t\t\t} else if (message.event === 'seat') {\n\t\t\t\tthis.#handlers.onSeat?.(message.data as SeatDelta);\n\t\t\t}\n\t\t};\n\t\tws.onclose = () => {\n\t\t\tthis.#handlers.onConnectionChange?.(false);\n\t\t\tif (this.#closed) return;\n\t\t\t// Equal-jitter backoff: reconnect somewhere in [backoff/2, backoff) so a\n\t\t\t// mass disconnect doesn't produce a synchronized reconnect thundering herd.\n\t\t\tconst delay = this.#backoff / 2 + Math.random() * (this.#backoff / 2);\n\t\t\tthis.#retry = setTimeout(() => this.#connect(), delay);\n\t\t\tthis.#backoff = Math.min(this.#backoff * 2, MAX_BACKOFF_MS);\n\t\t};\n\t}\n}\n","// A lightweight, dependency-free browser device fingerprint for the anti-scalping\n// device_velocity signal (RFC 0004). It is intentionally coarse and privacy-light:\n// a stable-ish hash of a handful of ambient browser/screen traits, NOT a unique\n// per-user identifier. Its only job is to make one machine placing many orders —\n// even behind rotated emails/phones/proxies — cluster together server-side.\n//\n// It never throws and returns undefined off the browser (SSR) or when the traits\n// are unavailable, so callers can attach it opportunistically.\n\n// djb2 — a tiny, fast string hash; hex output keeps the value short + opaque.\nfunction hash(input: string): string {\n\tlet h = 5381;\n\tfor (let i = 0; i < input.length; i++) {\n\t\th = (h * 33) ^ input.charCodeAt(i);\n\t}\n\t// >>> 0 coerces to an unsigned 32-bit int before hex encoding.\n\treturn (h >>> 0).toString(16).padStart(8, '0');\n}\n\n/**\n * Compute a coarse device fingerprint for the current browser, or undefined when\n * not in a browser / when the ambient traits can't be read. Combines a few stable\n * traits (UA, language, platform, screen geometry, timezone, hardware hints) into\n * one short hash. Deliberately best-effort — two different browsers on the same\n * machine hash differently, which is fine: it only needs to cluster reuse.\n */\nexport function deviceFingerprint(): string | undefined {\n\ttry {\n\t\tif (typeof navigator === 'undefined' || typeof screen === 'undefined') return undefined;\n\t\tconst nav = navigator as Navigator & { deviceMemory?: number };\n\t\tconst traits = [\n\t\t\tnav.userAgent,\n\t\t\tnav.language,\n\t\t\t(nav.languages ?? []).join(','),\n\t\t\tnav.platform,\n\t\t\tnav.hardwareConcurrency,\n\t\t\tnav.deviceMemory,\n\t\t\tscreen.width,\n\t\t\tscreen.height,\n\t\t\tscreen.colorDepth,\n\t\t\t// Timezone offset is stable per machine locale and cheap to read.\n\t\t\tnew Date().getTimezoneOffset(),\n\t\t\tIntl.DateTimeFormat().resolvedOptions().timeZone,\n\t\t];\n\t\treturn hash(traits.map((t) => String(t ?? '')).join('|'));\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n","// The typed core of @bookitive/js: a thin fetch wrapper over the public\n// checkout API, addressed by org + event slug. All response and payload types\n// are generated from the backend's public swagger — they cannot drift.\nimport { DtoErrorCode } from './generated/api';\nimport type {\n\tDtoAPIError,\n\tDtoPublicAvailability,\n\tDtoPublicDiscount,\n\tDtoPublicEvent,\n\tDtoPublicOrder,\n\tDtoPublicOrderRequest,\n\tDtoPublicSeating,\n\tDtoPublicVerification,\n} from './generated/api';\nimport { LiveSubscription, type LiveHandlers } from './live';\nimport { deviceFingerprint } from './fingerprint';\n\nexport interface BookitiveClientOptions {\n\t/** API origin, e.g. \"https://api.bookitive.com\" (no trailing slash). */\n\tbaseUrl: string;\n\t/** Organization slug — the {org} in events.bookitive.com/{org}/{event}. */\n\torg: string;\n\t/** Event slug. */\n\tevent: string;\n\t/** Custom fetch (SSR frameworks pass their own). Defaults to global fetch. */\n\tfetch?: typeof fetch;\n}\n\n/** API failure with the backend's error envelope attached. */\nexport class BookitiveError extends Error {\n\treadonly status: number;\n\t/** The backend error code (e.g. DtoErrorCode.ErrConflict), or \"UNKNOWN\" when\n\t * the response carried no JSON envelope. */\n\treadonly type: DtoErrorCode | 'UNKNOWN';\n\t/** Structured detail for some codes (e.g. PER_ORDER_LIMITS carries\n\t * ticketType/min/max/requested) — feeds checkoutErrorMessage(). */\n\treadonly fields?: Record<string, string>;\n\n\tconstructor(status: number, body: DtoAPIError | undefined, fallback: string) {\n\t\tsuper(body?.msg ?? fallback);\n\t\tthis.name = 'BookitiveError';\n\t\tthis.status = status;\n\t\tthis.type = body?.type ?? 'UNKNOWN';\n\t\tthis.fields = body?.fields;\n\t}\n\n\t/** The selection raced another buyer — refresh availability and reselect. */\n\tget isConflict(): boolean {\n\t\treturn this.type === DtoErrorCode.ErrConflict || this.status === 409;\n\t}\n}\n\nexport class BookitiveClient {\n\treadonly #base: string;\n\treadonly #fetch: typeof fetch;\n\n\tconstructor(options: BookitiveClientOptions) {\n\t\tthis.#base = `${options.baseUrl.replace(/\\/$/, '')}/public/v1/orgs/${encodeURIComponent(\n\t\t\toptions.org\n\t\t)}/events/${encodeURIComponent(options.event)}`;\n\t\t// bind, or a browser's fetch throws \"Illegal invocation\" when called\n\t\t// detached from window\n\t\tthis.#fetch = options.fetch ?? ((...args) => fetch(...args));\n\t}\n\n\t/** Event identity, org branding, priced ticket types, and floor tabs. */\n\tevent(): Promise<DtoPublicEvent> {\n\t\treturn this.#get<DtoPublicEvent>('');\n\t}\n\n\t/** Static chart geometry: floors → sections → tables, seats, GA areas. */\n\tseating(): Promise<DtoPublicSeating> {\n\t\treturn this.#get<DtoPublicSeating>('/seating');\n\t}\n\n\t/** Live sellability snapshot (poll it, or use live() for push updates). */\n\tavailability(): Promise<DtoPublicAvailability> {\n\t\treturn this.#get<DtoPublicAvailability>('/availability');\n\t}\n\n\t/**\n\t * Previews a promo code: what applying it would do. Throws a\n\t * BookitiveError with status 404 for unknown/paused/out-of-window codes\n\t * and 409 (error.isConflict) when the code is fully redeemed. The order\n\t * endpoint re-validates on submit.\n\t */\n\tdiscount(code: string): Promise<DtoPublicDiscount> {\n\t\treturn this.#get<DtoPublicDiscount>(`/discount?code=${encodeURIComponent(code)}`);\n\t}\n\n\t/**\n\t * Places a bank-transfer reservation: seats are held atomically for the\n\t * event's hold TTL and payment instructions are emailed (and returned).\n\t * A 409 (error.isConflict) means a seat was just taken — refresh and\n\t * reselect.\n\t */\n\tasync checkout(order: DtoPublicOrderRequest): Promise<DtoPublicOrder> {\n\t\t// Attach a coarse device fingerprint for the anti-scalping device_velocity\n\t\t// signal (RFC 0004) unless the caller already supplied one. Best-effort:\n\t\t// undefined off the browser or when traits are unavailable.\n\t\tconst body: DtoPublicOrderRequest =\n\t\t\torder.fingerprint === undefined ? { ...order, fingerprint: deviceFingerprint() } : order;\n\t\tconst res = await this.#fetch(`${this.#base}/orders`, {\n\t\t\tmethod: 'POST',\n\t\t\theaders: { 'Content-Type': 'application/json' },\n\t\t\tbody: JSON.stringify(body),\n\t\t});\n\t\treturn this.#parse<DtoPublicOrder>(res, 'checkout failed');\n\t}\n\n\t/**\n\t * Sends a one-time code by SMS for events that require a verified phone\n\t * (event.protection.requireVerifiedPhone). Captcha-gated like checkout, so\n\t * pass a captcha token minted on the Bookitive origin. Throws a\n\t * BookitiveError: 400 (bad/disallowed number), 403 (captcha/blocked), or\n\t * 429 (cooldown/rate limit — back off and let the buyer retry).\n\t */\n\tasync verifyStart(phone: string, captchaToken?: string): Promise<void> {\n\t\tconst res = await this.#fetch(`${this.#base}/verify/start`, {\n\t\t\tmethod: 'POST',\n\t\t\theaders: { 'Content-Type': 'application/json' },\n\t\t\tbody: JSON.stringify({ phone, captchaToken }),\n\t\t});\n\t\tawait this.#parse<void>(res, 'failed to send verification code');\n\t}\n\n\t/**\n\t * Validates the SMS code and returns a single-use verificationToken (valid\n\t * ~30 min) to pass as order.verificationToken. Throws a BookitiveError:\n\t * 400 (wrong code), 410 (expired — resend), or 429 (too many attempts).\n\t */\n\tverifyCheck(phone: string, code: string): Promise<DtoPublicVerification> {\n\t\treturn this.#post<DtoPublicVerification>('/verify/check', { phone, code });\n\t}\n\n\t/**\n\t * Subscribes to live availability over websocket: `startstate` fires with\n\t * the full snapshot on every (re)connect, then `seat` deltas stream in.\n\t * Reconnects automatically with backoff; call close() when done.\n\t */\n\tlive(handlers: LiveHandlers): LiveSubscription {\n\t\treturn new LiveSubscription(`${this.#base}/live`, handlers);\n\t}\n\n\tasync #get<T>(path: string): Promise<T> {\n\t\tconst res = await this.#fetch(`${this.#base}${path}`);\n\t\treturn this.#parse<T>(res, `request failed: ${path || '/'}`);\n\t}\n\n\tasync #post<T>(path: string, body: unknown): Promise<T> {\n\t\tconst res = await this.#fetch(`${this.#base}${path}`, {\n\t\t\tmethod: 'POST',\n\t\t\theaders: { 'Content-Type': 'application/json' },\n\t\t\tbody: JSON.stringify(body),\n\t\t});\n\t\treturn this.#parse<T>(res, `request failed: ${path}`);\n\t}\n\n\tasync #parse<T>(res: Response, fallback: string): Promise<T> {\n\t\tif (!res.ok) {\n\t\t\tlet body: DtoAPIError | undefined;\n\t\t\ttry {\n\t\t\t\tbody = (await res.json()) as DtoAPIError;\n\t\t\t} catch {\n\t\t\t\t// non-JSON error body — the fallback message covers it\n\t\t\t}\n\t\t\tthrow new BookitiveError(res.status, body, fallback);\n\t\t}\n\t\t// 204/205 never carry a body. Otherwise read the text and parse only\n\t\t// when it's non-empty — this avoids res.headers.get('content-length'),\n\t\t// which throws under SvelteKit's SSR fetch (response headers aren't\n\t\t// serialized by default) and would otherwise 500 any server-side load.\n\t\tif (res.status === 204 || res.status === 205) {\n\t\t\treturn undefined as T;\n\t\t}\n\t\tconst text = await res.text();\n\t\treturn (text ? JSON.parse(text) : undefined) as T;\n\t}\n}\n","// Buyer-facing texts for the public order API's error codes. The backend's\n// `msg` is an English developer string and must never reach a buyer; hosts\n// call checkoutErrorMessage() and fall back to their own generic copy when it\n// returns undefined (unknown code, network failure, plain Error).\nimport { BookitiveError } from './client';\n\ntype Lang = 'cs' | 'en';\ntype Message = string | ((fields: Record<string, string>) => string);\n\nconst MESSAGES: Record<Lang, Record<string, Message>> = {\n\tcs: {\n\t\tPER_ORDER_LIMITS: (f) =>\n\t\t\tf.min\n\t\t\t\t? `Vstupenek „${f.ticketType}“ je potřeba objednat alespoň ${f.min}.`\n\t\t\t\t: f.max\n\t\t\t\t\t? `Vstupenek „${f.ticketType}“ lze objednat nejvýše ${f.max} na jednu objednávku.`\n\t\t\t\t\t: 'Počet vstupenek je mimo povolený rozsah pro jednu objednávku.',\n\t\tPER_CUSTOMER_LIMIT:\n\t\t\t'Překročili jste maximální počet vstupenek na jednoho zákazníka pro tuto akci.',\n\t\tSOLD_OUT: 'Vybraný typ vstupenek je již vyprodaný.',\n\t\tSEATS_TAKEN: 'Některé z vybraných míst si právě zabral někdo jiný — vyberte prosím jiná.',\n\t\tCAPACITY_FULL: 'Kapacita vybrané zóny je již vyčerpaná.',\n\t\tSALE_WINDOW: 'Prodej tohoto typu vstupenek právě neprobíhá.',\n\t\tNOT_ON_SALE: 'Prodej vstupenek na tuto akci právě neprobíhá.',\n\t\tDISCOUNT_INVALID: 'Zadaný slevový kód není platný.',\n\t\tDISCOUNT_EXHAUSTED: 'Zadaný slevový kód už byl vyčerpán.',\n\t\tVERIFICATION_INVALID: 'Ověření telefonu vypršelo — ověřte se prosím znovu.',\n\t\tATTENDEES_REQUIRED: 'Vyplňte prosím jméno pro každou vstupenku.',\n\t},\n\ten: {\n\t\tPER_ORDER_LIMITS: (f) =>\n\t\t\tf.min\n\t\t\t\t? `You need to order at least ${f.min} “${f.ticketType}” tickets.`\n\t\t\t\t: f.max\n\t\t\t\t\t? `You can order at most ${f.max} “${f.ticketType}” tickets per order.`\n\t\t\t\t\t: 'The ticket quantity is outside the allowed per-order range.',\n\t\tPER_CUSTOMER_LIMIT:\n\t\t\t'You have reached the maximum number of tickets per customer for this event.',\n\t\tSOLD_OUT: 'The selected ticket type is sold out.',\n\t\tSEATS_TAKEN: 'One of your seats was just taken — please pick another.',\n\t\tCAPACITY_FULL: 'The selected area is at full capacity.',\n\t\tSALE_WINDOW: 'This ticket type is not on sale right now.',\n\t\tNOT_ON_SALE: 'Tickets for this event are not on sale right now.',\n\t\tDISCOUNT_INVALID: 'That discount code is not valid.',\n\t\tDISCOUNT_EXHAUSTED: 'That discount code has been fully redeemed.',\n\t\tVERIFICATION_INVALID: 'Your phone verification expired — please verify again.',\n\t\tATTENDEES_REQUIRED: 'Please fill in a name for every ticket.',\n\t},\n};\n\n/**\n * Localized buyer-facing message for a checkout failure, or undefined when\n * the error carries no known code (caller shows its own generic text).\n * Accepts any BCP 47 locale; everything except Czech falls back to English.\n */\nexport function checkoutErrorMessage(err: unknown, locale?: string): string | undefined {\n\tif (!(err instanceof BookitiveError)) return undefined;\n\tconst lang: Lang = locale?.toLowerCase().startsWith('cs') ? 'cs' : 'en';\n\tconst entry = MESSAGES[lang][err.type];\n\tif (!entry) return undefined;\n\treturn typeof entry === 'function' ? entry(err.fields ?? {}) : entry;\n}\n"],"mappings":"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","names":["DtoErrorCode"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bookitive/js",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "Typed client for the Bookitive public checkout API — event info, seating, live availability, and orders.",
5
5
  "license": "MIT",
6
6
  "type": "module",