@ticketlayer/elements 0.3.3 → 1.0.0

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.
Files changed (179) hide show
  1. package/README.md +3 -2
  2. package/dist/cjs/{index-BpfoKd9Q.js → index-DanPUX0s.js} +1 -1
  3. package/dist/cjs/index.cjs.js +2 -1
  4. package/dist/cjs/loader.cjs.js +2 -2
  5. package/dist/cjs/ticket-selector-BYVL13k_.js +3623 -0
  6. package/dist/cjs/ticketlayer-elements.cjs.js +2 -2
  7. package/dist/cjs/tl-buy-modal.cjs.entry.js +4 -4
  8. package/dist/cjs/tl-buy-tickets-button.cjs.entry.js +89 -71
  9. package/dist/cjs/tl-buy-tickets-wrapper.cjs.entry.js +62 -31
  10. package/dist/cjs/tl-cart-badge.cjs.entry.js +75 -62
  11. package/dist/cjs/tl-cart-drawer.cjs.entry.js +52 -24
  12. package/dist/cjs/tl-cart.cjs.entry.js +72 -98
  13. package/dist/cjs/tl-checkout-wrapper.cjs.entry.js +52 -30
  14. package/dist/cjs/tl-event-card.cjs.entry.js +44 -105
  15. package/dist/cjs/tl-event-list.cjs.entry.js +73 -36
  16. package/dist/cjs/tl-login.cjs.entry.js +75 -141
  17. package/dist/cjs/tl-my-orders.cjs.entry.js +53 -92
  18. package/dist/cjs/tl-occurrence-selector_2.cjs.entry.js +205 -641
  19. package/dist/cjs/tl-order-confirmation.cjs.entry.js +72 -105
  20. package/dist/cjs/tl-order-tickets_2.cjs.entry.js +50 -76
  21. package/dist/cjs/tl-promo-code.cjs.entry.js +51 -47
  22. package/dist/cjs/tl-seat-map.cjs.entry.js +4 -4
  23. package/dist/cjs/web-query-Ce73jB4k.js +16 -0
  24. package/dist/collection/components/tl-buy-modal/tl-buy-modal.css +12 -19
  25. package/dist/collection/components/tl-buy-modal/tl-buy-modal.js +2 -2
  26. package/dist/collection/components/tl-buy-tickets-button/tl-buy-tickets-button.css +41 -4
  27. package/dist/collection/components/tl-buy-tickets-button/tl-buy-tickets-button.js +105 -76
  28. package/dist/collection/components/tl-buy-tickets-wrapper/tl-buy-tickets-wrapper.css +2 -2
  29. package/dist/collection/components/tl-buy-tickets-wrapper/tl-buy-tickets-wrapper.js +73 -30
  30. package/dist/collection/components/tl-cart/tl-cart.css +52 -51
  31. package/dist/collection/components/tl-cart/tl-cart.js +82 -101
  32. package/dist/collection/components/tl-cart-badge/tl-cart-badge.css +11 -11
  33. package/dist/collection/components/tl-cart-badge/tl-cart-badge.js +62 -62
  34. package/dist/collection/components/tl-cart-drawer/tl-cart-drawer.css +6 -6
  35. package/dist/collection/components/tl-cart-drawer/tl-cart-drawer.js +56 -24
  36. package/dist/collection/components/tl-checkout-wrapper/tl-checkout-wrapper.css +2 -2
  37. package/dist/collection/components/tl-checkout-wrapper/tl-checkout-wrapper.js +61 -29
  38. package/dist/collection/components/tl-event-card/event-card-adapter.js +11 -23
  39. package/dist/collection/components/tl-event-card/tl-event-card.css +22 -22
  40. package/dist/collection/components/tl-event-card/tl-event-card.js +51 -84
  41. package/dist/collection/components/tl-event-list/tl-event-list.css +17 -17
  42. package/dist/collection/components/tl-event-list/tl-event-list.js +82 -38
  43. package/dist/collection/components/tl-login/tl-login.css +26 -26
  44. package/dist/collection/components/tl-login/tl-login.js +83 -147
  45. package/dist/collection/components/tl-my-orders/tl-my-orders.css +40 -40
  46. package/dist/collection/components/tl-my-orders/tl-my-orders.js +58 -96
  47. package/dist/collection/components/tl-occurrence-selector/tl-occurrence-selector.css +54 -65
  48. package/dist/collection/components/tl-occurrence-selector/tl-occurrence-selector.js +137 -516
  49. package/dist/collection/components/tl-order-confirmation/tl-order-confirmation.css +38 -38
  50. package/dist/collection/components/tl-order-confirmation/tl-order-confirmation.js +81 -113
  51. package/dist/collection/components/tl-order-tickets/tl-order-tickets.css +9 -9
  52. package/dist/collection/components/tl-order-tickets/tl-order-tickets.js +57 -92
  53. package/dist/collection/components/tl-promo-code/tl-promo-code.css +25 -25
  54. package/dist/collection/components/tl-promo-code/tl-promo-code.js +55 -50
  55. package/dist/collection/components/tl-seat-map/tl-seat-map.css +10 -10
  56. package/dist/collection/components/tl-seat-map/tl-seat-map.js +2 -2
  57. package/dist/collection/components/tl-ticket/tl-ticket.css +13 -13
  58. package/dist/collection/components/tl-ticket-selector/tl-ticket-selector.css +74 -49
  59. package/dist/collection/components/tl-ticket-selector/tl-ticket-selector.js +95 -135
  60. package/dist/collection/utils/cart-totals.js +9 -0
  61. package/dist/collection/utils/order-summary.js +15 -99
  62. package/dist/collection/utils/testing/deep-text.js +29 -0
  63. package/dist/collection/utils/testing/fixtures.js +290 -0
  64. package/dist/collection/utils/testing/mock-sdk.js +13 -2
  65. package/dist/collection/utils/web-query.js +12 -0
  66. package/dist/collection/utils/web-storage.js +22 -0
  67. package/dist/components/ticket-selector.js +1 -0
  68. package/dist/components/tl-buy-modal.js +1 -1
  69. package/dist/components/tl-buy-tickets-button.js +1 -1
  70. package/dist/components/tl-buy-tickets-wrapper2.js +1 -1
  71. package/dist/components/tl-cart-badge.js +1 -1
  72. package/dist/components/tl-cart-drawer.js +1 -1
  73. package/dist/components/tl-cart2.js +1 -1
  74. package/dist/components/tl-checkout-wrapper2.js +1 -1
  75. package/dist/components/tl-event-card2.js +1 -1
  76. package/dist/components/tl-event-list.js +1 -1
  77. package/dist/components/tl-login2.js +1 -1
  78. package/dist/components/tl-my-orders.js +1 -1
  79. package/dist/components/tl-occurrence-selector2.js +1 -1
  80. package/dist/components/tl-order-confirmation.js +1 -1
  81. package/dist/components/tl-order-tickets2.js +1 -1
  82. package/dist/components/tl-promo-code.js +1 -1
  83. package/dist/components/tl-seat-map.js +1 -1
  84. package/dist/components/tl-ticket-selector2.js +1 -1
  85. package/dist/components/tl-ticket2.js +1 -1
  86. package/dist/components/web-query.js +1 -0
  87. package/dist/elements.docs.json +36 -36
  88. package/dist/esm/{index-CNX8hZa8.js → index-imfxp765.js} +1 -1
  89. package/dist/esm/index.js +2 -1
  90. package/dist/esm/loader.js +3 -3
  91. package/dist/esm/ticket-selector-BwChnHVC.js +3606 -0
  92. package/dist/esm/ticketlayer-elements.js +3 -3
  93. package/dist/esm/tl-buy-modal.entry.js +4 -4
  94. package/dist/esm/tl-buy-tickets-button.entry.js +90 -72
  95. package/dist/esm/tl-buy-tickets-wrapper.entry.js +63 -32
  96. package/dist/esm/tl-cart-badge.entry.js +76 -63
  97. package/dist/esm/tl-cart-drawer.entry.js +53 -25
  98. package/dist/esm/tl-cart.entry.js +73 -99
  99. package/dist/esm/tl-checkout-wrapper.entry.js +53 -31
  100. package/dist/esm/tl-event-card.entry.js +45 -106
  101. package/dist/esm/tl-event-list.entry.js +74 -37
  102. package/dist/esm/tl-login.entry.js +76 -142
  103. package/dist/esm/tl-my-orders.entry.js +54 -93
  104. package/dist/esm/tl-occurrence-selector_2.entry.js +206 -642
  105. package/dist/esm/tl-order-confirmation.entry.js +73 -106
  106. package/dist/esm/tl-order-tickets_2.entry.js +51 -77
  107. package/dist/esm/tl-promo-code.entry.js +52 -48
  108. package/dist/esm/tl-seat-map.entry.js +4 -4
  109. package/dist/esm/web-query-Bdbb2lk5.js +14 -0
  110. package/dist/ticketlayer-elements/index.esm.js +1 -1
  111. package/dist/ticketlayer-elements/p-45923d2c.entry.js +1 -0
  112. package/dist/ticketlayer-elements/p-492adc77.entry.js +1 -0
  113. package/dist/ticketlayer-elements/p-4cfc9e53.entry.js +1 -0
  114. package/dist/ticketlayer-elements/p-5135630e.entry.js +1 -0
  115. package/dist/ticketlayer-elements/p-5bbce3b5.entry.js +1 -0
  116. package/dist/ticketlayer-elements/p-7d547721.entry.js +1 -0
  117. package/dist/ticketlayer-elements/p-7fe7b6ba.entry.js +1 -0
  118. package/dist/ticketlayer-elements/p-8122f0c1.entry.js +1 -0
  119. package/dist/ticketlayer-elements/p-98d40869.entry.js +1 -0
  120. package/dist/ticketlayer-elements/p-Bdbb2lk5.js +1 -0
  121. package/dist/ticketlayer-elements/p-BwChnHVC.js +1 -0
  122. package/dist/ticketlayer-elements/p-abec290a.entry.js +1 -0
  123. package/dist/ticketlayer-elements/p-ac79a3f6.entry.js +1 -0
  124. package/dist/ticketlayer-elements/p-d01f4429.entry.js +1 -0
  125. package/dist/ticketlayer-elements/p-d7ffaf46.entry.js +1 -0
  126. package/dist/ticketlayer-elements/p-e7170f3c.entry.js +1 -0
  127. package/dist/ticketlayer-elements/p-e787e862.entry.js +1 -0
  128. package/dist/ticketlayer-elements/p-f1ff931f.entry.js +1 -0
  129. package/dist/ticketlayer-elements/p-imfxp765.js +3 -0
  130. package/dist/ticketlayer-elements/ticketlayer-elements.css +1 -1
  131. package/dist/ticketlayer-elements/ticketlayer-elements.esm.js +1 -1
  132. package/dist/types/components/tl-buy-tickets-button/tl-buy-tickets-button.d.ts +40 -8
  133. package/dist/types/components/tl-buy-tickets-wrapper/tl-buy-tickets-wrapper.d.ts +20 -1
  134. package/dist/types/components/tl-cart/tl-cart.d.ts +40 -32
  135. package/dist/types/components/tl-cart-badge/tl-cart-badge.d.ts +22 -7
  136. package/dist/types/components/tl-cart-drawer/tl-cart-drawer.d.ts +28 -5
  137. package/dist/types/components/tl-checkout-wrapper/tl-checkout-wrapper.d.ts +18 -1
  138. package/dist/types/components/tl-event-card/event-card-adapter.d.ts +9 -20
  139. package/dist/types/components/tl-event-card/tl-event-card.d.ts +25 -28
  140. package/dist/types/components/tl-event-list/tl-event-list.d.ts +46 -6
  141. package/dist/types/components/tl-login/tl-login.d.ts +68 -22
  142. package/dist/types/components/tl-my-orders/tl-my-orders.d.ts +30 -12
  143. package/dist/types/components/tl-occurrence-selector/tl-occurrence-selector.d.ts +73 -161
  144. package/dist/types/components/tl-order-confirmation/tl-order-confirmation.d.ts +48 -15
  145. package/dist/types/components/tl-order-tickets/tl-order-tickets.d.ts +29 -31
  146. package/dist/types/components/tl-promo-code/tl-promo-code.d.ts +28 -10
  147. package/dist/types/components/tl-ticket/tl-ticket.d.ts +2 -11
  148. package/dist/types/components/tl-ticket-selector/tl-ticket-selector.d.ts +61 -32
  149. package/dist/types/components.d.ts +302 -62
  150. package/dist/types/utils/cart-totals.d.ts +10 -0
  151. package/dist/types/utils/order-summary.d.ts +16 -65
  152. package/dist/types/utils/testing/deep-text.d.ts +13 -0
  153. package/dist/types/utils/testing/fixtures.d.ts +225 -0
  154. package/dist/types/utils/testing/mock-sdk.d.ts +14 -1
  155. package/dist/types/utils/web-query.d.ts +18 -0
  156. package/dist/types/utils/web-storage.d.ts +16 -0
  157. package/elements.catalogue.json +676 -55
  158. package/package.json +14 -6
  159. package/dist/cjs/order-summary-ZMkJ5bez.js +0 -116
  160. package/dist/components/order-summary.js +0 -1
  161. package/dist/esm/order-summary-BGekdjN9.js +0 -103
  162. package/dist/ticketlayer-elements/p-0b1d16f8.entry.js +0 -1
  163. package/dist/ticketlayer-elements/p-1471f6d8.entry.js +0 -1
  164. package/dist/ticketlayer-elements/p-1c51cbc1.entry.js +0 -1
  165. package/dist/ticketlayer-elements/p-5169f68b.entry.js +0 -1
  166. package/dist/ticketlayer-elements/p-895ba1f3.entry.js +0 -1
  167. package/dist/ticketlayer-elements/p-8d5722e1.entry.js +0 -1
  168. package/dist/ticketlayer-elements/p-917d8e09.entry.js +0 -1
  169. package/dist/ticketlayer-elements/p-9629029a.entry.js +0 -1
  170. package/dist/ticketlayer-elements/p-BGekdjN9.js +0 -1
  171. package/dist/ticketlayer-elements/p-CNX8hZa8.js +0 -3
  172. package/dist/ticketlayer-elements/p-a22714d0.entry.js +0 -1
  173. package/dist/ticketlayer-elements/p-bf73044b.entry.js +0 -1
  174. package/dist/ticketlayer-elements/p-e02079fa.entry.js +0 -1
  175. package/dist/ticketlayer-elements/p-e04ace15.entry.js +0 -1
  176. package/dist/ticketlayer-elements/p-e6fab1d5.entry.js +0 -1
  177. package/dist/ticketlayer-elements/p-f826b753.entry.js +0 -1
  178. package/dist/ticketlayer-elements/p-fa639d88.entry.js +0 -1
  179. package/dist/ticketlayer-elements/p-fbaca948.entry.js +0 -1
@@ -1,5 +1,6 @@
1
1
  import { h } from "@stencil/core";
2
- import { whenSDK } from "../../utils/sdk-connector";
2
+ import { createElement, orderTicketsCore, } from "@ticketlayer/elements-core";
3
+ import { getSDK, whenSDK } from "../../utils/sdk-connector";
3
4
  /**
4
5
  * tl-order-tickets - renders all the tickets for an order as scannable QR
5
6
  * cards with PDF download. Fetches the order through the SDK (order-access
@@ -7,101 +8,73 @@ import { whenSDK } from "../../utils/sdk-connector";
7
8
  *
8
9
  * Usage: <tl-order-tickets order-id="ord_xxx"></tl-order-tickets>
9
10
  *
10
- * On an empty result it waits rather than concluding there is nothing.
11
- *
12
- * Tickets are issued asynchronously: the payment confirms, the order is
13
- * marked paid, and entitlement issuance follows a moment later. A confirmation
14
- * page therefore renders BEFORE the passes exist - reliably, not occasionally.
15
- * The component used to answer that with "No tickets for this order yet",
16
- * which is true and reads as "something went wrong" to someone who has just
17
- * been charged. Refreshing showed them, so the only thing missing was the
18
- * waiting.
19
- *
20
- * So: an empty order is treated as issuance in flight and polled, with the
21
- * gaps widening, for about a minute. Then it stops and says the tickets are
22
- * coming by email, because at that point something probably is wrong and a
23
- * page that polls forever is worse than one that admits it.
11
+ * On an empty result it waits rather than concluding there is nothing: tickets
12
+ * are issued a moment after the order is paid, so a confirmation page renders
13
+ * before the passes exist. The waiting, the widening schedule behind it and
14
+ * the point at which it gives up are all in `orderTicketsCore`
15
+ * (`@ticketlayer/elements-core`, AGENTS.md, TKT-11), along with every string.
16
+ * This harness renders and forwards, and the one thing it owns is pairing
17
+ * `mount()` with its teardown - which is what stops the polling when the
18
+ * element goes off screen.
24
19
  */
25
20
  export class TlOrderTickets {
26
21
  constructor() {
27
22
  /** Columns in the grid (default 2). */
28
23
  this.columns = 2;
29
- this.tickets = [];
30
- this.loading = true;
31
- this.error = null;
32
- /** The order loaded, but has no tickets yet and we are still waiting. */
33
- this.issuing = false;
34
- this.poll = 0;
24
+ this.element = createElement(orderTicketsCore, {
25
+ client: () => getSDK(),
26
+ clientReady: () => whenSDK(),
27
+ emit: (event, detail) => this.forward(event, detail),
28
+ });
29
+ /** The core's whole view. Assigning it is what makes Stencil re-render. */
30
+ this.view = this.element.view;
35
31
  }
32
+ /** A different order: forget this one and read again, from the top. */
36
33
  watchOrder() {
37
- this.reset();
38
- this.load();
34
+ this.syncProps();
35
+ this.element.actions.orderIdChanged();
39
36
  }
40
- async componentWillLoad() {
41
- await this.load();
42
- }
43
- disconnectedCallback() {
44
- this.stopPolling();
37
+ syncProps() {
38
+ this.element.setProps({ orderId: this.orderId, columns: this.columns });
45
39
  }
46
- reset() {
47
- this.stopPolling();
48
- this.poll = 0;
49
- this.tickets = [];
50
- this.issuing = false;
40
+ connectedCallback() {
41
+ this.syncProps();
42
+ this.unsubscribe ?? (this.unsubscribe = this.element.subscribe((view) => {
43
+ this.view = view;
44
+ }));
45
+ this.view = this.element.view;
46
+ this.teardown = this.element.mount();
51
47
  }
52
- stopPolling() {
53
- if (this.timer !== undefined) {
54
- clearTimeout(this.timer);
55
- this.timer = undefined;
56
- }
48
+ /**
49
+ * Off screen: run the mount's teardown, which is what stops the polling.
50
+ * Not `destroy()`: Stencil calls this for a DOM move as well as a removal,
51
+ * and a destroyed core never mounts again.
52
+ */
53
+ disconnectedCallback() {
54
+ this.teardown?.();
55
+ this.teardown = undefined;
56
+ this.unsubscribe?.();
57
+ this.unsubscribe = undefined;
57
58
  }
58
- async load() {
59
- try {
60
- this.error = null;
61
- const sdk = await whenSDK();
62
- const orders = sdk.getOrdersManager?.();
63
- if (!orders)
64
- throw new Error('Tickets are not available');
65
- const order = (await orders.get(this.orderId));
66
- this.tickets = order?.tickets ?? [];
67
- if (this.tickets.length > 0) {
68
- // Arrived. Stop asking.
69
- this.issuing = false;
70
- this.stopPolling();
71
- }
72
- else {
73
- const delay = TlOrderTickets.POLL_DELAYS_MS[this.poll];
74
- // `undefined` once the schedule runs out: give up waiting, and say so.
75
- this.issuing = delay !== undefined;
76
- if (delay !== undefined) {
77
- this.poll += 1;
78
- this.stopPolling();
79
- this.timer = setTimeout(() => this.load(), delay);
80
- }
81
- }
82
- }
83
- catch (err) {
84
- // A failed request is a different thing from an order with no tickets,
85
- // and stops the polling: retrying a 403 a dozen times helps nobody.
86
- this.stopPolling();
87
- this.issuing = false;
88
- this.error = err instanceof Error ? err.message : 'Failed to load tickets';
89
- }
90
- finally {
91
- this.loading = false;
92
- }
59
+ /** The core declares no events, so nothing ever reaches here. */
60
+ forward(_event, _detail) {
61
+ // Intentionally empty: tl-order-tickets announces nothing to its host.
93
62
  }
94
63
  render() {
95
- if (this.loading) {
96
- return h("div", { class: "state" }, "Loading tickets\u2026");
64
+ const { status, state, strings, derived } = this.view;
65
+ if (status === 'loading') {
66
+ return h("div", { class: "state" }, strings.loading);
97
67
  }
98
- if (this.error) {
99
- return h("div", { class: "state error" }, this.error);
68
+ if (status === 'error') {
69
+ return h("div", { class: "state error" }, state.error);
100
70
  }
101
- if (this.tickets.length === 0) {
102
- return this.issuing ? (h("div", { class: "state issuing", role: "status", "aria-live": "polite" }, h("span", { class: "spinner", "aria-hidden": "true" }), "Preparing your tickets\u2026")) : (h("div", { class: "state", role: "status" }, "Your tickets are taking longer than usual. They will be emailed to you as soon as they are ready."));
71
+ if (status === 'issuing') {
72
+ return (h("div", { class: "state issuing", role: "status", "aria-live": "polite" }, h("span", { class: "spinner", "aria-hidden": "true" }), strings.preparing));
103
73
  }
104
- return (h("div", { class: `grid cols-${this.columns}` }, this.tickets.map((t) => (h("tl-ticket", { key: t.id, ticket: t, orderId: this.orderId })))));
74
+ if (status === 'delayed') {
75
+ return (h("div", { class: "state", role: "status" }, strings.delayed));
76
+ }
77
+ return (h("div", { class: `grid cols-${derived.columns}` }, derived.tickets.map((t) => (h("tl-ticket", { key: t.id, ticket: t, orderId: derived.orderId })))));
105
78
  }
106
79
  static get is() { return "tl-order-tickets"; }
107
80
  static get encapsulation() { return "shadow"; }
@@ -160,24 +133,16 @@ export class TlOrderTickets {
160
133
  }
161
134
  static get states() {
162
135
  return {
163
- "tickets": {},
164
- "loading": {},
165
- "error": {},
166
- "issuing": {}
136
+ "view": {}
167
137
  };
168
138
  }
169
139
  static get watchers() {
170
140
  return [{
171
141
  "propName": "orderId",
172
142
  "methodName": "watchOrder"
143
+ }, {
144
+ "propName": "columns",
145
+ "methodName": "syncProps"
173
146
  }];
174
147
  }
175
148
  }
176
- /**
177
- * Gaps between polls, in milliseconds. Front-loaded because issuance
178
- * usually lands in the first few seconds, then widening so a genuinely
179
- * stuck order is not hammered. Sums to roughly one minute.
180
- */
181
- TlOrderTickets.POLL_DELAYS_MS = [
182
- 1000, 1500, 2000, 3000, 4000, 5000, 6000, 8000, 10000, 12000,
183
- ];
@@ -1,46 +1,46 @@
1
1
  :host {
2
2
  display: block;
3
- font-family: var(--tl-font-family, system-ui, sans-serif);
4
- color: var(--tl-foreground, #111827);
3
+ font-family: var(--tl-font-family);
4
+ color: var(--tl-foreground);
5
5
  }
6
6
 
7
7
  .field {
8
8
  display: flex;
9
- gap: var(--tl-spacing-2, 0.5rem);
9
+ gap: var(--tl-spacing-2);
10
10
  }
11
11
 
12
12
  .input {
13
13
  flex: 1;
14
14
  min-width: 0;
15
- padding: var(--tl-spacing-3, 0.75rem);
15
+ padding: var(--tl-spacing-3);
16
16
  font: inherit;
17
17
  font-size: 0.875rem;
18
- color: var(--tl-foreground, #111827);
19
- background: var(--tl-background, #ffffff);
20
- border: 1px solid var(--tl-border, #e5e7eb);
21
- border-radius: var(--tl-radius-md, 0.5rem);
18
+ color: var(--tl-foreground);
19
+ background: var(--tl-background);
20
+ border: 1px solid var(--tl-border);
21
+ border-radius: var(--tl-radius-md);
22
22
  text-transform: uppercase;
23
23
  }
24
24
 
25
25
  .input::placeholder {
26
26
  text-transform: none;
27
- color: var(--tl-muted-foreground, #6b7280);
27
+ color: var(--tl-muted-foreground);
28
28
  }
29
29
 
30
30
  .input:focus {
31
- outline: 2px solid var(--tl-primary, #6366f1);
31
+ outline: 2px solid var(--tl-primary);
32
32
  outline-offset: 1px;
33
33
  }
34
34
 
35
35
  .apply {
36
- padding: var(--tl-spacing-3, 0.75rem) var(--tl-spacing-4, 1rem);
36
+ padding: var(--tl-spacing-3) var(--tl-spacing-4);
37
37
  font: inherit;
38
38
  font-size: 0.875rem;
39
39
  font-weight: 500;
40
- color: var(--tl-primary-foreground, #ffffff);
41
- background: var(--tl-primary, #6366f1);
40
+ color: var(--tl-primary-foreground);
41
+ background: var(--tl-primary);
42
42
  border: none;
43
- border-radius: var(--tl-radius-md, 0.5rem);
43
+ border-radius: var(--tl-radius-md);
44
44
  cursor: pointer;
45
45
  white-space: nowrap;
46
46
  }
@@ -56,45 +56,45 @@
56
56
  }
57
57
 
58
58
  .error {
59
- margin: var(--tl-spacing-2, 0.5rem) 0 0;
59
+ margin: var(--tl-spacing-2) 0 0;
60
60
  font-size: 0.8125rem;
61
- color: var(--tl-error, #ef4444);
61
+ color: var(--tl-error);
62
62
  }
63
63
 
64
64
  .applied {
65
65
  display: flex;
66
66
  align-items: center;
67
67
  justify-content: space-between;
68
- gap: var(--tl-spacing-3, 0.75rem);
69
- padding: var(--tl-spacing-3, 0.75rem);
68
+ gap: var(--tl-spacing-3);
69
+ padding: var(--tl-spacing-3);
70
70
  font-size: 0.875rem;
71
- background: var(--tl-muted, #f9fafb);
72
- border: 1px solid var(--tl-border, #e5e7eb);
73
- border-radius: var(--tl-radius-md, 0.5rem);
71
+ background: var(--tl-muted);
72
+ border: 1px solid var(--tl-border);
73
+ border-radius: var(--tl-radius-md);
74
74
  }
75
75
 
76
76
  .applied-text {
77
77
  display: flex;
78
78
  align-items: center;
79
- gap: var(--tl-spacing-2, 0.5rem);
79
+ gap: var(--tl-spacing-2);
80
80
  }
81
81
 
82
82
  .icon {
83
83
  width: 1rem;
84
84
  height: 1rem;
85
85
  flex-shrink: 0;
86
- color: var(--tl-success, #10b981);
86
+ color: var(--tl-success);
87
87
  }
88
88
 
89
89
  .code {
90
- font-family: var(--tl-font-family-mono, ui-monospace, monospace);
90
+ font-family: var(--tl-font-family-mono);
91
91
  }
92
92
 
93
93
  .link {
94
94
  padding: 0;
95
95
  font: inherit;
96
96
  font-size: 0.8125rem;
97
- color: var(--tl-primary, #6366f1);
97
+ color: var(--tl-primary);
98
98
  background: none;
99
99
  border: none;
100
100
  cursor: pointer;
@@ -1,6 +1,6 @@
1
1
  import { h } from "@stencil/core";
2
- import { whenSDK } from "../../utils/sdk-connector";
3
- import { errorMessage, isAuthError } from "../../utils/order-summary";
2
+ import { createElement, promoCodeCore, } from "@ticketlayer/elements-core";
3
+ import { getSDK, whenSDK } from "../../utils/sdk-connector";
4
4
  /**
5
5
  * tl-promo-code - an input plus Apply button that redeems a presale or promo
6
6
  * code for the current cart (POST /sales/presale-codes/redeem through the
@@ -10,6 +10,9 @@ import { errorMessage, isAuthError } from "../../utils/order-summary";
10
10
  * The API has no un-redeem, so the applied state offers only "use a different
11
11
  * code", which clears the input for another redemption.
12
12
  *
13
+ * The web harness for `promoCodeCore`: the redemption, the two error messages
14
+ * and every string are the core's (AGENTS.md, TKT-11).
15
+ *
13
16
  * Usage: <tl-promo-code></tl-promo-code>
14
17
  *
15
18
  * @event tlPromoApplied - a code was redeemed ({ code, benefit, alreadyHeld, cart })
@@ -21,59 +24,55 @@ export class TlPromoCode {
21
24
  this.placeholder = 'Promo or presale code';
22
25
  /** Apply button label. */
23
26
  this.buttonLabel = 'Apply';
24
- this.code = '';
25
- this.applying = false;
26
- this.applied = null;
27
- this.error = null;
27
+ this.element = createElement(promoCodeCore, {
28
+ client: () => getSDK(),
29
+ clientReady: () => whenSDK(),
30
+ emit: (event, detail) => this.forward(event, detail),
31
+ });
32
+ /** The core's whole view. Assigning it is what makes Stencil re-render. */
33
+ this.view = this.element.view;
28
34
  this.handleInput = (e) => {
29
- this.code = e.target.value;
30
- this.error = null;
35
+ this.element.actions.setCode(e.target.value);
31
36
  };
32
37
  this.handleSubmit = (e) => {
33
38
  e.preventDefault();
34
- this.apply();
35
- };
36
- this.reset = () => {
37
- this.applied = null;
38
- this.error = null;
39
+ this.element.actions.apply();
39
40
  };
41
+ this.reset = () => this.element.actions.reset();
40
42
  }
41
- async apply() {
42
- const code = this.code.trim();
43
- if (!code) {
44
- this.error = 'Enter a code';
45
- return;
46
- }
47
- this.applying = true;
48
- this.error = null;
49
- try {
50
- const sdk = await whenSDK();
51
- const presale = sdk.getPresaleManager?.();
52
- if (!presale)
53
- throw new Error('Promo codes are not available');
54
- const result = await presale.redeem(code);
55
- // The SDK refreshed the cart on redeem; read the cached copy for the payload.
56
- const cart = await sdk.getCartManager?.()?.get().catch(() => null) ?? null;
57
- this.applied = { code: code.toUpperCase(), ...result, cart };
58
- this.code = '';
59
- this.tlPromoApplied.emit(this.applied);
60
- }
61
- catch (err) {
62
- console.error('[tl-promo-code] Redeem failed:', err);
63
- this.error = isAuthError(err)
64
- ? 'Sign in to use this code'
65
- : errorMessage(err, 'That code could not be applied');
66
- this.tlError.emit({ message: this.error });
67
- }
68
- finally {
69
- this.applying = false;
70
- }
43
+ syncProps() {
44
+ this.element.setProps({ placeholder: this.placeholder, buttonLabel: this.buttonLabel });
45
+ }
46
+ /**
47
+ * This core has nothing to do on mount, and the harness calls `mount()`
48
+ * anyway: a harness never asks what a core's lifecycle means (TKT-197).
49
+ */
50
+ connectedCallback() {
51
+ this.syncProps();
52
+ this.unsubscribe ?? (this.unsubscribe = this.element.subscribe((view) => {
53
+ this.view = view;
54
+ }));
55
+ this.view = this.element.view;
56
+ this.teardown = this.element.mount();
57
+ }
58
+ disconnectedCallback() {
59
+ this.teardown?.();
60
+ this.teardown = undefined;
61
+ this.unsubscribe?.();
62
+ this.unsubscribe = undefined;
63
+ }
64
+ forward(event, detail) {
65
+ if (event === 'tlPromoApplied')
66
+ this.tlPromoApplied.emit(detail);
67
+ else if (event === 'tlError')
68
+ this.tlError.emit(detail);
71
69
  }
72
70
  render() {
73
- if (this.applied) {
74
- return (h("div", { class: "applied", role: "status" }, h("span", { class: "applied-text" }, h("svg", { class: "icon", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "aria-hidden": "true" }, h("path", { "stroke-linecap": "round", "stroke-linejoin": "round", "stroke-width": "2", d: "M5 13l4 4L19 7" })), "Code ", h("strong", { class: "code" }, this.applied.code), this.applied.alreadyHeld ? ' was already applied' : ' applied'), h("button", { type: "button", class: "link", onClick: this.reset }, "Use a different code")));
71
+ const { status, state, strings, derived } = this.view;
72
+ if (status === 'applied') {
73
+ return (h("div", { class: "applied", role: "status" }, h("span", { class: "applied-text" }, h("svg", { class: "icon", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "aria-hidden": "true" }, h("path", { "stroke-linecap": "round", "stroke-linejoin": "round", "stroke-width": "2", d: "M5 13l4 4L19 7" })), strings.codePrefix, h("strong", { class: "code" }, derived.appliedCode), derived.appliedSuffix), h("button", { type: "button", class: "link", onClick: this.reset }, strings.useDifferent)));
75
74
  }
76
- return (h("form", { class: "promo", onSubmit: this.handleSubmit }, h("div", { class: "field" }, h("input", { class: "input", type: "text", autocomplete: "off", autoCapitalize: "characters", spellcheck: false, "aria-label": this.placeholder, placeholder: this.placeholder, value: this.code, disabled: this.applying, onInput: this.handleInput }), h("button", { type: "submit", class: "apply", disabled: this.applying }, this.applying ? 'Applying…' : this.buttonLabel)), this.error && (h("p", { class: "error", role: "alert" }, this.error))));
75
+ return (h("form", { class: "promo", onSubmit: this.handleSubmit }, h("div", { class: "field" }, h("input", { class: "input", type: "text", autocomplete: "off", autoCapitalize: "characters", spellcheck: false, "aria-label": this.placeholder, placeholder: this.placeholder, value: state.code, disabled: derived.disabled, onInput: this.handleInput }), h("button", { type: "submit", class: "apply", disabled: derived.disabled }, derived.actionLabel)), state.error && (h("p", { class: "error", role: "alert" }, state.error))));
77
76
  }
78
77
  static get is() { return "tl-promo-code"; }
79
78
  static get encapsulation() { return "shadow"; }
@@ -133,10 +132,7 @@ export class TlPromoCode {
133
132
  }
134
133
  static get states() {
135
134
  return {
136
- "code": {},
137
- "applying": {},
138
- "applied": {},
139
- "error": {}
135
+ "view": {}
140
136
  };
141
137
  }
142
138
  static get events() {
@@ -178,4 +174,13 @@ export class TlPromoCode {
178
174
  }
179
175
  }];
180
176
  }
177
+ static get watchers() {
178
+ return [{
179
+ "propName": "placeholder",
180
+ "methodName": "syncProps"
181
+ }, {
182
+ "propName": "buttonLabel",
183
+ "methodName": "syncProps"
184
+ }];
185
+ }
181
186
  }
@@ -4,14 +4,14 @@
4
4
 
5
5
  :host {
6
6
  display: block;
7
- font-family: var(--tl-font-family, system-ui, -apple-system, sans-serif);
7
+ font-family: var(--tl-font-family);
8
8
  }
9
9
 
10
10
  .seat-map-container {
11
11
  position: relative;
12
12
  overflow: hidden;
13
- border-radius: var(--tl-radius-md, 0.5rem);
14
- background: var(--tl-color-background, #ffffff);
13
+ border-radius: var(--tl-radius-md);
14
+ background: var(--tl-color-background);
15
15
  }
16
16
 
17
17
  .seat-map-iframe {
@@ -32,22 +32,22 @@
32
32
  flex-direction: column;
33
33
  align-items: center;
34
34
  justify-content: center;
35
- background: var(--tl-color-background, #ffffff);
36
- color: var(--tl-color-foreground, #0f172a);
35
+ background: var(--tl-color-background);
36
+ color: var(--tl-color-foreground);
37
37
  gap: 1rem;
38
38
  }
39
39
 
40
40
  .loading p {
41
41
  margin: 0;
42
42
  font-size: 0.875rem;
43
- color: var(--tl-color-muted-foreground, #64748b);
43
+ color: var(--tl-color-muted-foreground);
44
44
  }
45
45
 
46
46
  .spinner {
47
47
  width: 40px;
48
48
  height: 40px;
49
- border: 3px solid var(--tl-color-border, #e2e8f0);
50
- border-top-color: var(--tl-color-primary, #e64b12);
49
+ border: 3px solid var(--tl-color-border);
50
+ border-top-color: var(--tl-color-primary);
51
51
  border-radius: 50%;
52
52
  animation: tl-seat-map-spin 1s linear infinite;
53
53
  }
@@ -66,8 +66,8 @@
66
66
  flex-direction: column;
67
67
  align-items: center;
68
68
  justify-content: center;
69
- background: var(--tl-color-background, #ffffff);
70
- color: var(--tl-color-error, #ef4444);
69
+ background: var(--tl-color-background);
70
+ color: var(--tl-color-error);
71
71
  padding: 1.5rem;
72
72
  text-align: center;
73
73
  }
@@ -132,10 +132,10 @@ export class TlSeatMap {
132
132
  // Render
133
133
  // -------------------------------------------------------------------------
134
134
  render() {
135
- return (h("div", { key: 'c9beadc1792d7825ac3f99e6abc8b181e28fd1b6', class: "seat-map-container", style: {
135
+ return (h("div", { key: '1c6a23b0aa021c58fa62c106a92a2e970ca7f6d1', class: "seat-map-container", style: {
136
136
  width: this.width,
137
137
  height: this.height,
138
- } }, this.isLoading && (h("div", { key: 'ad792ea35f433a268c81722ce4e7d0e34780d337', class: "loading" }, h("div", { key: 'dd3e0657b16c2f265f59fbaa2ac39c559c2093b7', class: "spinner" }), h("p", { key: '2714e3073d0e5cddc50d530aa1cf46a7f73ba96f' }, "Loading seat map..."))), this.error && (h("div", { key: '3de9d73ab89a1ee963e73b4192d5c93e9667f11b', class: "error" }, h("p", { key: '0fea5c828b5b212ffdb45c9c7702d241e2b79cc4', class: "error-title" }, "Error"), h("p", { key: 'f2c090b982bb1ee6fb7d347d8860b4ce4798a891', class: "error-message" }, this.error))), h("iframe", { key: '8023219fdd467ec736f9627190690a6f73df3bb3', ref: (el) => (this.iframeRef = el), src: this.getIframeSrc(), class: {
138
+ } }, this.isLoading && (h("div", { key: '4a09ddbdbd38eb68675bb320cf33fd3ead5974a4', class: "loading" }, h("div", { key: 'c622ba1f996427f0624cfda2c782abd49ec72e19', class: "spinner" }), h("p", { key: '802f42554fc238a1ddc0f8a0f6a6b0de89bdfb60' }, "Loading seat map..."))), this.error && (h("div", { key: 'dde73e2430bad68c0a67306c0aa2b44810007cc5', class: "error" }, h("p", { key: '392d51bbc47ab34eab5670aa7a2faf800dabe638', class: "error-title" }, "Error"), h("p", { key: 'fcf6810f767f34bf3490c1af21320d262bdb961b', class: "error-message" }, this.error))), h("iframe", { key: '2333206fa6eb4fe6cdcbb1d30864d56d54583e1f', ref: (el) => (this.iframeRef = el), src: this.getIframeSrc(), class: {
139
139
  'seat-map-iframe': true,
140
140
  hidden: this.isLoading || !!this.error,
141
141
  }, title: "Ticketlayer Seat Map", allow: "fullscreen" })));
@@ -1,13 +1,13 @@
1
1
  :host {
2
2
  display: block;
3
- font-family: var(--tl-font-family, system-ui, sans-serif);
3
+ font-family: var(--tl-font-family);
4
4
  }
5
5
 
6
6
  .ticket {
7
- border: 1px solid var(--tl-border, #e5e7eb);
8
- border-radius: var(--tl-radius-lg, 0.75rem);
9
- background: var(--tl-card, #ffffff);
10
- padding: var(--tl-spacing-5, 1.25rem);
7
+ border: 1px solid var(--tl-border);
8
+ border-radius: var(--tl-radius-lg);
9
+ background: var(--tl-card);
10
+ padding: var(--tl-spacing-5);
11
11
  text-align: center;
12
12
  }
13
13
 
@@ -39,7 +39,7 @@
39
39
  left: 50%;
40
40
  transform: translate(-50%, -50%);
41
41
  padding: 0.25rem 0.5rem;
42
- border-radius: var(--tl-radius-sm, 0.25rem);
42
+ border-radius: var(--tl-radius-sm);
43
43
  background: rgba(17, 24, 39, 0.85);
44
44
  color: #ffffff;
45
45
  font-size: 0.7rem;
@@ -51,37 +51,37 @@
51
51
  .name {
52
52
  margin: 1rem 0 0;
53
53
  font-weight: 600;
54
- color: var(--tl-foreground, #111827);
54
+ color: var(--tl-foreground);
55
55
  }
56
56
 
57
57
  .meta {
58
58
  margin: 0.125rem 0 0;
59
59
  font-size: 0.875rem;
60
- color: var(--tl-muted-foreground, #6b7280);
60
+ color: var(--tl-muted-foreground);
61
61
  }
62
62
 
63
63
  .barcode {
64
64
  margin: 0.25rem 0 0;
65
65
  font-family: ui-monospace, monospace;
66
66
  font-size: 0.625rem;
67
- color: var(--tl-muted-foreground, #9ca3af);
67
+ color: var(--tl-muted-foreground);
68
68
  word-break: break-all;
69
69
  }
70
70
 
71
71
  .download {
72
72
  margin-top: 0.75rem;
73
73
  padding: 0.375rem 0.75rem;
74
- border: 1px solid var(--tl-border, #e5e7eb);
75
- border-radius: var(--tl-radius-md, 0.5rem);
74
+ border: 1px solid var(--tl-border);
75
+ border-radius: var(--tl-radius-md);
76
76
  background: transparent;
77
- color: var(--tl-foreground, #111827);
77
+ color: var(--tl-foreground);
78
78
  font-size: 0.875rem;
79
79
  font-weight: 500;
80
80
  cursor: pointer;
81
81
  }
82
82
 
83
83
  .download:hover {
84
- background: var(--tl-muted, #f3f4f6);
84
+ background: var(--tl-muted);
85
85
  }
86
86
 
87
87
  .download:disabled {