@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.
- package/dist/cjs/{index-I9CoYv3k.js → index-DanPUX0s.js} +1 -1
- package/dist/cjs/index.cjs.js +2 -1
- package/dist/cjs/loader.cjs.js +2 -2
- package/dist/cjs/ticket-selector-BYVL13k_.js +3623 -0
- package/dist/cjs/ticketlayer-elements.cjs.js +2 -2
- package/dist/cjs/tl-buy-modal.cjs.entry.js +3 -3
- package/dist/cjs/tl-buy-tickets-button.cjs.entry.js +89 -74
- package/dist/cjs/tl-buy-tickets-wrapper.cjs.entry.js +61 -30
- package/dist/cjs/tl-cart-badge.cjs.entry.js +74 -61
- package/dist/cjs/tl-cart-drawer.cjs.entry.js +51 -23
- package/dist/cjs/tl-cart.cjs.entry.js +71 -103
- package/dist/cjs/tl-checkout-wrapper.cjs.entry.js +51 -29
- package/dist/cjs/tl-event-card.cjs.entry.js +43 -105
- package/dist/cjs/tl-event-list.cjs.entry.js +72 -35
- package/dist/cjs/tl-login.cjs.entry.js +74 -141
- package/dist/cjs/tl-my-orders.cjs.entry.js +52 -92
- package/dist/cjs/tl-occurrence-selector_2.cjs.entry.js +153 -921
- package/dist/cjs/tl-order-confirmation.cjs.entry.js +71 -107
- package/dist/cjs/tl-order-tickets_2.cjs.entry.js +48 -74
- package/dist/cjs/tl-promo-code.cjs.entry.js +50 -47
- package/dist/cjs/tl-seat-map.cjs.entry.js +3 -3
- package/dist/cjs/web-query-Ce73jB4k.js +16 -0
- package/dist/collection/components/tl-buy-modal/tl-buy-modal.js +2 -2
- package/dist/collection/components/tl-buy-tickets-button/tl-buy-tickets-button.css +37 -0
- package/dist/collection/components/tl-buy-tickets-button/tl-buy-tickets-button.js +105 -79
- package/dist/collection/components/tl-buy-tickets-wrapper/tl-buy-tickets-wrapper.js +73 -30
- package/dist/collection/components/tl-cart/tl-cart.js +82 -107
- package/dist/collection/components/tl-cart-badge/tl-cart-badge.js +62 -62
- package/dist/collection/components/tl-cart-drawer/tl-cart-drawer.js +56 -24
- package/dist/collection/components/tl-checkout-wrapper/tl-checkout-wrapper.js +61 -29
- package/dist/collection/components/tl-event-card/event-card-adapter.js +11 -23
- package/dist/collection/components/tl-event-card/tl-event-card.js +51 -84
- package/dist/collection/components/tl-event-list/tl-event-list.js +82 -38
- package/dist/collection/components/tl-login/tl-login.js +83 -147
- package/dist/collection/components/tl-my-orders/tl-my-orders.js +58 -96
- package/dist/collection/components/tl-occurrence-selector/tl-occurrence-selector.js +137 -516
- package/dist/collection/components/tl-order-confirmation/tl-order-confirmation.js +81 -115
- package/dist/collection/components/tl-order-tickets/tl-order-tickets.js +57 -92
- package/dist/collection/components/tl-promo-code/tl-promo-code.js +55 -50
- package/dist/collection/components/tl-seat-map/tl-seat-map.js +2 -2
- package/dist/collection/components/tl-ticket-selector/tl-ticket-selector.js +34 -20
- package/dist/collection/utils/order-summary.js +15 -135
- package/dist/collection/utils/testing/deep-text.js +29 -0
- package/dist/collection/utils/testing/fixtures.js +290 -0
- package/dist/collection/utils/testing/mock-sdk.js +13 -2
- package/dist/collection/utils/web-query.js +12 -0
- package/dist/collection/utils/web-storage.js +22 -0
- package/dist/components/ticket-selector.js +1 -0
- package/dist/components/tl-buy-modal.js +1 -1
- package/dist/components/tl-buy-tickets-button.js +1 -1
- package/dist/components/tl-buy-tickets-wrapper2.js +1 -1
- package/dist/components/tl-cart-badge.js +1 -1
- package/dist/components/tl-cart-drawer.js +1 -1
- package/dist/components/tl-cart2.js +1 -1
- package/dist/components/tl-checkout-wrapper2.js +1 -1
- package/dist/components/tl-event-card2.js +1 -1
- package/dist/components/tl-event-list.js +1 -1
- package/dist/components/tl-login2.js +1 -1
- package/dist/components/tl-my-orders.js +1 -1
- package/dist/components/tl-occurrence-selector2.js +1 -1
- package/dist/components/tl-order-confirmation.js +1 -1
- package/dist/components/tl-order-tickets2.js +1 -1
- package/dist/components/tl-promo-code.js +1 -1
- package/dist/components/tl-seat-map.js +1 -1
- package/dist/components/tl-ticket-selector2.js +1 -1
- package/dist/components/web-query.js +1 -0
- package/dist/elements.docs.json +34 -34
- package/dist/esm/{index-OoYV1faY.js → index-imfxp765.js} +1 -1
- package/dist/esm/index.js +2 -1
- package/dist/esm/loader.js +3 -3
- package/dist/esm/ticket-selector-BwChnHVC.js +3606 -0
- package/dist/esm/ticketlayer-elements.js +3 -3
- package/dist/esm/tl-buy-modal.entry.js +3 -3
- package/dist/esm/tl-buy-tickets-button.entry.js +90 -75
- package/dist/esm/tl-buy-tickets-wrapper.entry.js +62 -31
- package/dist/esm/tl-cart-badge.entry.js +75 -62
- package/dist/esm/tl-cart-drawer.entry.js +52 -24
- package/dist/esm/tl-cart.entry.js +72 -104
- package/dist/esm/tl-checkout-wrapper.entry.js +52 -30
- package/dist/esm/tl-event-card.entry.js +44 -106
- package/dist/esm/tl-event-list.entry.js +73 -36
- package/dist/esm/tl-login.entry.js +75 -142
- package/dist/esm/tl-my-orders.entry.js +53 -93
- package/dist/esm/tl-occurrence-selector_2.entry.js +152 -920
- package/dist/esm/tl-order-confirmation.entry.js +72 -108
- package/dist/esm/tl-order-tickets_2.entry.js +49 -75
- package/dist/esm/tl-promo-code.entry.js +51 -48
- package/dist/esm/tl-seat-map.entry.js +3 -3
- package/dist/esm/web-query-Bdbb2lk5.js +14 -0
- package/dist/ticketlayer-elements/index.esm.js +1 -1
- package/dist/ticketlayer-elements/p-45923d2c.entry.js +1 -0
- package/dist/ticketlayer-elements/p-492adc77.entry.js +1 -0
- package/dist/ticketlayer-elements/p-4cfc9e53.entry.js +1 -0
- package/dist/ticketlayer-elements/{p-ac264adc.entry.js → p-5135630e.entry.js} +1 -1
- package/dist/ticketlayer-elements/p-5bbce3b5.entry.js +1 -0
- package/dist/ticketlayer-elements/p-7d547721.entry.js +1 -0
- package/dist/ticketlayer-elements/p-7fe7b6ba.entry.js +1 -0
- package/dist/ticketlayer-elements/p-8122f0c1.entry.js +1 -0
- package/dist/ticketlayer-elements/p-98d40869.entry.js +1 -0
- package/dist/ticketlayer-elements/p-Bdbb2lk5.js +1 -0
- package/dist/ticketlayer-elements/p-BwChnHVC.js +1 -0
- package/dist/ticketlayer-elements/p-abec290a.entry.js +1 -0
- package/dist/ticketlayer-elements/p-ac79a3f6.entry.js +1 -0
- package/dist/ticketlayer-elements/p-d01f4429.entry.js +1 -0
- package/dist/ticketlayer-elements/p-d7ffaf46.entry.js +1 -0
- package/dist/ticketlayer-elements/p-e7170f3c.entry.js +1 -0
- package/dist/ticketlayer-elements/p-e787e862.entry.js +1 -0
- package/dist/ticketlayer-elements/p-f1ff931f.entry.js +1 -0
- package/dist/ticketlayer-elements/p-imfxp765.js +3 -0
- package/dist/ticketlayer-elements/ticketlayer-elements.css +1 -1
- package/dist/ticketlayer-elements/ticketlayer-elements.esm.js +1 -1
- package/dist/types/components/tl-buy-tickets-button/tl-buy-tickets-button.d.ts +40 -8
- package/dist/types/components/tl-buy-tickets-wrapper/tl-buy-tickets-wrapper.d.ts +20 -1
- package/dist/types/components/tl-cart/tl-cart.d.ts +40 -42
- package/dist/types/components/tl-cart-badge/tl-cart-badge.d.ts +22 -7
- package/dist/types/components/tl-cart-drawer/tl-cart-drawer.d.ts +28 -5
- package/dist/types/components/tl-checkout-wrapper/tl-checkout-wrapper.d.ts +18 -1
- package/dist/types/components/tl-event-card/event-card-adapter.d.ts +9 -20
- package/dist/types/components/tl-event-card/tl-event-card.d.ts +25 -28
- package/dist/types/components/tl-event-list/tl-event-list.d.ts +46 -6
- package/dist/types/components/tl-login/tl-login.d.ts +68 -22
- package/dist/types/components/tl-my-orders/tl-my-orders.d.ts +30 -12
- package/dist/types/components/tl-occurrence-selector/tl-occurrence-selector.d.ts +73 -161
- package/dist/types/components/tl-order-confirmation/tl-order-confirmation.d.ts +48 -15
- package/dist/types/components/tl-order-tickets/tl-order-tickets.d.ts +29 -31
- package/dist/types/components/tl-promo-code/tl-promo-code.d.ts +28 -10
- package/dist/types/components/tl-ticket/tl-ticket.d.ts +2 -11
- package/dist/types/components/tl-ticket-selector/tl-ticket-selector.d.ts +22 -4
- package/dist/types/components.d.ts +270 -62
- package/dist/types/utils/order-summary.d.ts +14 -97
- package/dist/types/utils/testing/deep-text.d.ts +13 -0
- package/dist/types/utils/testing/fixtures.d.ts +225 -0
- package/dist/types/utils/testing/mock-sdk.d.ts +14 -1
- package/dist/types/utils/web-query.d.ts +18 -0
- package/dist/types/utils/web-storage.d.ts +16 -0
- package/elements.catalogue.json +602 -50
- package/package.json +8 -6
- package/dist/cjs/money-BtmM2gt-.js +0 -118
- package/dist/cjs/order-summary-X3TlZxc2.js +0 -150
- package/dist/components/money.js +0 -1
- package/dist/components/order-summary.js +0 -1
- package/dist/esm/money-4ig0-WB6.js +0 -112
- package/dist/esm/order-summary-DiDtvfZM.js +0 -136
- package/dist/ticketlayer-elements/p-0814420f.entry.js +0 -1
- package/dist/ticketlayer-elements/p-0acabc26.entry.js +0 -1
- package/dist/ticketlayer-elements/p-109a3feb.entry.js +0 -1
- package/dist/ticketlayer-elements/p-1a4ba71c.entry.js +0 -1
- package/dist/ticketlayer-elements/p-318965c1.entry.js +0 -1
- package/dist/ticketlayer-elements/p-347c7fd1.entry.js +0 -1
- package/dist/ticketlayer-elements/p-3649c26d.entry.js +0 -1
- package/dist/ticketlayer-elements/p-40260cc9.entry.js +0 -1
- package/dist/ticketlayer-elements/p-4ig0-WB6.js +0 -1
- package/dist/ticketlayer-elements/p-55fd2f9f.entry.js +0 -1
- package/dist/ticketlayer-elements/p-62ee659b.entry.js +0 -1
- package/dist/ticketlayer-elements/p-7d4ecc2b.entry.js +0 -1
- package/dist/ticketlayer-elements/p-7f0abf95.entry.js +0 -1
- package/dist/ticketlayer-elements/p-BXpVGW51.js +0 -1
- package/dist/ticketlayer-elements/p-OoYV1faY.js +0 -3
- package/dist/ticketlayer-elements/p-a3c1b26c.entry.js +0 -1
- package/dist/ticketlayer-elements/p-aeb4c6fb.entry.js +0 -1
- package/dist/ticketlayer-elements/p-ba62541f.entry.js +0 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { h } from "@stencil/core";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { createElement, orderConfirmationCore, } from "@ticketlayer/elements-core";
|
|
3
|
+
import { getSDK, whenSDK } from "../../utils/sdk-connector";
|
|
4
|
+
import { webQuery } from "../../utils/web-query";
|
|
4
5
|
/**
|
|
5
6
|
* tl-order-confirmation - the "thank you" page block: order reference,
|
|
6
7
|
* customer email, event and date, ticket lines, totals, then the tickets
|
|
@@ -13,6 +14,13 @@ import { errorMessage, formatDateTime, formatPrice, orderCustomerEmail, orderEve
|
|
|
13
14
|
* 3. the SDK's `checkout:completed` event, for a page that mounts this before
|
|
14
15
|
* the customer pays
|
|
15
16
|
*
|
|
17
|
+
* The web harness for `orderConfirmationCore`: it renders and it forwards. All
|
|
18
|
+
* three of those routes, the money rows, the event lookup and every string are
|
|
19
|
+
* in `@ticketlayer/elements-core` (AGENTS.md, TKT-11). What is left here is the
|
|
20
|
+
* Stencil surface and the two genuinely web things: the client the page
|
|
21
|
+
* published on `window.Ticketlayer`, and `window.location.search`, which is
|
|
22
|
+
* where this runtime keeps the parameters the core asks for.
|
|
23
|
+
*
|
|
16
24
|
* Usage: <tl-order-confirmation order-id="ord_xxx"></tl-order-confirmation>
|
|
17
25
|
*
|
|
18
26
|
* @event tlOrderLoaded - the order was loaded
|
|
@@ -24,127 +32,82 @@ export class TlOrderConfirmation {
|
|
|
24
32
|
this.showTickets = true;
|
|
25
33
|
/** Columns for the ticket grid. */
|
|
26
34
|
this.columns = 2;
|
|
27
|
-
this.
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
35
|
+
this.element = createElement(orderConfirmationCore, {
|
|
36
|
+
client: () => getSDK(),
|
|
37
|
+
clientReady: () => whenSDK(),
|
|
38
|
+
query: webQuery,
|
|
39
|
+
emit: (event, detail) => this.forward(event, detail),
|
|
40
|
+
});
|
|
41
|
+
/** The core's whole view. Assigning it is what makes Stencil re-render. */
|
|
42
|
+
this.view = this.element.view;
|
|
34
43
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
44
|
+
/**
|
|
45
|
+
* A new order id is a new subject, and the core resolves it again the way
|
|
46
|
+
* the mount did - the prop first, the link second. Not a remount: the
|
|
47
|
+
* `checkout:completed` subscription belongs to the element rather than to
|
|
48
|
+
* the order it is showing.
|
|
49
|
+
*/
|
|
50
|
+
watchOrderId() {
|
|
51
|
+
this.syncProps();
|
|
52
|
+
this.element.actions.orderIdChanged();
|
|
38
53
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
if (this.resolvedOrderId && !this.paymentFailed) {
|
|
46
|
-
await this.load();
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
componentDidLoad() {
|
|
50
|
-
// A page that mounts before checkout: pick the order up when the SDK completes it.
|
|
51
|
-
this.unsubscribe = onSDKEvent('checkout:completed', (payload) => {
|
|
52
|
-
const order = payload;
|
|
53
|
-
if (!order?.id)
|
|
54
|
-
return;
|
|
55
|
-
this.paymentFailed = false;
|
|
56
|
-
this.order = order;
|
|
57
|
-
this.resolvedOrderId = order.id;
|
|
58
|
-
this.tlOrderLoaded.emit({ order });
|
|
59
|
-
// Re-read for tickets, which are issued after the order is created.
|
|
60
|
-
this.load();
|
|
54
|
+
syncProps() {
|
|
55
|
+
this.element.setProps({
|
|
56
|
+
orderId: this.orderId,
|
|
57
|
+
customerEmail: this.customerEmail,
|
|
58
|
+
showTickets: this.showTickets,
|
|
59
|
+
columns: this.columns,
|
|
61
60
|
});
|
|
62
61
|
}
|
|
62
|
+
connectedCallback() {
|
|
63
|
+
this.syncProps();
|
|
64
|
+
this.unsubscribe ?? (this.unsubscribe = this.element.subscribe((view) => {
|
|
65
|
+
this.view = view;
|
|
66
|
+
}));
|
|
67
|
+
this.view = this.element.view;
|
|
68
|
+
this.teardown = this.element.mount();
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Off screen: run the mount's teardown, which drops the
|
|
72
|
+
* `checkout:completed` subscription. Not `destroy()`: Stencil calls this for
|
|
73
|
+
* a DOM move as well as a removal, and a destroyed core never mounts again.
|
|
74
|
+
*/
|
|
63
75
|
disconnectedCallback() {
|
|
76
|
+
this.teardown?.();
|
|
77
|
+
this.teardown = undefined;
|
|
64
78
|
this.unsubscribe?.();
|
|
79
|
+
this.unsubscribe = undefined;
|
|
65
80
|
}
|
|
66
|
-
|
|
67
|
-
if (
|
|
68
|
-
|
|
69
|
-
|
|
81
|
+
forward(event, detail) {
|
|
82
|
+
if (event === 'tlOrderLoaded')
|
|
83
|
+
this.tlOrderLoaded.emit(detail);
|
|
84
|
+
else if (event === 'tlError')
|
|
85
|
+
this.tlError.emit(detail);
|
|
70
86
|
}
|
|
71
|
-
|
|
72
|
-
return this.
|
|
87
|
+
renderRows() {
|
|
88
|
+
return (h("dl", { class: "rows" }, this.view.derived.rows.map((row) => (h("div", { class: "row", key: row.label }, h("dt", null, row.label), h("dd", { class: row.reference ? 'reference' : undefined }, row.value))))));
|
|
73
89
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
const sdk = await whenSDK();
|
|
82
|
-
const orders = sdk.getOrdersManager?.();
|
|
83
|
-
if (!orders)
|
|
84
|
-
throw new Error('Orders are not available');
|
|
85
|
-
const order = (await orders.get(id));
|
|
86
|
-
if (!order)
|
|
87
|
-
throw new Error('Order not found');
|
|
88
|
-
this.order = order;
|
|
89
|
-
this.signedInCustomer = sdk.getAuthManager?.()?.customer ?? null;
|
|
90
|
-
this.tlOrderLoaded.emit({ order });
|
|
91
|
-
this.resolveEvent(sdk, order);
|
|
92
|
-
}
|
|
93
|
-
catch (err) {
|
|
94
|
-
console.error('[tl-order-confirmation] Failed to load order:', err);
|
|
95
|
-
this.error = errorMessage(err, 'Failed to load your order');
|
|
96
|
-
this.tlError.emit({ message: this.error });
|
|
97
|
-
}
|
|
98
|
-
finally {
|
|
99
|
-
this.loading = false;
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
// The order view has no event name or date; look them up from the first
|
|
103
|
-
// ticket's occurrence. Non-blocking and best effort.
|
|
104
|
-
async resolveEvent(sdk, order) {
|
|
105
|
-
const occurrenceId = order.tickets?.find((t) => t.eventOccurrenceId)?.eventOccurrenceId;
|
|
106
|
-
if (!occurrenceId)
|
|
107
|
-
return;
|
|
108
|
-
try {
|
|
109
|
-
const resolved = await resolveOccurrence(sdk, occurrenceId);
|
|
110
|
-
if (resolved && this.order?.id === order.id)
|
|
111
|
-
this.occurrence = resolved;
|
|
112
|
-
}
|
|
113
|
-
catch {
|
|
114
|
-
// Leave the event line at what the order gives us.
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
renderRows(order) {
|
|
118
|
-
const email = this.customerEmail || orderCustomerEmail(order, this.signedInCustomer);
|
|
119
|
-
const eventName = this.occurrence?.eventName || orderEventName(order);
|
|
120
|
-
const startsAt = this.occurrence?.startsAt;
|
|
121
|
-
return (h("dl", { class: "rows" }, h("div", { class: "row" }, h("dt", null, "Order reference"), h("dd", { class: "reference" }, order.orderNumber)), email && (h("div", { class: "row" }, h("dt", null, "Email"), h("dd", null, email))), eventName && (h("div", { class: "row" }, h("dt", null, "Event"), h("dd", null, eventName))), startsAt && (h("div", { class: "row" }, h("dt", null, "Date"), h("dd", null, formatDateTime(startsAt))))));
|
|
122
|
-
}
|
|
123
|
-
// Money comes from orderTotals: the booking fee is its own row named after
|
|
124
|
-
// the channel's fee profile, the subtotal is the ticket lines alone (the
|
|
125
|
-
// order's own `subtotal` folds the fee line into it) and the total is the
|
|
126
|
-
// order's figure, so what is shown is what was charged.
|
|
127
|
-
renderTotals(order) {
|
|
128
|
-
const totals = orderTotals(order);
|
|
129
|
-
const c = totals.currency;
|
|
130
|
-
return (h("div", { class: "totals" }, h("div", { class: "total-row" }, h("span", null, "Subtotal"), h("span", null, formatPrice(totals.subtotal, c))), totals.showFee && (h("div", { class: "total-row fee" }, h("span", null, totals.feeLabel), h("span", null, formatPrice(totals.fees, c)))), totals.tax > 0 && (h("div", { class: "total-row" }, h("span", null, "Tax"), h("span", null, formatPrice(totals.tax, c)))), totals.discount > 0 && (h("div", { class: "total-row discount" }, h("span", null, "Discount"), h("span", null, "-", formatPrice(totals.discount, c)))), h("div", { class: "total-row total" }, h("span", null, "Total"), h("span", null, formatPrice(totals.total, c)))));
|
|
90
|
+
// Money comes from the core's orderTotals: the booking fee is its own row
|
|
91
|
+
// named after the channel's fee profile, the subtotal is the ticket lines
|
|
92
|
+
// alone (the order's own `subtotal` folds the fee line into it) and the
|
|
93
|
+
// total is the order's figure, so what is shown is what was charged.
|
|
94
|
+
renderTotals() {
|
|
95
|
+
const { strings, derived } = this.view;
|
|
96
|
+
return (h("div", { class: "totals" }, h("div", { class: "total-row" }, h("span", null, strings.subtotalRow), h("span", null, derived.subtotalLabel)), derived.totals.showFee && (h("div", { class: "total-row fee" }, h("span", null, derived.totals.feeLabel), h("span", null, derived.feeAmountLabel))), derived.showTaxRow && (h("div", { class: "total-row" }, h("span", null, strings.taxRow), h("span", null, derived.taxLabel))), derived.showDiscountRow && (h("div", { class: "total-row discount" }, h("span", null, strings.discountRow), h("span", null, "-", derived.discountLabel))), h("div", { class: "total-row total" }, h("span", null, strings.totalRow), h("span", null, derived.totalLabel))));
|
|
131
97
|
}
|
|
132
98
|
render() {
|
|
133
|
-
|
|
134
|
-
|
|
99
|
+
const { status, state, strings, derived } = this.view;
|
|
100
|
+
if (status === 'payment-failed') {
|
|
101
|
+
return (h("div", { class: "confirmation" }, h("h1", { class: "title" }, strings.paymentFailedTitle), h("p", { class: "lead" }, strings.paymentFailedLead)));
|
|
135
102
|
}
|
|
136
103
|
// Nothing to show yet: no order in props, URL or from checkout.
|
|
137
|
-
if (
|
|
104
|
+
if (status === 'idle') {
|
|
138
105
|
return null;
|
|
139
106
|
}
|
|
140
|
-
if (
|
|
107
|
+
if (status === 'loading') {
|
|
141
108
|
return (h("div", { class: "confirmation" }, h("div", { class: "skeleton-line short" }), h("div", { class: "skeleton-line long" }), h("div", { class: "skeleton-block" })));
|
|
142
109
|
}
|
|
143
|
-
|
|
144
|
-
const orderId = order?.id || this.resolvedOrderId;
|
|
145
|
-
return (h("div", { class: "confirmation" }, h("div", { class: "check", "aria-hidden": "true" }, h("svg", { 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" }))), h("h1", { class: "title" }, "You're going!"), h("p", { class: "lead" }, order?.orderNumber
|
|
146
|
-
? `Order ${order.orderNumber} is confirmed. Your tickets are on their way to your email.`
|
|
147
|
-
: 'Your order is confirmed. Your tickets are on their way to your email.'), this.error && !order && (h("p", { class: "error" }, "We couldn't load your order details yet. ", this.error)), order && Array.isArray(order.items) && (h("div", { class: "summary" }, this.renderRows(order), h("ul", { class: "lines" }, ticketItems(order).map((item, i) => (h("li", { class: "line", key: i }, h("span", { class: "line-name" }, item.quantity, " x ", item.name), h("span", { class: "line-price" }, formatPrice(item.subtotal, order.currency)))))), this.renderTotals(order))), this.showTickets && order && (h("div", { class: "tickets" }, h("h2", { class: "subtitle" }, "Your tickets"), h("tl-order-tickets", { orderId: orderId, columns: this.columns }), h("p", { class: "hint" }, "Show these QR codes at the door for entry. Use Download PDF on a ticket to save it.")))));
|
|
110
|
+
return (h("div", { class: "confirmation" }, h("div", { class: "check", "aria-hidden": "true" }, h("svg", { 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" }))), h("h1", { class: "title" }, strings.title), h("p", { class: "lead" }, derived.leadText), derived.showError && (h("p", { class: "error" }, strings.errorPrefix, state.error)), derived.showSummary && (h("div", { class: "summary" }, this.renderRows(), h("ul", { class: "lines" }, derived.lines.map((line, i) => (h("li", { class: "line", key: i }, h("span", { class: "line-name" }, line.label), h("span", { class: "line-price" }, line.priceLabel))))), this.renderTotals())), derived.showTickets && (h("div", { class: "tickets" }, h("h2", { class: "subtitle" }, strings.ticketsTitle), h("tl-order-tickets", { orderId: derived.ticketsOrderId, columns: this.columns }), h("p", { class: "hint" }, strings.ticketsHint)))));
|
|
148
111
|
}
|
|
149
112
|
static get is() { return "tl-order-confirmation"; }
|
|
150
113
|
static get encapsulation() { return "shadow"; }
|
|
@@ -242,13 +205,7 @@ export class TlOrderConfirmation {
|
|
|
242
205
|
}
|
|
243
206
|
static get states() {
|
|
244
207
|
return {
|
|
245
|
-
"
|
|
246
|
-
"resolvedOrderId": {},
|
|
247
|
-
"loading": {},
|
|
248
|
-
"error": {},
|
|
249
|
-
"paymentFailed": {},
|
|
250
|
-
"occurrence": {},
|
|
251
|
-
"signedInCustomer": {}
|
|
208
|
+
"view": {}
|
|
252
209
|
};
|
|
253
210
|
}
|
|
254
211
|
static get events() {
|
|
@@ -268,8 +225,8 @@ export class TlOrderConfirmation {
|
|
|
268
225
|
"references": {
|
|
269
226
|
"TlOrderData": {
|
|
270
227
|
"location": "import",
|
|
271
|
-
"path": "
|
|
272
|
-
"id": "
|
|
228
|
+
"path": "@ticketlayer/elements-core",
|
|
229
|
+
"id": "../elements-core/dist/esm/index.d.ts::TlOrderData",
|
|
273
230
|
"referenceLocation": "TlOrderData"
|
|
274
231
|
}
|
|
275
232
|
}
|
|
@@ -295,6 +252,15 @@ export class TlOrderConfirmation {
|
|
|
295
252
|
return [{
|
|
296
253
|
"propName": "orderId",
|
|
297
254
|
"methodName": "watchOrderId"
|
|
255
|
+
}, {
|
|
256
|
+
"propName": "customerEmail",
|
|
257
|
+
"methodName": "syncProps"
|
|
258
|
+
}, {
|
|
259
|
+
"propName": "showTickets",
|
|
260
|
+
"methodName": "syncProps"
|
|
261
|
+
}, {
|
|
262
|
+
"propName": "columns",
|
|
263
|
+
"methodName": "syncProps"
|
|
298
264
|
}];
|
|
299
265
|
}
|
|
300
266
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { h } from "@stencil/core";
|
|
2
|
-
import {
|
|
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
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
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.
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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.
|
|
38
|
-
this.
|
|
34
|
+
this.syncProps();
|
|
35
|
+
this.element.actions.orderIdChanged();
|
|
39
36
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
43
|
-
disconnectedCallback() {
|
|
44
|
-
this.stopPolling();
|
|
37
|
+
syncProps() {
|
|
38
|
+
this.element.setProps({ orderId: this.orderId, columns: this.columns });
|
|
45
39
|
}
|
|
46
|
-
|
|
47
|
-
this.
|
|
48
|
-
this.
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
|
|
96
|
-
|
|
64
|
+
const { status, state, strings, derived } = this.view;
|
|
65
|
+
if (status === 'loading') {
|
|
66
|
+
return h("div", { class: "state" }, strings.loading);
|
|
97
67
|
}
|
|
98
|
-
if (
|
|
99
|
-
return h("div", { class: "state error" },
|
|
68
|
+
if (status === 'error') {
|
|
69
|
+
return h("div", { class: "state error" }, state.error);
|
|
100
70
|
}
|
|
101
|
-
if (
|
|
102
|
-
return
|
|
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
|
-
|
|
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
|
-
"
|
|
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,6 +1,6 @@
|
|
|
1
1
|
import { h } from "@stencil/core";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
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.
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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.
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
this.tlError.emit(
|
|
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
|
-
|
|
74
|
-
|
|
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:
|
|
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
|
-
"
|
|
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
|
}
|
|
@@ -132,10 +132,10 @@ export class TlSeatMap {
|
|
|
132
132
|
// Render
|
|
133
133
|
// -------------------------------------------------------------------------
|
|
134
134
|
render() {
|
|
135
|
-
return (h("div", { key: '
|
|
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: '
|
|
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" })));
|