@ticketlayer/elements 0.2.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 (195) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +55 -0
  3. package/dist/cjs/index-DfzcrE9-.js +3598 -0
  4. package/dist/cjs/index.cjs.js +16 -0
  5. package/dist/cjs/loader.cjs.js +12 -0
  6. package/dist/cjs/order-summary-ZMkJ5bez.js +116 -0
  7. package/dist/cjs/sdk-connector-ClDF3013.js +108 -0
  8. package/dist/cjs/ticketlayer-elements.cjs.js +24 -0
  9. package/dist/cjs/tl-buy-modal.cjs.entry.js +127 -0
  10. package/dist/cjs/tl-buy-tickets-button.cjs.entry.js +131 -0
  11. package/dist/cjs/tl-buy-tickets-wrapper.cjs.entry.js +58 -0
  12. package/dist/cjs/tl-cart-badge.cjs.entry.js +109 -0
  13. package/dist/cjs/tl-cart-drawer.cjs.entry.js +67 -0
  14. package/dist/cjs/tl-cart.cjs.entry.js +148 -0
  15. package/dist/cjs/tl-checkout-wrapper.cjs.entry.js +57 -0
  16. package/dist/cjs/tl-event-card.cjs.entry.js +140 -0
  17. package/dist/cjs/tl-event-list.cjs.entry.js +80 -0
  18. package/dist/cjs/tl-login.cjs.entry.js +184 -0
  19. package/dist/cjs/tl-my-orders.cjs.entry.js +123 -0
  20. package/dist/cjs/tl-occurrence-selector_2.cjs.entry.js +754 -0
  21. package/dist/cjs/tl-order-confirmation.cjs.entry.js +146 -0
  22. package/dist/cjs/tl-order-tickets_2.cjs.entry.js +3198 -0
  23. package/dist/cjs/tl-promo-code.cjs.entry.js +75 -0
  24. package/dist/cjs/tl-seat-map.cjs.entry.js +147 -0
  25. package/dist/collection/collection-manifest.json +29 -0
  26. package/dist/collection/components/tl-buy-modal/tl-buy-modal.css +264 -0
  27. package/dist/collection/components/tl-buy-modal/tl-buy-modal.js +249 -0
  28. package/dist/collection/components/tl-buy-tickets-button/tl-buy-tickets-button.css +120 -0
  29. package/dist/collection/components/tl-buy-tickets-button/tl-buy-tickets-button.js +412 -0
  30. package/dist/collection/components/tl-buy-tickets-wrapper/tl-buy-tickets-wrapper.css +23 -0
  31. package/dist/collection/components/tl-buy-tickets-wrapper/tl-buy-tickets-wrapper.js +192 -0
  32. package/dist/collection/components/tl-cart/tl-cart.css +251 -0
  33. package/dist/collection/components/tl-cart/tl-cart.js +314 -0
  34. package/dist/collection/components/tl-cart-badge/tl-cart-badge.css +138 -0
  35. package/dist/collection/components/tl-cart-badge/tl-cart-badge.js +217 -0
  36. package/dist/collection/components/tl-cart-drawer/tl-cart-drawer.css +98 -0
  37. package/dist/collection/components/tl-cart-drawer/tl-cart-drawer.js +185 -0
  38. package/dist/collection/components/tl-checkout-wrapper/tl-checkout-wrapper.css +23 -0
  39. package/dist/collection/components/tl-checkout-wrapper/tl-checkout-wrapper.js +148 -0
  40. package/dist/collection/components/tl-event-card/event-card-adapter.js +23 -0
  41. package/dist/collection/components/tl-event-card/tl-event-card.css +175 -0
  42. package/dist/collection/components/tl-event-card/tl-event-card.js +205 -0
  43. package/dist/collection/components/tl-event-list/tl-event-list.css +134 -0
  44. package/dist/collection/components/tl-event-list/tl-event-list.js +171 -0
  45. package/dist/collection/components/tl-login/tl-login.css +133 -0
  46. package/dist/collection/components/tl-login/tl-login.js +309 -0
  47. package/dist/collection/components/tl-my-orders/tl-my-orders.css +204 -0
  48. package/dist/collection/components/tl-my-orders/tl-my-orders.js +221 -0
  49. package/dist/collection/components/tl-occurrence-selector/tl-occurrence-selector.css +658 -0
  50. package/dist/collection/components/tl-occurrence-selector/tl-occurrence-selector.js +707 -0
  51. package/dist/collection/components/tl-order-confirmation/tl-order-confirmation.css +176 -0
  52. package/dist/collection/components/tl-order-confirmation/tl-order-confirmation.js +298 -0
  53. package/dist/collection/components/tl-order-tickets/tl-order-tickets.css +30 -0
  54. package/dist/collection/components/tl-order-tickets/tl-order-tickets.js +122 -0
  55. package/dist/collection/components/tl-promo-code/tl-promo-code.css +106 -0
  56. package/dist/collection/components/tl-promo-code/tl-promo-code.js +181 -0
  57. package/dist/collection/components/tl-seat-map/tl-seat-map.css +85 -0
  58. package/dist/collection/components/tl-seat-map/tl-seat-map.js +366 -0
  59. package/dist/collection/components/tl-ticket/tl-ticket.css +90 -0
  60. package/dist/collection/components/tl-ticket/tl-ticket.js +129 -0
  61. package/dist/collection/components/tl-ticket-selector/tl-ticket-selector.css +205 -0
  62. package/dist/collection/components/tl-ticket-selector/tl-ticket-selector.js +332 -0
  63. package/dist/collection/index.js +10 -0
  64. package/dist/collection/utils/order-summary.js +101 -0
  65. package/dist/collection/utils/sdk-connector.js +100 -0
  66. package/dist/collection/utils/testing/mock-sdk.js +95 -0
  67. package/dist/components/index.d.ts +35 -0
  68. package/dist/components/index.js +1 -0
  69. package/dist/components/order-summary.js +1 -0
  70. package/dist/components/sdk-connector.js +1 -0
  71. package/dist/components/tl-buy-modal.d.ts +11 -0
  72. package/dist/components/tl-buy-modal.js +1 -0
  73. package/dist/components/tl-buy-tickets-button.d.ts +11 -0
  74. package/dist/components/tl-buy-tickets-button.js +1 -0
  75. package/dist/components/tl-buy-tickets-wrapper.d.ts +11 -0
  76. package/dist/components/tl-buy-tickets-wrapper.js +1 -0
  77. package/dist/components/tl-buy-tickets-wrapper2.js +1 -0
  78. package/dist/components/tl-cart-badge.d.ts +11 -0
  79. package/dist/components/tl-cart-badge.js +1 -0
  80. package/dist/components/tl-cart-drawer.d.ts +11 -0
  81. package/dist/components/tl-cart-drawer.js +1 -0
  82. package/dist/components/tl-cart.d.ts +11 -0
  83. package/dist/components/tl-cart.js +1 -0
  84. package/dist/components/tl-cart2.js +1 -0
  85. package/dist/components/tl-checkout-wrapper.d.ts +11 -0
  86. package/dist/components/tl-checkout-wrapper.js +1 -0
  87. package/dist/components/tl-checkout-wrapper2.js +1 -0
  88. package/dist/components/tl-event-card.d.ts +11 -0
  89. package/dist/components/tl-event-card.js +1 -0
  90. package/dist/components/tl-event-card2.js +1 -0
  91. package/dist/components/tl-event-list.d.ts +11 -0
  92. package/dist/components/tl-event-list.js +1 -0
  93. package/dist/components/tl-login.d.ts +11 -0
  94. package/dist/components/tl-login.js +1 -0
  95. package/dist/components/tl-login2.js +1 -0
  96. package/dist/components/tl-my-orders.d.ts +11 -0
  97. package/dist/components/tl-my-orders.js +1 -0
  98. package/dist/components/tl-occurrence-selector.d.ts +11 -0
  99. package/dist/components/tl-occurrence-selector.js +1 -0
  100. package/dist/components/tl-occurrence-selector2.js +1 -0
  101. package/dist/components/tl-order-confirmation.d.ts +11 -0
  102. package/dist/components/tl-order-confirmation.js +1 -0
  103. package/dist/components/tl-order-tickets.d.ts +11 -0
  104. package/dist/components/tl-order-tickets.js +1 -0
  105. package/dist/components/tl-order-tickets2.js +1 -0
  106. package/dist/components/tl-promo-code.d.ts +11 -0
  107. package/dist/components/tl-promo-code.js +1 -0
  108. package/dist/components/tl-seat-map.d.ts +11 -0
  109. package/dist/components/tl-seat-map.js +1 -0
  110. package/dist/components/tl-ticket-selector.d.ts +11 -0
  111. package/dist/components/tl-ticket-selector.js +1 -0
  112. package/dist/components/tl-ticket-selector2.js +1 -0
  113. package/dist/components/tl-ticket.d.ts +11 -0
  114. package/dist/components/tl-ticket.js +1 -0
  115. package/dist/components/tl-ticket2.js +1 -0
  116. package/dist/elements.docs.d.ts +454 -0
  117. package/dist/elements.docs.json +3008 -0
  118. package/dist/esm/index-Ba-RZTFI.js +3588 -0
  119. package/dist/esm/index.js +4 -0
  120. package/dist/esm/loader.js +10 -0
  121. package/dist/esm/order-summary-BGekdjN9.js +103 -0
  122. package/dist/esm/sdk-connector-CqhvfyKV.js +102 -0
  123. package/dist/esm/ticketlayer-elements.js +20 -0
  124. package/dist/esm/tl-buy-modal.entry.js +125 -0
  125. package/dist/esm/tl-buy-tickets-button.entry.js +129 -0
  126. package/dist/esm/tl-buy-tickets-wrapper.entry.js +56 -0
  127. package/dist/esm/tl-cart-badge.entry.js +107 -0
  128. package/dist/esm/tl-cart-drawer.entry.js +65 -0
  129. package/dist/esm/tl-cart.entry.js +146 -0
  130. package/dist/esm/tl-checkout-wrapper.entry.js +55 -0
  131. package/dist/esm/tl-event-card.entry.js +138 -0
  132. package/dist/esm/tl-event-list.entry.js +78 -0
  133. package/dist/esm/tl-login.entry.js +182 -0
  134. package/dist/esm/tl-my-orders.entry.js +121 -0
  135. package/dist/esm/tl-occurrence-selector_2.entry.js +751 -0
  136. package/dist/esm/tl-order-confirmation.entry.js +144 -0
  137. package/dist/esm/tl-order-tickets_2.entry.js +3195 -0
  138. package/dist/esm/tl-promo-code.entry.js +73 -0
  139. package/dist/esm/tl-seat-map.entry.js +145 -0
  140. package/dist/index.cjs.js +1 -0
  141. package/dist/index.js +1 -0
  142. package/dist/ticketlayer-elements/index.esm.js +1 -0
  143. package/dist/ticketlayer-elements/p-0291df4c.entry.js +1 -0
  144. package/dist/ticketlayer-elements/p-10f01a1f.entry.js +1 -0
  145. package/dist/ticketlayer-elements/p-11273f8f.entry.js +1 -0
  146. package/dist/ticketlayer-elements/p-340ab4f5.entry.js +1 -0
  147. package/dist/ticketlayer-elements/p-41d22d7c.entry.js +1 -0
  148. package/dist/ticketlayer-elements/p-82752e3b.entry.js +1 -0
  149. package/dist/ticketlayer-elements/p-976770f3.entry.js +1 -0
  150. package/dist/ticketlayer-elements/p-9a903ff6.entry.js +1 -0
  151. package/dist/ticketlayer-elements/p-9c9072c8.entry.js +1 -0
  152. package/dist/ticketlayer-elements/p-9d8163d6.entry.js +1 -0
  153. package/dist/ticketlayer-elements/p-BGekdjN9.js +1 -0
  154. package/dist/ticketlayer-elements/p-Ba-RZTFI.js +2 -0
  155. package/dist/ticketlayer-elements/p-CqhvfyKV.js +1 -0
  156. package/dist/ticketlayer-elements/p-a5c90b68.entry.js +1 -0
  157. package/dist/ticketlayer-elements/p-b6d479ad.entry.js +1 -0
  158. package/dist/ticketlayer-elements/p-dd43f015.entry.js +1 -0
  159. package/dist/ticketlayer-elements/p-eb640760.entry.js +1 -0
  160. package/dist/ticketlayer-elements/p-ee3aa227.entry.js +1 -0
  161. package/dist/ticketlayer-elements/p-fb9444c4.entry.js +1 -0
  162. package/dist/ticketlayer-elements/ticketlayer-elements.css +1 -0
  163. package/dist/ticketlayer-elements/ticketlayer-elements.esm.js +1 -0
  164. package/dist/types/components/tl-buy-modal/tl-buy-modal.d.ts +54 -0
  165. package/dist/types/components/tl-buy-tickets-button/tl-buy-tickets-button.d.ts +61 -0
  166. package/dist/types/components/tl-buy-tickets-wrapper/tl-buy-tickets-wrapper.d.ts +52 -0
  167. package/dist/types/components/tl-cart/tl-cart.d.ts +80 -0
  168. package/dist/types/components/tl-cart-badge/tl-cart-badge.d.ts +36 -0
  169. package/dist/types/components/tl-cart-drawer/tl-cart-drawer.d.ts +35 -0
  170. package/dist/types/components/tl-checkout-wrapper/tl-checkout-wrapper.d.ts +39 -0
  171. package/dist/types/components/tl-event-card/event-card-adapter.d.ts +23 -0
  172. package/dist/types/components/tl-event-card/tl-event-card.d.ts +55 -0
  173. package/dist/types/components/tl-event-list/tl-event-list.d.ts +30 -0
  174. package/dist/types/components/tl-login/tl-login.d.ts +59 -0
  175. package/dist/types/components/tl-my-orders/tl-my-orders.d.ts +39 -0
  176. package/dist/types/components/tl-occurrence-selector/tl-occurrence-selector.d.ts +208 -0
  177. package/dist/types/components/tl-order-confirmation/tl-order-confirmation.d.ts +54 -0
  178. package/dist/types/components/tl-order-tickets/tl-order-tickets.d.ts +21 -0
  179. package/dist/types/components/tl-promo-code/tl-promo-code.d.ts +39 -0
  180. package/dist/types/components/tl-seat-map/tl-seat-map.d.ts +68 -0
  181. package/dist/types/components/tl-ticket/tl-ticket.d.ts +28 -0
  182. package/dist/types/components/tl-ticket-selector/tl-ticket-selector.d.ts +78 -0
  183. package/dist/types/components.d.ts +1629 -0
  184. package/dist/types/index.d.ts +14 -0
  185. package/dist/types/stencil-public-runtime.d.ts +1839 -0
  186. package/dist/types/utils/order-summary.d.ts +67 -0
  187. package/dist/types/utils/sdk-connector.d.ts +113 -0
  188. package/dist/types/utils/testing/mock-sdk.d.ts +36 -0
  189. package/elements.catalogue.json +968 -0
  190. package/loader/cdn.js +1 -0
  191. package/loader/index.cjs.js +1 -0
  192. package/loader/index.d.ts +24 -0
  193. package/loader/index.es2017.js +1 -0
  194. package/loader/index.js +2 -0
  195. package/package.json +101 -0
@@ -0,0 +1,205 @@
1
+ :host {
2
+ /* Map SDK variables (--tl-*) to component variables (--tl-color-*) */
3
+ /* This allows the component to inherit theme from SDK */
4
+ --tl-color-primary: var(--tl-primary, #6366f1);
5
+ --tl-color-primary-foreground: var(--tl-primary-foreground, #ffffff);
6
+ --tl-color-background: var(--tl-background, #ffffff);
7
+ --tl-color-foreground: var(--tl-foreground, #111827);
8
+ --tl-color-muted: var(--tl-muted, #f3f4f6);
9
+ --tl-color-muted-foreground: var(--tl-muted-foreground, #6b7280);
10
+ --tl-color-border: var(--tl-border, #e5e7eb);
11
+ --tl-color-destructive: var(--tl-error, #ef4444);
12
+
13
+ display: block;
14
+ font-family: var(--tl-font-family, system-ui, sans-serif);
15
+ }
16
+
17
+ .ticket-selector {
18
+ background: var(--tl-color-background, #ffffff);
19
+ }
20
+
21
+ /* Skeleton loading */
22
+ .skeleton-container {
23
+ padding: var(--tl-spacing-4, 1rem);
24
+ }
25
+
26
+ .skeleton-row {
27
+ height: 60px;
28
+ background: var(--tl-color-muted, #f3f4f6);
29
+ border-radius: var(--tl-radius-md, 0.5rem);
30
+ margin-bottom: var(--tl-spacing-3, 0.75rem);
31
+ animation: pulse 1.5s ease-in-out infinite;
32
+ }
33
+
34
+ @keyframes pulse {
35
+ 0%, 100% { opacity: 1; }
36
+ 50% { opacity: 0.5; }
37
+ }
38
+
39
+ /* Error state */
40
+ .error-state {
41
+ padding: var(--tl-spacing-4, 1rem);
42
+ color: var(--tl-color-destructive, #ef4444);
43
+ }
44
+
45
+ .error-message {
46
+ margin: 0;
47
+ font-size: 14px;
48
+ }
49
+
50
+ /* Ticket list */
51
+ .ticket-list {
52
+ margin-bottom: var(--tl-spacing-4, 1rem);
53
+ }
54
+
55
+ /* Ticket row */
56
+ .ticket-row {
57
+ display: flex;
58
+ align-items: center;
59
+ justify-content: space-between;
60
+ padding: var(--tl-spacing-3, 0.75rem) 0;
61
+ border-bottom: 1px solid var(--tl-color-border, #e5e7eb);
62
+ }
63
+
64
+ .ticket-row.sold-out {
65
+ opacity: 0.5;
66
+ }
67
+
68
+ .ticket-info {
69
+ flex: 1;
70
+ min-width: 0;
71
+ }
72
+
73
+ .ticket-header {
74
+ display: flex;
75
+ align-items: center;
76
+ gap: var(--tl-spacing-2, 0.5rem);
77
+ flex-wrap: wrap;
78
+ }
79
+
80
+ .ticket-name {
81
+ font-weight: 600;
82
+ color: var(--tl-color-foreground, #111827);
83
+ font-size: 15px;
84
+ }
85
+
86
+ .status-badge {
87
+ font-size: 12px;
88
+ font-weight: 500;
89
+ padding: 2px 8px;
90
+ border-radius: 4px;
91
+ }
92
+
93
+ .status-badge.limited {
94
+ background-color: var(--tl-color-warning-bg, #fef3c7);
95
+ color: var(--tl-color-warning, #d97706);
96
+ }
97
+
98
+ .status-badge.sold-out {
99
+ background-color: var(--tl-color-destructive-bg, #fee2e2);
100
+ color: var(--tl-color-destructive, #ef4444);
101
+ }
102
+
103
+ .ticket-description {
104
+ margin: var(--tl-spacing-1, 0.25rem) 0 0;
105
+ font-size: 14px;
106
+ color: var(--tl-color-muted-foreground, #6b7280);
107
+ }
108
+
109
+ .ticket-price {
110
+ margin: var(--tl-spacing-1, 0.25rem) 0 0;
111
+ font-size: 14px;
112
+ font-weight: 600;
113
+ color: var(--tl-color-foreground, #111827);
114
+ }
115
+
116
+ /* Quantity controls */
117
+ .quantity-controls {
118
+ display: flex;
119
+ align-items: center;
120
+ gap: var(--tl-spacing-2, 0.5rem);
121
+ margin-left: var(--tl-spacing-4, 1rem);
122
+ }
123
+
124
+ .qty-btn {
125
+ width: 32px;
126
+ height: 32px;
127
+ border-radius: var(--tl-radius-sm, 0.25rem);
128
+ border: 1px solid var(--tl-color-border, #d1d5db);
129
+ background: var(--tl-color-background, #ffffff);
130
+ color: var(--tl-color-foreground, #111827);
131
+ font-size: 18px;
132
+ font-weight: 500;
133
+ cursor: pointer;
134
+ display: flex;
135
+ align-items: center;
136
+ justify-content: center;
137
+ transition: background-color 0.15s, border-color 0.15s;
138
+ }
139
+
140
+ .qty-btn:hover:not(:disabled) {
141
+ background-color: var(--tl-color-muted, #f3f4f6);
142
+ border-color: var(--tl-color-border-hover, #9ca3af);
143
+ }
144
+
145
+ .qty-btn:disabled {
146
+ cursor: not-allowed;
147
+ opacity: 0.5;
148
+ }
149
+
150
+ .qty-value {
151
+ width: 32px;
152
+ text-align: center;
153
+ font-weight: 600;
154
+ font-size: 16px;
155
+ color: var(--tl-color-foreground, #111827);
156
+ }
157
+
158
+ /* Success message */
159
+ .success-message {
160
+ display: flex;
161
+ align-items: center;
162
+ justify-content: center;
163
+ gap: var(--tl-spacing-2, 0.5rem);
164
+ padding: var(--tl-spacing-3, 0.75rem);
165
+ background-color: var(--tl-color-success, #10b981);
166
+ color: #ffffff;
167
+ border-radius: var(--tl-radius-md, 0.5rem);
168
+ margin-bottom: var(--tl-spacing-3, 0.75rem);
169
+ font-weight: 500;
170
+ font-size: 14px;
171
+ }
172
+
173
+ .success-icon {
174
+ width: 18px;
175
+ height: 18px;
176
+ }
177
+
178
+ /* Add to cart button */
179
+ .add-to-cart-btn {
180
+ width: 100%;
181
+ padding: var(--tl-spacing-3, 0.75rem) var(--tl-spacing-4, 1rem);
182
+ border-radius: var(--tl-radius-md, 0.5rem);
183
+ border: none;
184
+ background-color: var(--tl-color-primary, #6366f1);
185
+ color: var(--tl-color-primary-foreground, #ffffff);
186
+ font-weight: 600;
187
+ font-size: 16px;
188
+ cursor: pointer;
189
+ transition: opacity 0.15s, transform 0.1s;
190
+ }
191
+
192
+ .add-to-cart-btn:hover:not(:disabled) {
193
+ opacity: 0.9;
194
+ }
195
+
196
+ .add-to-cart-btn:active:not(:disabled) {
197
+ transform: scale(0.98);
198
+ }
199
+
200
+ .add-to-cart-btn.disabled,
201
+ .add-to-cart-btn:disabled {
202
+ background-color: var(--tl-color-muted, #d1d5db);
203
+ color: var(--tl-color-muted-foreground, #6b7280);
204
+ cursor: not-allowed;
205
+ }
@@ -0,0 +1,332 @@
1
+ import { h } from "@stencil/core";
2
+ import { getSDK, hasSDK, whenSDK } from "../../utils/sdk-connector";
3
+ export class TlTicketSelector {
4
+ constructor() {
5
+ /**
6
+ * Show add to cart button
7
+ */
8
+ this.showButton = true;
9
+ /**
10
+ * Button label
11
+ */
12
+ this.buttonLabel = 'Add to Cart';
13
+ this.ticketTypes = [];
14
+ this.quantities = {};
15
+ this.loading = true;
16
+ this.error = null;
17
+ this.adding = false;
18
+ this.addSuccess = false;
19
+ }
20
+ watchPropsChange() {
21
+ this.loadTicketTypes();
22
+ }
23
+ async componentWillLoad() {
24
+ await this.waitForSDKAndLoad();
25
+ }
26
+ /**
27
+ * Wait for SDK to be ready, then load ticket types
28
+ */
29
+ async waitForSDKAndLoad() {
30
+ if (!this.eventId || !this.occurrenceId) {
31
+ return;
32
+ }
33
+ try {
34
+ await whenSDK();
35
+ await this.loadTicketTypes();
36
+ }
37
+ catch (err) {
38
+ this.error = err instanceof Error ? err.message : 'SDK not initialized.';
39
+ this.loading = false;
40
+ }
41
+ }
42
+ async loadTicketTypes() {
43
+ try {
44
+ console.log('[tl-ticket-selector] Loading ticket types...', {
45
+ eventId: this.eventId,
46
+ occurrenceId: this.occurrenceId
47
+ });
48
+ this.loading = true;
49
+ this.error = null;
50
+ const sdk = getSDK();
51
+ const eventsManager = sdk.getEventsManager();
52
+ const types = await eventsManager.getTicketTypes(this.eventId, this.occurrenceId);
53
+ console.log('[tl-ticket-selector] Ticket types loaded:', types.length);
54
+ this.ticketTypes = types;
55
+ // Initialise quantities to 0
56
+ const initialQuantities = {};
57
+ types.forEach((t) => {
58
+ initialQuantities[t.id] = 0;
59
+ });
60
+ this.quantities = initialQuantities;
61
+ }
62
+ catch (err) {
63
+ console.error('[tl-ticket-selector] Failed to load ticket types:', err);
64
+ this.error = err instanceof Error ? err.message : 'Failed to load tickets';
65
+ this.tlError.emit({ message: this.error });
66
+ }
67
+ finally {
68
+ this.loading = false;
69
+ }
70
+ }
71
+ handleQuantityChange(ticketTypeId, delta) {
72
+ const ticketType = this.ticketTypes.find(t => t.id === ticketTypeId);
73
+ if (!ticketType)
74
+ return;
75
+ const currentQty = this.quantities[ticketTypeId] || 0;
76
+ const newQty = Math.max(0, Math.min(ticketType.maxPerOrder, currentQty + delta));
77
+ this.quantities = {
78
+ ...this.quantities,
79
+ [ticketTypeId]: newQty,
80
+ };
81
+ const total = this.calculateTotal();
82
+ this.tlQuantityChange.emit({ ticketTypeId, quantity: newQty, total });
83
+ }
84
+ calculateTotal() {
85
+ return this.ticketTypes.reduce((sum, tt) => {
86
+ return sum + (this.quantities[tt.id] || 0) * tt.price;
87
+ }, 0);
88
+ }
89
+ getTotalItems() {
90
+ return Object.values(this.quantities).reduce((sum, qty) => sum + qty, 0);
91
+ }
92
+ async handleAddToCart() {
93
+ const itemsToAdd = Object.entries(this.quantities)
94
+ .filter(([_, qty]) => qty > 0)
95
+ .map(([ticketTypeId, quantity]) => ({ ticketTypeId, quantity }));
96
+ if (itemsToAdd.length === 0)
97
+ return;
98
+ this.adding = true;
99
+ this.addSuccess = false;
100
+ try {
101
+ if (hasSDK()) {
102
+ const sdk = getSDK();
103
+ const cartManager = sdk.getCartManager?.();
104
+ if (cartManager) {
105
+ for (const item of itemsToAdd) {
106
+ await cartManager.addItem(item.ticketTypeId, item.quantity, this.occurrenceId);
107
+ }
108
+ console.log('[tl-ticket-selector] Items added to cart');
109
+ }
110
+ }
111
+ // Reset quantities after adding
112
+ const resetQuantities = {};
113
+ this.ticketTypes.forEach(t => {
114
+ resetQuantities[t.id] = 0;
115
+ });
116
+ this.quantities = resetQuantities;
117
+ this.addSuccess = true;
118
+ // Clear success message after 3 seconds
119
+ setTimeout(() => {
120
+ this.addSuccess = false;
121
+ }, 3000);
122
+ this.tlAddToCart.emit({ items: itemsToAdd });
123
+ }
124
+ catch (err) {
125
+ console.error('[tl-ticket-selector] Failed to add to cart:', err);
126
+ this.error = err instanceof Error ? err.message : 'Failed to add to cart';
127
+ this.tlError.emit({ message: this.error });
128
+ }
129
+ finally {
130
+ this.adding = false;
131
+ }
132
+ }
133
+ formatPrice(priceInPence, currency = 'GBP') {
134
+ return new Intl.NumberFormat('en-GB', {
135
+ style: 'currency',
136
+ currency,
137
+ }).format(priceInPence / 100);
138
+ }
139
+ renderSkeleton() {
140
+ return (h("div", { class: "skeleton-container" }, h("div", { class: "skeleton-row" }), h("div", { class: "skeleton-row" }), h("div", { class: "skeleton-row" })));
141
+ }
142
+ renderError() {
143
+ return (h("div", { class: "error-state" }, h("p", { class: "error-message" }, this.error)));
144
+ }
145
+ renderTicketRow(tt) {
146
+ const isSoldOut = tt.availabilityStatus === 'sold_out';
147
+ const qty = this.quantities[tt.id] || 0;
148
+ return (h("div", { class: `ticket-row ${isSoldOut ? 'sold-out' : ''}`, key: tt.id }, h("div", { class: "ticket-info" }, h("div", { class: "ticket-header" }, h("span", { class: "ticket-name" }, tt.name), tt.availabilityStatus === 'limited' && (h("span", { class: "status-badge limited" }, "Limited")), isSoldOut && (h("span", { class: "status-badge sold-out" }, "Sold Out"))), tt.description && (h("p", { class: "ticket-description" }, tt.description)), h("p", { class: "ticket-price" }, this.formatPrice(tt.price, tt.currency))), h("div", { class: "quantity-controls" }, h("button", { class: "qty-btn", onClick: () => this.handleQuantityChange(tt.id, -1), disabled: isSoldOut || qty === 0, "aria-label": "Decrease quantity" }, "\u2212"), h("span", { class: "qty-value" }, qty), h("button", { class: "qty-btn", onClick: () => this.handleQuantityChange(tt.id, 1), disabled: isSoldOut || qty >= tt.maxPerOrder, "aria-label": "Increase quantity" }, "+"))));
149
+ }
150
+ render() {
151
+ if (this.loading) {
152
+ return this.renderSkeleton();
153
+ }
154
+ if (this.error) {
155
+ return this.renderError();
156
+ }
157
+ const totalItems = this.getTotalItems();
158
+ const totalPrice = this.calculateTotal();
159
+ return (h("div", { class: "ticket-selector" }, h("div", { class: "ticket-list" }, this.ticketTypes.map(tt => this.renderTicketRow(tt))), this.addSuccess && (h("div", { class: "success-message" }, h("svg", { class: "success-icon", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor" }, h("path", { "stroke-linecap": "round", "stroke-linejoin": "round", "stroke-width": "2", d: "M5 13l4 4L19 7" })), "Added to cart!")), this.showButton && (h("button", { class: `add-to-cart-btn ${totalItems === 0 ? 'disabled' : ''}`, onClick: () => this.handleAddToCart(), disabled: totalItems === 0 || this.adding }, this.adding
160
+ ? 'Adding...'
161
+ : totalItems === 0
162
+ ? 'Select Tickets'
163
+ : `${this.buttonLabel} - ${this.formatPrice(totalPrice)}`))));
164
+ }
165
+ static get is() { return "tl-ticket-selector"; }
166
+ static get encapsulation() { return "shadow"; }
167
+ static get originalStyleUrls() {
168
+ return {
169
+ "$": ["tl-ticket-selector.css"]
170
+ };
171
+ }
172
+ static get styleUrls() {
173
+ return {
174
+ "$": ["tl-ticket-selector.css"]
175
+ };
176
+ }
177
+ static get properties() {
178
+ return {
179
+ "eventId": {
180
+ "type": "string",
181
+ "mutable": false,
182
+ "complexType": {
183
+ "original": "string",
184
+ "resolved": "string",
185
+ "references": {}
186
+ },
187
+ "required": true,
188
+ "optional": false,
189
+ "docs": {
190
+ "tags": [],
191
+ "text": "Event ID"
192
+ },
193
+ "getter": false,
194
+ "setter": false,
195
+ "reflect": false,
196
+ "attribute": "event-id"
197
+ },
198
+ "occurrenceId": {
199
+ "type": "string",
200
+ "mutable": false,
201
+ "complexType": {
202
+ "original": "string",
203
+ "resolved": "string",
204
+ "references": {}
205
+ },
206
+ "required": true,
207
+ "optional": false,
208
+ "docs": {
209
+ "tags": [],
210
+ "text": "Occurrence ID"
211
+ },
212
+ "getter": false,
213
+ "setter": false,
214
+ "reflect": false,
215
+ "attribute": "occurrence-id"
216
+ },
217
+ "showButton": {
218
+ "type": "boolean",
219
+ "mutable": false,
220
+ "complexType": {
221
+ "original": "boolean",
222
+ "resolved": "boolean",
223
+ "references": {}
224
+ },
225
+ "required": false,
226
+ "optional": false,
227
+ "docs": {
228
+ "tags": [],
229
+ "text": "Show add to cart button"
230
+ },
231
+ "getter": false,
232
+ "setter": false,
233
+ "reflect": false,
234
+ "attribute": "show-button",
235
+ "defaultValue": "true"
236
+ },
237
+ "buttonLabel": {
238
+ "type": "string",
239
+ "mutable": false,
240
+ "complexType": {
241
+ "original": "string",
242
+ "resolved": "string",
243
+ "references": {}
244
+ },
245
+ "required": false,
246
+ "optional": false,
247
+ "docs": {
248
+ "tags": [],
249
+ "text": "Button label"
250
+ },
251
+ "getter": false,
252
+ "setter": false,
253
+ "reflect": false,
254
+ "attribute": "button-label",
255
+ "defaultValue": "'Add to Cart'"
256
+ }
257
+ };
258
+ }
259
+ static get states() {
260
+ return {
261
+ "ticketTypes": {},
262
+ "quantities": {},
263
+ "loading": {},
264
+ "error": {},
265
+ "adding": {},
266
+ "addSuccess": {}
267
+ };
268
+ }
269
+ static get events() {
270
+ return [{
271
+ "method": "tlAddToCart",
272
+ "name": "tlAddToCart",
273
+ "bubbles": true,
274
+ "cancelable": true,
275
+ "composed": true,
276
+ "docs": {
277
+ "tags": [],
278
+ "text": "Emitted when items are added to cart"
279
+ },
280
+ "complexType": {
281
+ "original": "{ items: AddToCartItem[] }",
282
+ "resolved": "{ items: AddToCartItem[]; }",
283
+ "references": {
284
+ "AddToCartItem": {
285
+ "location": "local",
286
+ "path": "/home/runner/work/live-elements/live-elements/src/components/tl-ticket-selector/tl-ticket-selector.tsx",
287
+ "id": "src/components/tl-ticket-selector/tl-ticket-selector.tsx::AddToCartItem"
288
+ }
289
+ }
290
+ }
291
+ }, {
292
+ "method": "tlQuantityChange",
293
+ "name": "tlQuantityChange",
294
+ "bubbles": true,
295
+ "cancelable": true,
296
+ "composed": true,
297
+ "docs": {
298
+ "tags": [],
299
+ "text": "Emitted when quantity changes"
300
+ },
301
+ "complexType": {
302
+ "original": "{ ticketTypeId: string; quantity: number; total: number }",
303
+ "resolved": "{ ticketTypeId: string; quantity: number; total: number; }",
304
+ "references": {}
305
+ }
306
+ }, {
307
+ "method": "tlError",
308
+ "name": "tlError",
309
+ "bubbles": true,
310
+ "cancelable": true,
311
+ "composed": true,
312
+ "docs": {
313
+ "tags": [],
314
+ "text": "Emitted on error"
315
+ },
316
+ "complexType": {
317
+ "original": "{ message: string }",
318
+ "resolved": "{ message: string; }",
319
+ "references": {}
320
+ }
321
+ }];
322
+ }
323
+ static get watchers() {
324
+ return [{
325
+ "propName": "eventId",
326
+ "methodName": "watchPropsChange"
327
+ }, {
328
+ "propName": "occurrenceId",
329
+ "methodName": "watchPropsChange"
330
+ }];
331
+ }
332
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Ticketlayer Elements
3
+ *
4
+ * Web Components for Ticketlayer ticket purchasing.
5
+ */
6
+ // SDK Connector
7
+ export { setSDK, getSDK, hasSDK, whenSDK, onSDKEvent } from './utils/sdk-connector';
8
+ // Wrapper component types (Stencil will auto-export the components)
9
+ export { TlBuyTicketsWrapper } from './components/tl-buy-tickets-wrapper/tl-buy-tickets-wrapper';
10
+ export { TlCheckoutWrapper } from './components/tl-checkout-wrapper/tl-checkout-wrapper';
@@ -0,0 +1,101 @@
1
+ /** Money arrives as minor-unit integers or decimal strings ("50.00"). */
2
+ export function formatPrice(value, currency = 'GBP') {
3
+ if (value === null || value === undefined || value === '')
4
+ return '';
5
+ const fmt = new Intl.NumberFormat('en-GB', { style: 'currency', currency });
6
+ if (typeof value === 'number')
7
+ return fmt.format(value / 100);
8
+ const n = Number(value);
9
+ return Number.isNaN(n) ? String(value) : fmt.format(n);
10
+ }
11
+ export function formatDateTime(iso) {
12
+ return new Date(iso).toLocaleDateString('en-GB', {
13
+ weekday: 'short',
14
+ day: 'numeric',
15
+ month: 'short',
16
+ year: 'numeric',
17
+ hour: '2-digit',
18
+ minute: '2-digit',
19
+ });
20
+ }
21
+ export function formatDate(iso) {
22
+ return new Date(iso).toLocaleDateString('en-GB', {
23
+ day: 'numeric',
24
+ month: 'short',
25
+ year: 'numeric',
26
+ });
27
+ }
28
+ /** Line items that are tickets (fees, taxes and discounts have their own type). */
29
+ export function ticketItems(order) {
30
+ return (order.items ?? []).filter((i) => !i.type || i.type === 'ticket');
31
+ }
32
+ export function ticketCount(order) {
33
+ const fromItems = ticketItems(order).reduce((sum, i) => sum + (Number(i.quantity) || 0), 0);
34
+ return fromItems || order.tickets?.length || 0;
35
+ }
36
+ /** Event name as the order view exposes it: the first ticket line's name. */
37
+ export function orderEventName(order) {
38
+ return order.eventName || ticketItems(order)[0]?.name || order.items?.[0]?.name || null;
39
+ }
40
+ export function orderCustomerEmail(order, customer) {
41
+ const fromOrder = order?.customerEmail || order?.customer?.email;
42
+ if (fromOrder)
43
+ return fromOrder;
44
+ const c = customer;
45
+ return c?.email || null;
46
+ }
47
+ export function statusLabel(status) {
48
+ if (!status)
49
+ return '';
50
+ return status.replace(/_/g, ' ').replace(/^\w/, (m) => m.toUpperCase());
51
+ }
52
+ export function statusTone(status) {
53
+ switch (status) {
54
+ case 'confirmed':
55
+ return 'success';
56
+ case 'completed':
57
+ case 'fulfilled':
58
+ return 'info';
59
+ case 'pending':
60
+ case 'processing':
61
+ return 'warning';
62
+ case 'cancelled':
63
+ case 'expired':
64
+ case 'failed':
65
+ return 'error';
66
+ default:
67
+ return 'muted';
68
+ }
69
+ }
70
+ export function errorMessage(err, fallback) {
71
+ return err instanceof Error && err.message ? err.message : fallback;
72
+ }
73
+ /** True for the "not signed in" family of API errors (401/403, NO_SESSION, NOT_AUTHENTICATED). */
74
+ export function isAuthError(err) {
75
+ const e = err;
76
+ if (!e || typeof e !== 'object')
77
+ return false;
78
+ return e.status === 401 || e.status === 403 || e.code === 'NO_SESSION' || e.code === 'NOT_AUTHENTICATED';
79
+ }
80
+ /**
81
+ * Find the event and start time for an occurrence id. The order view only
82
+ * carries the occurrence id, so this walks the channel's listings (cached by
83
+ * live-api) until one owns the occurrence. Best effort: null when not found.
84
+ */
85
+ export async function resolveOccurrence(sdk, occurrenceId, maxEvents = 12) {
86
+ const events = sdk.getEventsManager();
87
+ const listing = await events.list({ limit: maxEvents });
88
+ const candidates = listing.items.slice(0, maxEvents);
89
+ for (const candidate of candidates) {
90
+ const detail = (await events.get(candidate.id).catch(() => null));
91
+ const occ = detail?.occurrences?.find((o) => o.id === occurrenceId);
92
+ if (occ) {
93
+ return {
94
+ eventId: detail?.id ?? candidate.id,
95
+ eventName: detail?.name ?? candidate.name ?? '',
96
+ startsAt: occ.startsAt ?? null,
97
+ };
98
+ }
99
+ }
100
+ return null;
101
+ }