@ticketlayer/elements 0.4.0 → 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 (161) hide show
  1. package/dist/cjs/{index-I9CoYv3k.js → index-DanPUX0s.js} +1 -1
  2. package/dist/cjs/index.cjs.js +2 -1
  3. package/dist/cjs/loader.cjs.js +2 -2
  4. package/dist/cjs/ticket-selector-BYVL13k_.js +3623 -0
  5. package/dist/cjs/ticketlayer-elements.cjs.js +2 -2
  6. package/dist/cjs/tl-buy-modal.cjs.entry.js +3 -3
  7. package/dist/cjs/tl-buy-tickets-button.cjs.entry.js +89 -74
  8. package/dist/cjs/tl-buy-tickets-wrapper.cjs.entry.js +61 -30
  9. package/dist/cjs/tl-cart-badge.cjs.entry.js +74 -61
  10. package/dist/cjs/tl-cart-drawer.cjs.entry.js +51 -23
  11. package/dist/cjs/tl-cart.cjs.entry.js +71 -103
  12. package/dist/cjs/tl-checkout-wrapper.cjs.entry.js +51 -29
  13. package/dist/cjs/tl-event-card.cjs.entry.js +43 -105
  14. package/dist/cjs/tl-event-list.cjs.entry.js +72 -35
  15. package/dist/cjs/tl-login.cjs.entry.js +74 -141
  16. package/dist/cjs/tl-my-orders.cjs.entry.js +52 -92
  17. package/dist/cjs/tl-occurrence-selector_2.cjs.entry.js +153 -921
  18. package/dist/cjs/tl-order-confirmation.cjs.entry.js +71 -107
  19. package/dist/cjs/tl-order-tickets_2.cjs.entry.js +48 -74
  20. package/dist/cjs/tl-promo-code.cjs.entry.js +50 -47
  21. package/dist/cjs/tl-seat-map.cjs.entry.js +3 -3
  22. package/dist/cjs/web-query-Ce73jB4k.js +16 -0
  23. package/dist/collection/components/tl-buy-modal/tl-buy-modal.js +2 -2
  24. package/dist/collection/components/tl-buy-tickets-button/tl-buy-tickets-button.css +37 -0
  25. package/dist/collection/components/tl-buy-tickets-button/tl-buy-tickets-button.js +105 -79
  26. package/dist/collection/components/tl-buy-tickets-wrapper/tl-buy-tickets-wrapper.js +73 -30
  27. package/dist/collection/components/tl-cart/tl-cart.js +82 -107
  28. package/dist/collection/components/tl-cart-badge/tl-cart-badge.js +62 -62
  29. package/dist/collection/components/tl-cart-drawer/tl-cart-drawer.js +56 -24
  30. package/dist/collection/components/tl-checkout-wrapper/tl-checkout-wrapper.js +61 -29
  31. package/dist/collection/components/tl-event-card/event-card-adapter.js +11 -23
  32. package/dist/collection/components/tl-event-card/tl-event-card.js +51 -84
  33. package/dist/collection/components/tl-event-list/tl-event-list.js +82 -38
  34. package/dist/collection/components/tl-login/tl-login.js +83 -147
  35. package/dist/collection/components/tl-my-orders/tl-my-orders.js +58 -96
  36. package/dist/collection/components/tl-occurrence-selector/tl-occurrence-selector.js +137 -516
  37. package/dist/collection/components/tl-order-confirmation/tl-order-confirmation.js +81 -115
  38. package/dist/collection/components/tl-order-tickets/tl-order-tickets.js +57 -92
  39. package/dist/collection/components/tl-promo-code/tl-promo-code.js +55 -50
  40. package/dist/collection/components/tl-seat-map/tl-seat-map.js +2 -2
  41. package/dist/collection/components/tl-ticket-selector/tl-ticket-selector.js +34 -20
  42. package/dist/collection/utils/order-summary.js +15 -135
  43. package/dist/collection/utils/testing/deep-text.js +29 -0
  44. package/dist/collection/utils/testing/fixtures.js +290 -0
  45. package/dist/collection/utils/testing/mock-sdk.js +13 -2
  46. package/dist/collection/utils/web-query.js +12 -0
  47. package/dist/collection/utils/web-storage.js +22 -0
  48. package/dist/components/ticket-selector.js +1 -0
  49. package/dist/components/tl-buy-modal.js +1 -1
  50. package/dist/components/tl-buy-tickets-button.js +1 -1
  51. package/dist/components/tl-buy-tickets-wrapper2.js +1 -1
  52. package/dist/components/tl-cart-badge.js +1 -1
  53. package/dist/components/tl-cart-drawer.js +1 -1
  54. package/dist/components/tl-cart2.js +1 -1
  55. package/dist/components/tl-checkout-wrapper2.js +1 -1
  56. package/dist/components/tl-event-card2.js +1 -1
  57. package/dist/components/tl-event-list.js +1 -1
  58. package/dist/components/tl-login2.js +1 -1
  59. package/dist/components/tl-my-orders.js +1 -1
  60. package/dist/components/tl-occurrence-selector2.js +1 -1
  61. package/dist/components/tl-order-confirmation.js +1 -1
  62. package/dist/components/tl-order-tickets2.js +1 -1
  63. package/dist/components/tl-promo-code.js +1 -1
  64. package/dist/components/tl-seat-map.js +1 -1
  65. package/dist/components/tl-ticket-selector2.js +1 -1
  66. package/dist/components/web-query.js +1 -0
  67. package/dist/elements.docs.json +34 -34
  68. package/dist/esm/{index-OoYV1faY.js → index-imfxp765.js} +1 -1
  69. package/dist/esm/index.js +2 -1
  70. package/dist/esm/loader.js +3 -3
  71. package/dist/esm/ticket-selector-BwChnHVC.js +3606 -0
  72. package/dist/esm/ticketlayer-elements.js +3 -3
  73. package/dist/esm/tl-buy-modal.entry.js +3 -3
  74. package/dist/esm/tl-buy-tickets-button.entry.js +90 -75
  75. package/dist/esm/tl-buy-tickets-wrapper.entry.js +62 -31
  76. package/dist/esm/tl-cart-badge.entry.js +75 -62
  77. package/dist/esm/tl-cart-drawer.entry.js +52 -24
  78. package/dist/esm/tl-cart.entry.js +72 -104
  79. package/dist/esm/tl-checkout-wrapper.entry.js +52 -30
  80. package/dist/esm/tl-event-card.entry.js +44 -106
  81. package/dist/esm/tl-event-list.entry.js +73 -36
  82. package/dist/esm/tl-login.entry.js +75 -142
  83. package/dist/esm/tl-my-orders.entry.js +53 -93
  84. package/dist/esm/tl-occurrence-selector_2.entry.js +152 -920
  85. package/dist/esm/tl-order-confirmation.entry.js +72 -108
  86. package/dist/esm/tl-order-tickets_2.entry.js +49 -75
  87. package/dist/esm/tl-promo-code.entry.js +51 -48
  88. package/dist/esm/tl-seat-map.entry.js +3 -3
  89. package/dist/esm/web-query-Bdbb2lk5.js +14 -0
  90. package/dist/ticketlayer-elements/index.esm.js +1 -1
  91. package/dist/ticketlayer-elements/p-45923d2c.entry.js +1 -0
  92. package/dist/ticketlayer-elements/p-492adc77.entry.js +1 -0
  93. package/dist/ticketlayer-elements/p-4cfc9e53.entry.js +1 -0
  94. package/dist/ticketlayer-elements/{p-ac264adc.entry.js → p-5135630e.entry.js} +1 -1
  95. package/dist/ticketlayer-elements/p-5bbce3b5.entry.js +1 -0
  96. package/dist/ticketlayer-elements/p-7d547721.entry.js +1 -0
  97. package/dist/ticketlayer-elements/p-7fe7b6ba.entry.js +1 -0
  98. package/dist/ticketlayer-elements/p-8122f0c1.entry.js +1 -0
  99. package/dist/ticketlayer-elements/p-98d40869.entry.js +1 -0
  100. package/dist/ticketlayer-elements/p-Bdbb2lk5.js +1 -0
  101. package/dist/ticketlayer-elements/p-BwChnHVC.js +1 -0
  102. package/dist/ticketlayer-elements/p-abec290a.entry.js +1 -0
  103. package/dist/ticketlayer-elements/p-ac79a3f6.entry.js +1 -0
  104. package/dist/ticketlayer-elements/p-d01f4429.entry.js +1 -0
  105. package/dist/ticketlayer-elements/p-d7ffaf46.entry.js +1 -0
  106. package/dist/ticketlayer-elements/p-e7170f3c.entry.js +1 -0
  107. package/dist/ticketlayer-elements/p-e787e862.entry.js +1 -0
  108. package/dist/ticketlayer-elements/p-f1ff931f.entry.js +1 -0
  109. package/dist/ticketlayer-elements/p-imfxp765.js +3 -0
  110. package/dist/ticketlayer-elements/ticketlayer-elements.css +1 -1
  111. package/dist/ticketlayer-elements/ticketlayer-elements.esm.js +1 -1
  112. package/dist/types/components/tl-buy-tickets-button/tl-buy-tickets-button.d.ts +40 -8
  113. package/dist/types/components/tl-buy-tickets-wrapper/tl-buy-tickets-wrapper.d.ts +20 -1
  114. package/dist/types/components/tl-cart/tl-cart.d.ts +40 -42
  115. package/dist/types/components/tl-cart-badge/tl-cart-badge.d.ts +22 -7
  116. package/dist/types/components/tl-cart-drawer/tl-cart-drawer.d.ts +28 -5
  117. package/dist/types/components/tl-checkout-wrapper/tl-checkout-wrapper.d.ts +18 -1
  118. package/dist/types/components/tl-event-card/event-card-adapter.d.ts +9 -20
  119. package/dist/types/components/tl-event-card/tl-event-card.d.ts +25 -28
  120. package/dist/types/components/tl-event-list/tl-event-list.d.ts +46 -6
  121. package/dist/types/components/tl-login/tl-login.d.ts +68 -22
  122. package/dist/types/components/tl-my-orders/tl-my-orders.d.ts +30 -12
  123. package/dist/types/components/tl-occurrence-selector/tl-occurrence-selector.d.ts +73 -161
  124. package/dist/types/components/tl-order-confirmation/tl-order-confirmation.d.ts +48 -15
  125. package/dist/types/components/tl-order-tickets/tl-order-tickets.d.ts +29 -31
  126. package/dist/types/components/tl-promo-code/tl-promo-code.d.ts +28 -10
  127. package/dist/types/components/tl-ticket/tl-ticket.d.ts +2 -11
  128. package/dist/types/components/tl-ticket-selector/tl-ticket-selector.d.ts +22 -4
  129. package/dist/types/components.d.ts +270 -62
  130. package/dist/types/utils/order-summary.d.ts +14 -97
  131. package/dist/types/utils/testing/deep-text.d.ts +13 -0
  132. package/dist/types/utils/testing/fixtures.d.ts +225 -0
  133. package/dist/types/utils/testing/mock-sdk.d.ts +14 -1
  134. package/dist/types/utils/web-query.d.ts +18 -0
  135. package/dist/types/utils/web-storage.d.ts +16 -0
  136. package/elements.catalogue.json +602 -50
  137. package/package.json +8 -6
  138. package/dist/cjs/money-BtmM2gt-.js +0 -118
  139. package/dist/cjs/order-summary-X3TlZxc2.js +0 -150
  140. package/dist/components/money.js +0 -1
  141. package/dist/components/order-summary.js +0 -1
  142. package/dist/esm/money-4ig0-WB6.js +0 -112
  143. package/dist/esm/order-summary-DiDtvfZM.js +0 -136
  144. package/dist/ticketlayer-elements/p-0814420f.entry.js +0 -1
  145. package/dist/ticketlayer-elements/p-0acabc26.entry.js +0 -1
  146. package/dist/ticketlayer-elements/p-109a3feb.entry.js +0 -1
  147. package/dist/ticketlayer-elements/p-1a4ba71c.entry.js +0 -1
  148. package/dist/ticketlayer-elements/p-318965c1.entry.js +0 -1
  149. package/dist/ticketlayer-elements/p-347c7fd1.entry.js +0 -1
  150. package/dist/ticketlayer-elements/p-3649c26d.entry.js +0 -1
  151. package/dist/ticketlayer-elements/p-40260cc9.entry.js +0 -1
  152. package/dist/ticketlayer-elements/p-4ig0-WB6.js +0 -1
  153. package/dist/ticketlayer-elements/p-55fd2f9f.entry.js +0 -1
  154. package/dist/ticketlayer-elements/p-62ee659b.entry.js +0 -1
  155. package/dist/ticketlayer-elements/p-7d4ecc2b.entry.js +0 -1
  156. package/dist/ticketlayer-elements/p-7f0abf95.entry.js +0 -1
  157. package/dist/ticketlayer-elements/p-BXpVGW51.js +0 -1
  158. package/dist/ticketlayer-elements/p-OoYV1faY.js +0 -3
  159. package/dist/ticketlayer-elements/p-a3c1b26c.entry.js +0 -1
  160. package/dist/ticketlayer-elements/p-aeb4c6fb.entry.js +0 -1
  161. package/dist/ticketlayer-elements/p-ba62541f.entry.js +0 -1
@@ -1,6 +1,7 @@
1
1
  import { h } from "@stencil/core";
2
- import { whenSDK } from "../../utils/sdk-connector";
3
- import { errorMessage } from "../../utils/order-summary";
2
+ import { createElement, loginCore, } from "@ticketlayer/elements-core";
3
+ import { getSDK, whenSDK } from "../../utils/sdk-connector";
4
+ import { webQuery } from "../../utils/web-query";
4
5
  /**
5
6
  * tl-login - magic-link sign-in. The customer enters an email, we ask the SDK
6
7
  * to send a link (POST /sales/customer-auth/magic-link), then either the link
@@ -11,6 +12,13 @@ import { errorMessage } from "../../utils/order-summary";
11
12
  * States: enter email -> check your inbox (resend, enter code) -> signed in
12
13
  * (email + sign out).
13
14
  *
15
+ * The web harness for `loginCore`: it renders and it forwards. The phases, the
16
+ * exchange, the auth subscriptions and every string are in
17
+ * `@ticketlayer/elements-core` (AGENTS.md, TKT-11). What is left here is the
18
+ * Stencil surface and the two genuinely web things: the client the page
19
+ * published on `window.Ticketlayer`, and `window.location.search`, which is
20
+ * where this runtime keeps the `?code=` the core asks for.
21
+ *
14
22
  * Usage: <tl-login></tl-login>
15
23
  *
16
24
  * @event tlAuthChanged - signed in or out ({ customer, authenticated })
@@ -23,167 +31,92 @@ export class TlLogin {
23
31
  this.heading = 'Sign in';
24
32
  /** Offer a "enter the code" path alongside the emailed link (default true). */
25
33
  this.allowCodeEntry = true;
26
- this.phase = 'email';
27
- this.email = '';
28
- this.code = '';
29
- this.busy = false;
30
- this.error = null;
31
- this.notice = null;
32
- this.customer = null;
33
- this.auth = null;
34
- this.unsubscribes = [];
35
- // A magic-link code is single-use; never exchange the URL code twice.
36
- this.urlCodeUsed = false;
34
+ this.element = createElement(loginCore, {
35
+ client: () => getSDK(),
36
+ clientReady: () => whenSDK(),
37
+ query: webQuery,
38
+ emit: (event, detail) => this.forward(event, detail),
39
+ });
40
+ /** The core's whole view. Assigning it is what makes Stencil re-render. */
41
+ this.view = this.element.view;
37
42
  this.handleEmailInput = (e) => {
38
- this.email = e.target.value;
39
- this.error = null;
43
+ this.element.actions.setEmail(e.target.value);
40
44
  };
41
45
  this.handleCodeInput = (e) => {
42
- this.code = e.target.value;
43
- this.error = null;
46
+ this.element.actions.setCode(e.target.value);
44
47
  };
45
48
  this.handleEmailSubmit = (e) => {
46
49
  e.preventDefault();
47
- this.requestLink();
50
+ this.element.actions.requestLink();
48
51
  };
49
52
  this.handleCodeSubmit = (e) => {
50
53
  e.preventDefault();
51
- this.exchange(this.code.trim());
54
+ this.element.actions.submitCode();
52
55
  };
53
56
  }
54
- async componentWillLoad() {
55
- const sdk = await whenSDK().catch(() => null);
56
- if (!sdk) {
57
- this.error = 'Sign-in is not available right now';
58
- return;
59
- }
60
- this.auth = sdk.getAuthManager?.() ?? null;
61
- if (!this.auth) {
62
- this.error = 'Sign-in is not available with this SDK';
63
- return;
64
- }
65
- this.syncFromSDK();
66
- // The session hydrates after the SDK publishes; follow both signals.
67
- this.unsubscribes.push(sdk.on('auth:changed', () => this.syncFromSDK()));
68
- this.unsubscribes.push(sdk.on('session:changed', () => this.syncFromSDK()));
69
- const urlCode = this.codeFromUrl();
70
- if (urlCode && !this.auth.authenticated) {
71
- await this.exchange(urlCode);
72
- }
57
+ syncProps() {
58
+ this.element.setProps({ heading: this.heading, allowCodeEntry: this.allowCodeEntry });
73
59
  }
74
- disconnectedCallback() {
75
- this.unsubscribes.forEach((off) => off());
76
- this.unsubscribes = [];
77
- }
78
- codeFromUrl() {
79
- if (typeof window === 'undefined' || !window.location?.search || this.urlCodeUsed)
80
- return null;
81
- const code = new URLSearchParams(window.location.search).get('code');
82
- if (code)
83
- this.urlCodeUsed = true;
84
- return code;
60
+ connectedCallback() {
61
+ this.syncProps();
62
+ this.unsubscribe ?? (this.unsubscribe = this.element.subscribe((view) => {
63
+ this.view = view;
64
+ }));
65
+ this.view = this.element.view;
66
+ this.teardown = this.element.mount();
85
67
  }
86
- syncFromSDK() {
87
- if (!this.auth)
88
- return;
89
- if (this.auth.authenticated) {
90
- this.customer = this.auth.customer ?? null;
91
- this.phase = 'signed-in';
92
- }
93
- else if (this.phase === 'signed-in') {
94
- this.customer = null;
95
- this.phase = 'email';
96
- }
97
- }
98
- async requestLink(resend = false) {
99
- const email = this.email.trim();
100
- if (!email) {
101
- this.error = 'Enter your email address';
102
- return;
103
- }
104
- if (!this.auth)
105
- return;
106
- this.busy = true;
107
- this.error = null;
108
- this.notice = null;
109
- try {
110
- // Always reports success; account existence is never revealed.
111
- await this.auth.requestMagicLink({ email });
112
- this.phase = 'sent';
113
- this.notice = resend ? 'Sent again. Check your inbox.' : null;
114
- this.tlMagicLinkSent.emit({ email });
115
- }
116
- catch (err) {
117
- console.error('[tl-login] Magic-link request failed:', err);
118
- this.error = errorMessage(err, 'Could not send your sign-in link');
119
- this.tlError.emit({ message: this.error });
120
- }
121
- finally {
122
- this.busy = false;
123
- }
68
+ /**
69
+ * Nothing is painted until this page's client is up.
70
+ *
71
+ * A rendering decision, and the harness's to make: Stencil waits on this
72
+ * promise before the first render, and without it the form would flash
73
+ * disabled for a microtask on every page. The core is already at work by
74
+ * now - `connectedCallback` mounted it - so this waits on the same
75
+ * `whenSDK()` it is waiting on rather than deciding anything of its own, and
76
+ * a page that never starts Live renders the core's "not available" state
77
+ * instead of hanging.
78
+ */
79
+ componentWillLoad() {
80
+ return whenSDK().catch(() => undefined);
124
81
  }
125
- async exchange(code) {
126
- if (!code) {
127
- this.error = 'Enter the code from your email';
128
- return;
129
- }
130
- if (!this.auth)
131
- return;
132
- const previous = this.phase;
133
- this.busy = true;
134
- this.error = null;
135
- this.phase = 'verifying';
136
- try {
137
- const res = (await this.auth.exchange(code));
138
- this.customer = this.auth.customer ?? res?.customer ?? null;
139
- this.phase = 'signed-in';
140
- this.code = '';
141
- this.tlAuthChanged.emit({ customer: this.customer, authenticated: true });
142
- }
143
- catch (err) {
144
- console.error('[tl-login] Code exchange failed:', err);
145
- this.phase = previous === 'verifying' || previous === 'email' ? 'email' : 'code';
146
- this.error = 'That code did not work. Check it and try again, or request a new link.';
147
- this.tlError.emit({ message: this.error });
148
- }
149
- finally {
150
- this.busy = false;
151
- }
82
+ /**
83
+ * Off screen: run the mount's teardown, which drops both auth
84
+ * subscriptions. Not `destroy()`: Stencil calls this for a DOM move as well
85
+ * as a removal, and a destroyed core never mounts again.
86
+ */
87
+ disconnectedCallback() {
88
+ this.teardown?.();
89
+ this.teardown = undefined;
90
+ this.unsubscribe?.();
91
+ this.unsubscribe = undefined;
152
92
  }
153
- async signOut() {
154
- if (!this.auth)
155
- return;
156
- this.busy = true;
157
- try {
158
- await this.auth.logout();
159
- }
160
- catch (err) {
161
- console.error('[tl-login] Sign out failed:', err);
162
- }
163
- finally {
164
- this.busy = false;
165
- this.customer = null;
166
- this.phase = 'email';
167
- this.tlAuthChanged.emit({ customer: null, authenticated: false });
168
- }
93
+ forward(event, detail) {
94
+ if (event === 'tlAuthChanged')
95
+ this.tlAuthChanged.emit(detail);
96
+ else if (event === 'tlMagicLinkSent')
97
+ this.tlMagicLinkSent.emit(detail);
98
+ else if (event === 'tlError')
99
+ this.tlError.emit(detail);
169
100
  }
170
101
  renderError() {
171
- return this.error ? (h("p", { class: "error", role: "alert" }, this.error)) : null;
102
+ const error = this.view.state.error;
103
+ return error ? (h("p", { class: "error", role: "alert" }, error)) : null;
172
104
  }
173
105
  render() {
174
- if (this.phase === 'signed-in') {
175
- return (h("div", { class: "login signed-in" }, h("p", { class: "signed-in-text" }, "Signed in as ", h("strong", null, this.customer?.email ?? 'your account')), h("button", { type: "button", class: "secondary", disabled: this.busy, onClick: () => this.signOut() }, "Sign out")));
106
+ const { status, state, strings, derived } = this.view;
107
+ if (status === 'signed-in') {
108
+ return (h("div", { class: "login signed-in" }, h("p", { class: "signed-in-text" }, strings.signedInPrefix, h("strong", null, derived.signedInEmail)), h("button", { type: "button", class: "secondary", disabled: state.busy, onClick: () => this.element.actions.signOut() }, strings.signOut)));
176
109
  }
177
- if (this.phase === 'verifying') {
178
- return (h("div", { class: "login" }, h("h2", { class: "title" }, "Signing you in\u2026"), h("p", { class: "lead" }, "Just a moment.")));
110
+ if (status === 'verifying') {
111
+ return (h("div", { class: "login" }, h("h2", { class: "title" }, strings.verifyingTitle), h("p", { class: "lead" }, strings.verifyingLead)));
179
112
  }
180
- if (this.phase === 'sent') {
181
- return (h("div", { class: "login" }, h("h2", { class: "title" }, "Check your inbox"), h("p", { class: "lead" }, "We've sent a sign-in link to ", h("strong", null, this.email), ". Open it on this device to continue."), this.notice && h("p", { class: "notice" }, this.notice), this.renderError(), h("div", { class: "actions" }, h("button", { type: "button", class: "secondary", disabled: this.busy, onClick: () => this.requestLink(true) }, this.busy ? 'Sending…' : 'Resend link'), this.allowCodeEntry && (h("button", { type: "button", class: "link", onClick: () => (this.phase = 'code') }, "Enter the code instead")), h("button", { type: "button", class: "link", onClick: () => (this.phase = 'email') }, "Use a different email"))));
113
+ if (status === 'sent') {
114
+ return (h("div", { class: "login" }, h("h2", { class: "title" }, strings.sentTitle), h("p", { class: "lead" }, strings.sentLeadBefore, h("strong", null, state.email), strings.sentLeadAfter), state.notice && h("p", { class: "notice" }, state.notice), this.renderError(), h("div", { class: "actions" }, h("button", { type: "button", class: "secondary", disabled: state.busy, onClick: () => this.element.actions.requestLink(true) }, derived.resendLabel), derived.showCodeEntry && (h("button", { type: "button", class: "link", onClick: () => this.element.actions.goTo('code') }, strings.enterCodeInstead)), h("button", { type: "button", class: "link", onClick: () => this.element.actions.goTo('email') }, strings.differentEmail))));
182
115
  }
183
- if (this.phase === 'code') {
184
- return (h("form", { class: "login", onSubmit: this.handleCodeSubmit }, h("h2", { class: "title" }, "Enter your code"), h("p", { class: "lead" }, "Type the 6-digit code from the email we sent to ", h("strong", null, this.email), "."), h("label", { class: "label", htmlFor: "tl-login-code" }, "Code"), h("input", { id: "tl-login-code", class: "input code-input", type: "text", inputmode: "numeric", autocomplete: "one-time-code", placeholder: "123456", value: this.code, disabled: this.busy, onInput: this.handleCodeInput }), this.renderError(), h("button", { type: "submit", class: "primary", disabled: this.busy }, this.busy ? 'Checking…' : 'Sign in'), h("div", { class: "actions" }, h("button", { type: "button", class: "link", onClick: () => this.requestLink(true) }, "Resend link"), h("button", { type: "button", class: "link", onClick: () => (this.phase = 'sent') }, "Back"))));
116
+ if (status === 'code') {
117
+ return (h("form", { class: "login", onSubmit: this.handleCodeSubmit }, h("h2", { class: "title" }, strings.codeTitle), h("p", { class: "lead" }, strings.codeLeadBefore, h("strong", null, state.email), strings.codeLeadAfter), h("label", { class: "label", htmlFor: "tl-login-code" }, strings.codeLabel), h("input", { id: "tl-login-code", class: "input code-input", type: "text", inputmode: "numeric", autocomplete: "one-time-code", placeholder: strings.codePlaceholder, value: state.code, disabled: state.busy, onInput: this.handleCodeInput }), this.renderError(), h("button", { type: "submit", class: "primary", disabled: state.busy }, derived.codeSubmitLabel), h("div", { class: "actions" }, h("button", { type: "button", class: "link", onClick: () => this.element.actions.requestLink(true) }, strings.resendLink), h("button", { type: "button", class: "link", onClick: () => this.element.actions.goTo('sent') }, strings.back))));
185
118
  }
186
- return (h("form", { class: "login", onSubmit: this.handleEmailSubmit }, h("h2", { class: "title" }, this.heading), h("p", { class: "lead" }, "Enter your email and we'll send you a secure sign-in link. No password needed."), h("label", { class: "label", htmlFor: "tl-login-email" }, "Email address"), h("input", { id: "tl-login-email", class: "input", type: "email", autocomplete: "email", placeholder: "you@example.com", required: true, value: this.email, disabled: this.busy || !this.auth, onInput: this.handleEmailInput }), this.renderError(), h("button", { type: "submit", class: "primary", disabled: this.busy || !this.auth }, this.busy ? 'Sending link…' : 'Email me a sign-in link')));
119
+ return (h("form", { class: "login", onSubmit: this.handleEmailSubmit }, h("h2", { class: "title" }, this.heading), h("p", { class: "lead" }, strings.emailLead), h("label", { class: "label", htmlFor: "tl-login-email" }, strings.emailLabel), h("input", { id: "tl-login-email", class: "input", type: "email", autocomplete: "email", placeholder: strings.emailPlaceholder, required: true, value: state.email, disabled: derived.inputsDisabled, onInput: this.handleEmailInput }), this.renderError(), h("button", { type: "submit", class: "primary", disabled: derived.inputsDisabled }, derived.emailSubmitLabel)));
187
120
  }
188
121
  static get is() { return "tl-login"; }
189
122
  static get encapsulation() { return "shadow"; }
@@ -243,13 +176,7 @@ export class TlLogin {
243
176
  }
244
177
  static get states() {
245
178
  return {
246
- "phase": {},
247
- "email": {},
248
- "code": {},
249
- "busy": {},
250
- "error": {},
251
- "notice": {},
252
- "customer": {}
179
+ "view": {}
253
180
  };
254
181
  }
255
182
  static get events() {
@@ -306,4 +233,13 @@ export class TlLogin {
306
233
  }
307
234
  }];
308
235
  }
236
+ static get watchers() {
237
+ return [{
238
+ "propName": "heading",
239
+ "methodName": "syncProps"
240
+ }, {
241
+ "propName": "allowCodeEntry",
242
+ "methodName": "syncProps"
243
+ }];
244
+ }
309
245
  }
@@ -1,12 +1,16 @@
1
1
  import { h } from "@stencil/core";
2
- import { whenSDK } from "../../utils/sdk-connector";
3
- import { errorMessage, formatDate, formatPrice, isAuthError, orderEventName, statusLabel, statusTone, ticketCount, } from "../../utils/order-summary";
2
+ import { createElement, myOrdersCore, } from "@ticketlayer/elements-core";
3
+ import { getSDK, whenSDK } from "../../utils/sdk-connector";
4
4
  /**
5
5
  * tl-my-orders - the signed-in customer's orders (GET /sales/my-orders through
6
6
  * the SDK): event, date, ticket count, total and status per row. Clicking a
7
7
  * row expands it to show the order's tickets (tl-order-tickets). Shows
8
8
  * tl-login inline when nobody is signed in.
9
9
  *
10
+ * The web harness for `myOrdersCore`: it renders and it forwards. Who is
11
+ * signed in, the two auth subscriptions that keep that current, the listing
12
+ * and every string are in `@ticketlayer/elements-core` (AGENTS.md, TKT-11).
13
+ *
10
14
  * Usage: <tl-my-orders></tl-my-orders>
11
15
  *
12
16
  * @event tlOrderSelect - a row was expanded ({ orderId })
@@ -18,107 +22,60 @@ export class TlMyOrders {
18
22
  this.emptyMessage = "When you buy tickets they'll show up here, ready to view and download.";
19
23
  /** Columns for the expanded ticket grid. */
20
24
  this.columns = 2;
21
- this.orders = [];
22
- this.authenticated = false;
23
- this.loading = true;
24
- this.error = null;
25
- this.expandedId = null;
26
- this.sdk = null;
27
- this.unsubscribes = [];
25
+ this.element = createElement(myOrdersCore, {
26
+ client: () => getSDK(),
27
+ clientReady: () => whenSDK(),
28
+ emit: (event, detail) => this.forward(event, detail),
29
+ });
30
+ /** The core's whole view. Assigning it is what makes Stencil re-render. */
31
+ this.view = this.element.view;
28
32
  }
29
- async componentWillLoad() {
30
- this.sdk = await whenSDK().catch(() => null);
31
- if (!this.sdk) {
32
- this.loading = false;
33
- this.error = 'Orders are not available right now';
34
- return;
35
- }
36
- // Older SDKs have no auth accessor: try the list and let a 401 decide.
37
- const auth = this.sdk.getAuthManager?.();
38
- this.authenticated = auth ? auth.authenticated : true;
39
- // The session hydrates after the SDK publishes; follow both signals.
40
- this.unsubscribes.push(this.sdk.on('auth:changed', () => this.syncAuth()));
41
- this.unsubscribes.push(this.sdk.on('session:changed', () => this.syncAuth()));
42
- if (this.authenticated) {
43
- await this.load();
44
- }
45
- else {
46
- this.loading = false;
47
- }
33
+ syncProps() {
34
+ this.element.setProps({ emptyMessage: this.emptyMessage, columns: this.columns });
48
35
  }
49
- disconnectedCallback() {
50
- this.unsubscribes.forEach((off) => off());
51
- this.unsubscribes = [];
36
+ connectedCallback() {
37
+ this.syncProps();
38
+ this.unsubscribe ?? (this.unsubscribe = this.element.subscribe((view) => {
39
+ this.view = view;
40
+ }));
41
+ this.view = this.element.view;
42
+ this.teardown = this.element.mount();
52
43
  }
53
- syncAuth() {
54
- const auth = this.sdk?.getAuthManager?.();
55
- if (!auth)
56
- return;
57
- const was = this.authenticated;
58
- this.authenticated = auth.authenticated;
59
- if (this.authenticated && !was) {
60
- this.load();
61
- }
62
- else if (!this.authenticated && was) {
63
- this.orders = [];
64
- this.expandedId = null;
65
- }
66
- }
67
- async load() {
68
- if (!this.sdk)
69
- return;
70
- try {
71
- this.loading = true;
72
- this.error = null;
73
- const orders = this.sdk.getOrdersManager?.();
74
- if (!orders)
75
- throw new Error('Orders are not available');
76
- const list = (await orders.mine());
77
- this.orders = list ?? [];
78
- this.authenticated = true;
79
- }
80
- catch (err) {
81
- if (isAuthError(err)) {
82
- // Not signed in: fall through to the inline login rather than an error.
83
- this.authenticated = false;
84
- this.orders = [];
85
- }
86
- else {
87
- console.error('[tl-my-orders] Failed to load orders:', err);
88
- this.error = errorMessage(err, 'Failed to load your orders');
89
- this.tlError.emit({ message: this.error });
90
- }
91
- }
92
- finally {
93
- this.loading = false;
94
- }
44
+ /**
45
+ * Off screen: run the mount's teardown, which drops both auth
46
+ * subscriptions. Not `destroy()`: Stencil calls this for a DOM move as well
47
+ * as a removal, and a destroyed core never mounts again.
48
+ */
49
+ disconnectedCallback() {
50
+ this.teardown?.();
51
+ this.teardown = undefined;
52
+ this.unsubscribe?.();
53
+ this.unsubscribe = undefined;
95
54
  }
96
- toggle(orderId) {
97
- const opening = this.expandedId !== orderId;
98
- this.expandedId = opening ? orderId : null;
99
- if (opening)
100
- this.tlOrderSelect.emit({ orderId });
55
+ forward(event, detail) {
56
+ if (event === 'tlOrderSelect')
57
+ this.tlOrderSelect.emit(detail);
58
+ else if (event === 'tlError')
59
+ this.tlError.emit(detail);
101
60
  }
102
- renderRow(order) {
103
- const expanded = this.expandedId === order.id;
104
- const count = ticketCount(order);
105
- const tone = statusTone(order.status);
106
- return (h("li", { class: { order: true, expanded }, key: order.id }, h("button", { type: "button", class: "order-row", "aria-expanded": expanded ? 'true' : 'false', onClick: () => this.toggle(order.id) }, h("span", { class: "order-main" }, h("span", { class: "order-number" }, "Order ", order.orderNumber), h("span", { class: "order-event" }, orderEventName(order) ?? 'Order'), h("span", { class: "order-meta" }, order.createdAt && h("span", null, formatDate(order.createdAt)), h("span", null, count, " ticket", count === 1 ? '' : 's'))), h("span", { class: "order-side" }, h("span", { class: "order-total" }, formatPrice(order.total, order.currency)), h("span", { class: `status status-${tone}` }, statusLabel(order.status)), h("svg", { class: "chevron", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "aria-hidden": "true" }, h("path", { "stroke-linecap": "round", "stroke-linejoin": "round", "stroke-width": "2", d: "M9 18l6-6-6-6" })))), expanded && (h("div", { class: "order-tickets" }, h("tl-order-tickets", { orderId: order.id, columns: this.columns })))));
61
+ renderRow(row) {
62
+ return (h("li", { class: { order: true, expanded: row.expanded }, key: row.id }, h("button", { type: "button", class: "order-row", "aria-expanded": row.expanded ? 'true' : 'false', onClick: () => this.element.actions.toggle(row.id) }, h("span", { class: "order-main" }, h("span", { class: "order-number" }, row.numberLabel), h("span", { class: "order-event" }, row.eventName), h("span", { class: "order-meta" }, row.dateLabel && h("span", null, row.dateLabel), h("span", null, row.countLabel))), h("span", { class: "order-side" }, h("span", { class: "order-total" }, row.totalLabel), h("span", { class: `status status-${row.statusTone}` }, row.statusLabel), h("svg", { class: "chevron", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "aria-hidden": "true" }, h("path", { "stroke-linecap": "round", "stroke-linejoin": "round", "stroke-width": "2", d: "M9 18l6-6-6-6" })))), row.expanded && (h("div", { class: "order-tickets" }, h("tl-order-tickets", { orderId: row.id, columns: this.columns })))));
107
63
  }
108
64
  render() {
109
- if (this.loading) {
65
+ const { status, strings, state, derived } = this.view;
66
+ if (status === 'loading') {
110
67
  return (h("div", { class: "orders" }, [0, 1, 2].map((i) => (h("div", { class: "skeleton", key: i })))));
111
68
  }
112
- if (!this.authenticated) {
113
- return (h("div", { class: "orders" }, h("div", { class: "signin" }, h("p", { class: "signin-title" }, "Sign in to see your orders"), h("p", { class: "signin-text" }, "Your tickets and order history live in your account.")), h("tl-login", null)));
69
+ if (status === 'signed-out') {
70
+ return (h("div", { class: "orders" }, h("div", { class: "signin" }, h("p", { class: "signin-title" }, strings.signInTitle), h("p", { class: "signin-text" }, strings.signInText)), h("tl-login", null)));
114
71
  }
115
- if (this.error) {
116
- return (h("div", { class: "orders" }, h("div", { class: "state error" }, h("p", { class: "state-title" }, "Could not load your orders"), h("p", { class: "state-text" }, this.error))));
72
+ if (status === 'error') {
73
+ return (h("div", { class: "orders" }, h("div", { class: "state error" }, h("p", { class: "state-title" }, strings.errorTitle), h("p", { class: "state-text" }, state.error))));
117
74
  }
118
- if (this.orders.length === 0) {
119
- return (h("div", { class: "orders" }, h("div", { class: "state empty" }, h("svg", { class: "empty-icon", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "aria-hidden": "true" }, h("path", { "stroke-linecap": "round", "stroke-linejoin": "round", "stroke-width": "1.5", d: "M2 9a3 3 0 0 1 0 6v2a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-2a3 3 0 0 1 0-6V7a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2Z" }), h("path", { "stroke-linecap": "round", "stroke-linejoin": "round", "stroke-width": "1.5", d: "M13 5v2M13 17v2M13 11v2" })), h("p", { class: "state-title" }, "No orders yet"), h("p", { class: "state-text" }, this.emptyMessage))));
75
+ if (status === 'empty') {
76
+ return (h("div", { class: "orders" }, h("div", { class: "state empty" }, h("svg", { class: "empty-icon", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "aria-hidden": "true" }, h("path", { "stroke-linecap": "round", "stroke-linejoin": "round", "stroke-width": "1.5", d: "M2 9a3 3 0 0 1 0 6v2a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-2a3 3 0 0 1 0-6V7a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2Z" }), h("path", { "stroke-linecap": "round", "stroke-linejoin": "round", "stroke-width": "1.5", d: "M13 5v2M13 17v2M13 11v2" })), h("p", { class: "state-title" }, strings.emptyTitle), h("p", { class: "state-text" }, this.emptyMessage))));
120
77
  }
121
- return (h("div", { class: "orders" }, h("ul", { class: "order-list" }, this.orders.map((o) => this.renderRow(o)))));
78
+ return (h("div", { class: "orders" }, h("ul", { class: "order-list" }, derived.rows.map((row) => this.renderRow(row)))));
122
79
  }
123
80
  static get is() { return "tl-my-orders"; }
124
81
  static get encapsulation() { return "shadow"; }
@@ -178,11 +135,7 @@ export class TlMyOrders {
178
135
  }
179
136
  static get states() {
180
137
  return {
181
- "orders": {},
182
- "authenticated": {},
183
- "loading": {},
184
- "error": {},
185
- "expandedId": {}
138
+ "view": {}
186
139
  };
187
140
  }
188
141
  static get events() {
@@ -218,4 +171,13 @@ export class TlMyOrders {
218
171
  }
219
172
  }];
220
173
  }
174
+ static get watchers() {
175
+ return [{
176
+ "propName": "emptyMessage",
177
+ "methodName": "syncProps"
178
+ }, {
179
+ "propName": "columns",
180
+ "methodName": "syncProps"
181
+ }];
182
+ }
221
183
  }