@occluze/tether 0.6.8 → 0.7.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/README.md +11 -11
- package/dist/esm/astro.js +2 -2
- package/dist/esm/chunks/account-UIPWJQ3Y.js +1 -0
- package/dist/esm/chunks/account-pages-6ROA6Y4V.js +1 -0
- package/dist/esm/chunks/analytics-2V632IRV.js +1 -0
- package/dist/esm/chunks/chunk-4GCXM764.js +1 -0
- package/dist/esm/chunks/chunk-56FCLJJD.js +1 -0
- package/dist/esm/chunks/chunk-5Z5O3767.js +1 -0
- package/dist/esm/chunks/chunk-6OZJCAGZ.js +1 -0
- package/dist/esm/chunks/chunk-A5SYCVQM.js +1 -0
- package/dist/esm/chunks/chunk-BA4LQJJQ.js +1 -0
- package/dist/esm/chunks/chunk-BFTZR4OB.js +4 -0
- package/dist/esm/chunks/chunk-C5JYQCWH.js +1 -0
- package/dist/esm/chunks/chunk-E5YO6274.js +1 -0
- package/dist/esm/chunks/chunk-MPDWM2NN.js +1 -0
- package/dist/esm/chunks/chunk-T44MUL4Y.js +1 -0
- package/dist/esm/chunks/chunk-YLUULATY.js +1 -0
- package/dist/esm/chunks/diagnose-3FEKRIEK.js +14 -0
- package/dist/esm/chunks/extras-6POFBNOP.js +1 -0
- package/dist/esm/chunks/markets-ui-FD6NI2OG.js +1 -0
- package/dist/esm/chunks/search-UJ73YBQ5.js +1 -0
- package/dist/esm/index.js +1 -1
- package/dist/esm/server.js +2 -2
- package/dist/presets/barba.esm.js +19 -23
- package/dist/tether.account-pages.js +320 -365
- package/dist/tether.account-pages.min.js +1 -1
- package/dist/tether.account.js +242 -491
- package/dist/tether.account.min.js +1 -1
- package/dist/tether.analytics.js +66 -88
- package/dist/tether.analytics.min.js +1 -1
- package/dist/tether.diagnose.js +36 -33
- package/dist/tether.diagnose.min.js +2 -2
- package/dist/tether.extras.js +28 -36
- package/dist/tether.extras.min.js +1 -1
- package/dist/tether.js +1270 -894
- package/dist/tether.markets.js +73 -90
- package/dist/tether.markets.min.js +1 -1
- package/dist/tether.min.js +0 -0
- package/dist/tether.min.js.map +4 -4
- package/dist/tether.search.js +44 -68
- package/dist/tether.search.min.js +1 -1
- package/dist/types/server/src/shared/account-orders.d.ts +44 -0
- package/dist/types/server/src/shared/account-routes.d.ts +153 -0
- package/dist/types/server/src/shared/api-versions.d.ts +13 -0
- package/dist/types/server/src/shared/error-message.d.ts +2 -0
- package/dist/types/server/src/shared/json.d.ts +25 -0
- package/dist/types/server/src/shared/market-codes.d.ts +16 -0
- package/dist/types/src/account/addresses-view.d.ts +5 -0
- package/dist/types/src/account/answers.d.ts +40 -0
- package/dist/types/src/account/markup.d.ts +19 -0
- package/dist/types/src/account/operations.d.ts +41 -0
- package/dist/types/src/account/order-lines.d.ts +13 -0
- package/dist/types/src/account/order-list.d.ts +12 -0
- package/dist/types/src/account/order-page.d.ts +6 -0
- package/dist/types/src/account/page-answers.d.ts +182 -0
- package/dist/types/src/account/page-events.d.ts +2 -0
- package/dist/types/src/account/server-client.d.ts +60 -0
- package/dist/types/src/account/subscriptions-view.d.ts +2 -0
- package/dist/types/src/account-api.d.ts +18 -0
- package/dist/types/src/account-marks.d.ts +15 -0
- package/dist/types/src/account-pages.d.ts +8 -0
- package/dist/types/src/account.d.ts +64 -0
- package/dist/types/src/amounts.d.ts +12 -0
- package/dist/types/src/analytics-shopify.d.ts +41 -0
- package/dist/types/src/analytics.d.ts +2 -0
- package/dist/types/src/api-version.d.ts +28 -0
- package/dist/types/src/astro.d.ts +42 -0
- package/dist/types/src/cart/cart-shape.d.ts +43 -0
- package/dist/types/src/cart/checkout.d.ts +16 -0
- package/dist/types/src/cart/drawer.d.ts +18 -0
- package/dist/types/src/cart/mutations.d.ts +79 -0
- package/dist/types/src/cart/render.d.ts +17 -0
- package/dist/types/src/cart/store.d.ts +182 -0
- package/dist/types/src/cart.d.ts +106 -0
- package/dist/types/src/client.d.ts +100 -0
- package/dist/types/src/collection.d.ts +25 -0
- package/dist/types/src/config-sections.d.ts +40 -0
- package/dist/types/src/config.d.ts +185 -0
- package/dist/types/src/controls/cart-controls.d.ts +2 -0
- package/dist/types/src/controls/discount.d.ts +2 -0
- package/dist/types/src/controls/extras.d.ts +5 -0
- package/dist/types/src/controls/navigation.d.ts +12 -0
- package/dist/types/src/diagnose-markup.d.ts +19 -0
- package/dist/types/src/diagnose.d.ts +60 -0
- package/dist/types/src/dom.d.ts +75 -0
- package/dist/types/src/extras.d.ts +21 -0
- package/dist/types/src/fragments.d.ts +158 -0
- package/dist/types/src/globals.d.ts +15 -0
- package/dist/types/src/images.d.ts +20 -0
- package/dist/types/src/index.d.ts +167 -0
- package/dist/types/src/initial.d.ts +56 -0
- package/dist/types/src/lazy.d.ts +28 -0
- package/dist/types/src/loader.d.ts +84 -0
- package/dist/types/src/market-codes.d.ts +6 -0
- package/dist/types/src/markets-ui.d.ts +32 -0
- package/dist/types/src/markets.d.ts +72 -0
- package/dist/types/src/markup.d.ts +131 -0
- package/dist/types/src/mounting.d.ts +46 -0
- package/dist/types/src/page-market.d.ts +27 -0
- package/dist/types/src/presets/barba.d.ts +56 -0
- package/dist/types/src/product-events.d.ts +2 -0
- package/dist/types/src/product-render.d.ts +17 -0
- package/dist/types/src/product-shape.d.ts +88 -0
- package/dist/types/src/product-state.d.ts +110 -0
- package/dist/types/src/product.d.ts +66 -0
- package/dist/types/src/repeaters.d.ts +16 -0
- package/dist/types/src/search.d.ts +33 -0
- package/dist/types/src/selling-plans.d.ts +64 -0
- package/dist/types/src/server.d.ts +88 -0
- package/dist/types/src/shopify-values.d.ts +46 -0
- package/dist/types/src/status.d.ts +33 -0
- package/dist/types/src/storage.d.ts +12 -0
- package/dist/types/src/store-html.d.ts +39 -0
- package/dist/types/src/storefront/catalog.d.ts +38 -0
- package/dist/types/src/storefront/json-ld.d.ts +20 -0
- package/dist/types/src/variant-render.d.ts +14 -0
- package/package.json +24 -23
- package/src/account/addresses-view.ts +58 -0
- package/src/account/answers.ts +79 -0
- package/src/account/markup.ts +36 -0
- package/src/account/operations.ts +145 -0
- package/src/account/{order-lines.js → order-lines.ts} +20 -41
- package/src/account/order-list.ts +107 -0
- package/src/account/order-page.ts +137 -0
- package/src/account/page-answers.ts +317 -0
- package/src/account/{page-events.js → page-events.ts} +55 -90
- package/src/account/server-client.ts +183 -0
- package/src/account/{subscriptions-view.js → subscriptions-view.ts} +25 -68
- package/src/account-api.ts +98 -0
- package/src/account-marks.ts +35 -0
- package/src/{account-pages.js → account-pages.ts} +29 -37
- package/src/account.ts +284 -0
- package/src/amounts.ts +64 -0
- package/src/{analytics-shopify.js → analytics-shopify.ts} +112 -107
- package/src/analytics.ts +315 -0
- package/src/{api-version.js → api-version.ts} +25 -31
- package/src/{astro.js → astro.ts} +110 -104
- package/src/cart/cart-shape.ts +25 -0
- package/src/cart/checkout.ts +101 -0
- package/src/cart/{drawer.js → drawer.ts} +18 -31
- package/src/cart/mutations.ts +237 -0
- package/src/cart/{render.js → render.ts} +68 -96
- package/src/cart/store.ts +279 -0
- package/src/{cart.js → cart.ts} +82 -128
- package/src/client.ts +391 -0
- package/src/{collection.js → collection.ts} +30 -38
- package/src/config-sections.ts +284 -0
- package/src/config.ts +480 -0
- package/src/controls/{cart-controls.js → cart-controls.ts} +22 -54
- package/src/controls/{discount.js → discount.ts} +13 -34
- package/src/controls/{extras.js → extras.ts} +15 -24
- package/src/controls/{navigation.js → navigation.ts} +24 -30
- package/src/{diagnose-markup.js → diagnose-markup.ts} +37 -72
- package/src/{diagnose.js → diagnose.ts} +95 -96
- package/src/dom.ts +268 -0
- package/src/{extras.js → extras.ts} +43 -52
- package/src/fragments.ts +261 -0
- package/src/globals.d.ts +15 -0
- package/src/{images.js → images.ts} +30 -43
- package/src/index.ts +415 -0
- package/src/initial.ts +101 -0
- package/src/lazy-iife.ts +197 -0
- package/src/lazy.ts +42 -0
- package/src/{loader.js → loader.ts} +149 -123
- package/src/market-codes.ts +6 -0
- package/src/{markets-ui.js → markets-ui.ts} +91 -68
- package/src/markets.ts +246 -0
- package/src/markup.ts +127 -0
- package/src/{mounting.js → mounting.ts} +88 -101
- package/src/{page-market.js → page-market.ts} +32 -38
- package/src/part-integrity.ts +7 -0
- package/src/parts/account-pages.ts +7 -0
- package/src/parts/account.ts +7 -0
- package/src/parts/analytics.ts +7 -0
- package/src/parts/diagnose.ts +7 -0
- package/src/parts/extras.ts +7 -0
- package/src/parts/markets.ts +7 -0
- package/src/parts/search.ts +7 -0
- package/src/presets/barba.ts +194 -0
- package/src/{product-events.js → product-events.ts} +33 -48
- package/src/{product-render.js → product-render.ts} +38 -77
- package/src/product-shape.ts +49 -0
- package/src/product-state.ts +318 -0
- package/src/{product.js → product.ts} +90 -126
- package/src/{repeaters.js → repeaters.ts} +40 -52
- package/src/{search.js → search.ts} +85 -103
- package/src/selling-plans.ts +145 -0
- package/src/server.ts +211 -0
- package/src/{shopify-values.js → shopify-values.ts} +24 -22
- package/src/{status.js → status.ts} +34 -54
- package/src/storage.ts +34 -0
- package/src/store-html.ts +359 -0
- package/src/storefront/catalog.ts +196 -0
- package/src/storefront/{json-ld.js → json-ld.ts} +45 -68
- package/src/{variant-render.js → variant-render.ts} +24 -50
- package/dist/esm/chunks/account-C3NP6HFB.js +0 -1
- package/dist/esm/chunks/account-pages-WLK374FO.js +0 -1
- package/dist/esm/chunks/analytics-G3SAWMNL.js +0 -1
- package/dist/esm/chunks/chunk-27PJJZGC.js +0 -1
- package/dist/esm/chunks/chunk-2FSXS55U.js +0 -1
- package/dist/esm/chunks/chunk-FCSWQDVV.js +0 -1
- package/dist/esm/chunks/chunk-FOMSMIAM.js +0 -1
- package/dist/esm/chunks/chunk-GZJTCLLW.js +0 -1
- package/dist/esm/chunks/chunk-HEQILZVR.js +0 -1
- package/dist/esm/chunks/chunk-JOOEXYF3.js +0 -4
- package/dist/esm/chunks/chunk-MPTQYLSM.js +0 -1
- package/dist/esm/chunks/chunk-NEHZ37TU.js +0 -3
- package/dist/esm/chunks/chunk-V74KGEJF.js +0 -1
- package/dist/esm/chunks/chunk-VT4CDUJJ.js +0 -1
- package/dist/esm/chunks/chunk-YJLWX2HK.js +0 -1
- package/dist/esm/chunks/diagnose-6VVGZNTY.js +0 -14
- package/dist/esm/chunks/extras-GJVRDFM3.js +0 -1
- package/dist/esm/chunks/markets-ui-UNB4PTA7.js +0 -1
- package/dist/esm/chunks/search-OU75RNQL.js +0 -1
- package/src/account/addresses-view.js +0 -92
- package/src/account/browser-session.js +0 -359
- package/src/account/markup.js +0 -48
- package/src/account/operations.js +0 -180
- package/src/account/order-list.js +0 -91
- package/src/account/order-page.js +0 -192
- package/src/account/server-client.js +0 -172
- package/src/account/shared.js +0 -111
- package/src/account/shopify-auth.js +0 -150
- package/src/account-api.js +0 -118
- package/src/account-links.js +0 -35
- package/src/account.js +0 -342
- package/src/amounts.js +0 -36
- package/src/analytics.js +0 -352
- package/src/cart/checkout.js +0 -97
- package/src/cart/mutations.js +0 -234
- package/src/cart/store.js +0 -326
- package/src/client.js +0 -267
- package/src/config.js +0 -349
- package/src/dom.js +0 -287
- package/src/fragments.js +0 -237
- package/src/index.js +0 -353
- package/src/initial.js +0 -87
- package/src/lazy-iife.js +0 -116
- package/src/lazy.js +0 -39
- package/src/market-codes.js +0 -35
- package/src/markets.js +0 -334
- package/src/markup.js +0 -64
- package/src/parts/account-pages.js +0 -6
- package/src/parts/account.js +0 -6
- package/src/parts/analytics.js +0 -6
- package/src/parts/diagnose.js +0 -6
- package/src/parts/extras.js +0 -6
- package/src/parts/markets.js +0 -6
- package/src/parts/search.js +0 -6
- package/src/presets/barba.js +0 -190
- package/src/product-state.js +0 -318
- package/src/selling-plans.js +0 -154
- package/src/server.js +0 -215
- package/src/store-html.js +0 -0
- package/src/storefront/catalog.js +0 -199
- package/types/astro.d.ts +0 -28
- package/types/barba.d.ts +0 -16
- package/types/index.d.ts +0 -577
- package/types/server.d.ts +0 -182
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The order page (`tether-order`): one order in full, with its lines, totals, addresses, shipments and what can be
|
|
3
|
+
* returned. `tether-order-state` and `tether-order-show` follow the load: `loading`, then `ready`, `not-found` or
|
|
4
|
+
* `error`.
|
|
5
|
+
*/
|
|
6
|
+
import { reportError } from '../config.ts';
|
|
7
|
+
import { isHttpsUrl } from '../shopify-values.ts';
|
|
8
|
+
import { attr, formatDate, formatMoney, get, qsa, repeat, sel, setText, show } from '../dom.ts';
|
|
9
|
+
import { addressText, bindsIn, enumText, ownValue, showIn } from './markup.ts';
|
|
10
|
+
import { fillLines } from './order-lines.ts';
|
|
11
|
+
import { order } from './operations.ts';
|
|
12
|
+
import type { Fulfillment, OrderDetail } from './page-answers.ts';
|
|
13
|
+
|
|
14
|
+
type OrderState = 'loading' | 'ready' | 'not-found' | 'error';
|
|
15
|
+
|
|
16
|
+
/** @returns The order `tether-order` names: an id, or `url:<param>` for one in the page's URL. */
|
|
17
|
+
function orderKey(el: HTMLElement): string | null {
|
|
18
|
+
const raw = get(el, 'order') || '';
|
|
19
|
+
if (!raw.startsWith('url:')) return raw;
|
|
20
|
+
return new URL(window.location.href).searchParams.get(raw.slice('url:'.length) || 'id');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Every state but `ready`, which shows with the order. */
|
|
24
|
+
export function showOrderState(el: HTMLElement, state: Exclude<OrderState, 'ready'>): void {
|
|
25
|
+
el.setAttribute(attr('order-state'), state);
|
|
26
|
+
showIn(el, 'order-show', {
|
|
27
|
+
loading: state === 'loading',
|
|
28
|
+
ready: false,
|
|
29
|
+
'not-found': state === 'not-found',
|
|
30
|
+
error: state === 'error',
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** @returns The text of each `tether-order-bind` value. */
|
|
35
|
+
const orderTexts = (detail: OrderDetail): Record<string, string> => ({
|
|
36
|
+
name: detail.name ?? '',
|
|
37
|
+
date: formatDate(detail.processedAt),
|
|
38
|
+
total: formatMoney(detail.totalPrice),
|
|
39
|
+
subtotal: formatMoney(detail.subtotal),
|
|
40
|
+
shipping: formatMoney(detail.totalShipping),
|
|
41
|
+
tax: formatMoney(detail.totalTax),
|
|
42
|
+
status: enumText(detail.financialStatus),
|
|
43
|
+
fulfillment: enumText(detail.fulfillmentStatus),
|
|
44
|
+
'shipping-address': addressText(detail.shippingAddress),
|
|
45
|
+
'billing-address': addressText(detail.billingAddress),
|
|
46
|
+
items: detail.lineItems.nodes.map((line) => `${String(line.quantity)}× ${line.title || line.name || ''}`).join(', '),
|
|
47
|
+
cancelled: formatDate(detail.cancelledAt),
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
/** One shipment row: its status and dates, and its tracking link, hidden without an https one. */
|
|
51
|
+
function fillFulfillment(row: HTMLElement, fulfillment: Fulfillment): void {
|
|
52
|
+
const tracking = fulfillment.trackingInformation[0];
|
|
53
|
+
const texts: Record<string, string> = {
|
|
54
|
+
status: enumText(fulfillment.latestShipmentStatus || fulfillment.status),
|
|
55
|
+
date: formatDate(fulfillment.createdAt),
|
|
56
|
+
eta: formatDate(fulfillment.estimatedDeliveryAt),
|
|
57
|
+
company: tracking?.company || '',
|
|
58
|
+
number: tracking?.number || '',
|
|
59
|
+
};
|
|
60
|
+
bindsIn(row, 'fulfillment-bind').forEach((el) => {
|
|
61
|
+
const key = get(el, 'fulfillment-bind') ?? '';
|
|
62
|
+
if (key === 'url') {
|
|
63
|
+
const url = tracking?.url ?? null;
|
|
64
|
+
const linked = url !== null && isHttpsUrl(url);
|
|
65
|
+
if (linked) el.setAttribute('href', url);
|
|
66
|
+
show(el, linked);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const text = ownValue(texts, key);
|
|
70
|
+
if (text !== undefined) setText(el, text);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function fillOrder(el: HTMLElement, detail: OrderDetail): void {
|
|
75
|
+
const returnable = new Map(
|
|
76
|
+
(detail.returnInformation?.returnableLineItems.nodes ?? []).map((entry) => [
|
|
77
|
+
entry.lineItem?.id ?? '',
|
|
78
|
+
entry.quantity,
|
|
79
|
+
]),
|
|
80
|
+
);
|
|
81
|
+
const shipments = detail.fulfillments.nodes;
|
|
82
|
+
const texts = orderTexts(detail);
|
|
83
|
+
qsa(el, sel('order-bind')).forEach((bind) => {
|
|
84
|
+
const key = get(bind, 'order-bind') ?? '';
|
|
85
|
+
if (key === 'url') {
|
|
86
|
+
const statusPage = detail.statusPageUrl;
|
|
87
|
+
if (statusPage !== null && isHttpsUrl(statusPage)) bind.setAttribute('href', statusPage);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
const text = ownValue(texts, key);
|
|
91
|
+
if (text !== undefined) setText(bind, text);
|
|
92
|
+
});
|
|
93
|
+
qsa(el, sel('order-lines')).forEach((box) => {
|
|
94
|
+
fillLines(box, detail.lineItems.nodes, returnable);
|
|
95
|
+
});
|
|
96
|
+
qsa(el, sel('order-fulfillments')).forEach((box) => {
|
|
97
|
+
repeat(box, shipments, fillFulfillment);
|
|
98
|
+
});
|
|
99
|
+
const returnRequested = el.dataset['tetherReturned'] === '1';
|
|
100
|
+
showIn(el, 'order-show', {
|
|
101
|
+
loading: false,
|
|
102
|
+
ready: true,
|
|
103
|
+
error: false,
|
|
104
|
+
'not-found': false,
|
|
105
|
+
cancelled: Boolean(detail.cancelledAt),
|
|
106
|
+
shipped: shipments.length > 0,
|
|
107
|
+
returnable: [...returnable.values()].some((quantity) => quantity > 0),
|
|
108
|
+
'has-returns': !returnRequested && detail.returns.nodes.length > 0,
|
|
109
|
+
'return-requested': returnRequested,
|
|
110
|
+
});
|
|
111
|
+
el.setAttribute(attr('order-state'), 'ready');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Loads and renders the order `el` names. A failure is reported and shows the error state. */
|
|
115
|
+
export async function renderOrder(el: HTMLElement): Promise<void> {
|
|
116
|
+
const key = orderKey(el);
|
|
117
|
+
if (!key) {
|
|
118
|
+
el.setAttribute(attr('order-state'), 'not-found');
|
|
119
|
+
showIn(el, 'order-show', { 'not-found': true });
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
showOrderState(el, 'loading');
|
|
123
|
+
let detail: OrderDetail | null;
|
|
124
|
+
try {
|
|
125
|
+
detail = await order(key);
|
|
126
|
+
} catch (error) {
|
|
127
|
+
reportError(error, 'order');
|
|
128
|
+
showOrderState(el, 'error');
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
if (!detail) {
|
|
132
|
+
showOrderState(el, 'not-found');
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
el.dataset['tetherOrderId'] = detail.id;
|
|
136
|
+
fillOrder(el, detail);
|
|
137
|
+
}
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The account pages' answers as typed values: addresses, the order page, subscriptions and what a change returns. The
|
|
3
|
+
* rules are answers.ts's: every selected field is kept, a missing one reads as `null`, and the ids the pages act on
|
|
4
|
+
* are required.
|
|
5
|
+
*/
|
|
6
|
+
import {
|
|
7
|
+
countOrZero,
|
|
8
|
+
field,
|
|
9
|
+
isRecord,
|
|
10
|
+
listOf,
|
|
11
|
+
nodesOf,
|
|
12
|
+
numberOrNull,
|
|
13
|
+
textList,
|
|
14
|
+
textOrNull,
|
|
15
|
+
} from '../../server/src/shared/json.ts';
|
|
16
|
+
import { imageOrNull, moneyOrNull, toOrderLine } from '../../server/src/shared/account-orders.ts';
|
|
17
|
+
import type { AccountImage, AccountOrderLine } from '../../server/src/shared/account-orders.ts';
|
|
18
|
+
import type { Money } from '../shopify-values.ts';
|
|
19
|
+
import { requiredId, unexpected } from './answers.ts';
|
|
20
|
+
|
|
21
|
+
/** An address as the documents select it. */
|
|
22
|
+
export interface MailingAddress {
|
|
23
|
+
id: string | null;
|
|
24
|
+
firstName: string | null;
|
|
25
|
+
lastName: string | null;
|
|
26
|
+
company: string | null;
|
|
27
|
+
address1: string | null;
|
|
28
|
+
address2: string | null;
|
|
29
|
+
city: string | null;
|
|
30
|
+
province: string | null;
|
|
31
|
+
zoneCode: string | null;
|
|
32
|
+
zip: string | null;
|
|
33
|
+
country: string | null;
|
|
34
|
+
countryCode: string | null;
|
|
35
|
+
phoneNumber: string | null;
|
|
36
|
+
/** One line per entry. */
|
|
37
|
+
formatted: string[];
|
|
38
|
+
}
|
|
39
|
+
/** One of the customer's saved addresses. */
|
|
40
|
+
export type AccountAddress = MailingAddress & { id: string };
|
|
41
|
+
export type CustomerAddress = AccountAddress & { isDefault: boolean };
|
|
42
|
+
interface ReturnReason {
|
|
43
|
+
id: string;
|
|
44
|
+
name: string | null;
|
|
45
|
+
handle: string | null;
|
|
46
|
+
}
|
|
47
|
+
/** A line of the order page's document. */
|
|
48
|
+
export type OrderDetailLine = AccountOrderLine & {
|
|
49
|
+
id: string;
|
|
50
|
+
name: string | null;
|
|
51
|
+
sku: string | null;
|
|
52
|
+
price: Money | null;
|
|
53
|
+
totalPrice: Money | null;
|
|
54
|
+
suggestedReturnReasonDefinitions: { nodes: ReturnReason[] };
|
|
55
|
+
};
|
|
56
|
+
interface Tracking {
|
|
57
|
+
company: string | null;
|
|
58
|
+
number: string | null;
|
|
59
|
+
url: string | null;
|
|
60
|
+
}
|
|
61
|
+
export interface Fulfillment {
|
|
62
|
+
id: string | null;
|
|
63
|
+
status: string | null;
|
|
64
|
+
createdAt: string | null;
|
|
65
|
+
estimatedDeliveryAt: string | null;
|
|
66
|
+
latestShipmentStatus: string | null;
|
|
67
|
+
trackingInformation: Tracking[];
|
|
68
|
+
}
|
|
69
|
+
interface ReturnableLine {
|
|
70
|
+
quantity: number;
|
|
71
|
+
lineItem: { id: string } | null;
|
|
72
|
+
}
|
|
73
|
+
/** A return Shopify opened. */
|
|
74
|
+
export interface OpenedReturn {
|
|
75
|
+
id: string;
|
|
76
|
+
status: string | null;
|
|
77
|
+
}
|
|
78
|
+
/** The order page's document. */
|
|
79
|
+
export interface OrderDetail {
|
|
80
|
+
id: string;
|
|
81
|
+
name: string | null;
|
|
82
|
+
number: number | null;
|
|
83
|
+
processedAt: string | null;
|
|
84
|
+
cancelledAt: string | null;
|
|
85
|
+
financialStatus: string | null;
|
|
86
|
+
fulfillmentStatus: string | null;
|
|
87
|
+
statusPageUrl: string | null;
|
|
88
|
+
requiresShipping: boolean | null;
|
|
89
|
+
totalPrice: Money | null;
|
|
90
|
+
subtotal: Money | null;
|
|
91
|
+
totalShipping: Money | null;
|
|
92
|
+
totalTax: Money | null;
|
|
93
|
+
shippingAddress: MailingAddress | null;
|
|
94
|
+
billingAddress: MailingAddress | null;
|
|
95
|
+
lineItems: { nodes: OrderDetailLine[] };
|
|
96
|
+
fulfillments: { nodes: Fulfillment[] };
|
|
97
|
+
returnInformation: { returnableLineItems: { nodes: ReturnableLine[] } } | null;
|
|
98
|
+
returns: { nodes: OpenedReturn[] };
|
|
99
|
+
}
|
|
100
|
+
export interface SubscriptionLine {
|
|
101
|
+
id: string | null;
|
|
102
|
+
name: string | null;
|
|
103
|
+
title: string | null;
|
|
104
|
+
variantTitle: string | null;
|
|
105
|
+
quantity: number;
|
|
106
|
+
sku: string | null;
|
|
107
|
+
currentPrice: Money | null;
|
|
108
|
+
lineDiscountedPrice: Money | null;
|
|
109
|
+
image: AccountImage | null;
|
|
110
|
+
}
|
|
111
|
+
interface BillingCycle {
|
|
112
|
+
cycleIndex: number | null;
|
|
113
|
+
skipped: boolean;
|
|
114
|
+
billingAttemptExpectedDate: string | null;
|
|
115
|
+
}
|
|
116
|
+
/** The subscriptions document. */
|
|
117
|
+
export interface SubscriptionContract {
|
|
118
|
+
id: string;
|
|
119
|
+
/** `ACTIVE`, `PAUSED`, `CANCELLED`, `EXPIRED` or `FAILED`. */
|
|
120
|
+
status: string | null;
|
|
121
|
+
nextBillingDate: string | null;
|
|
122
|
+
currencyCode: string | null;
|
|
123
|
+
deliveryPrice: Money | null;
|
|
124
|
+
deliveryPolicy: { interval: string | null; intervalCount: { count: number } | null } | null;
|
|
125
|
+
lines: { nodes: SubscriptionLine[] };
|
|
126
|
+
upcomingBillingCycles: { nodes: BillingCycle[] };
|
|
127
|
+
}
|
|
128
|
+
export interface Profile {
|
|
129
|
+
firstName: string | null;
|
|
130
|
+
lastName: string | null;
|
|
131
|
+
}
|
|
132
|
+
/** What a subscription action changed: the contract (pause, resume, cancel) or its next billing cycle (skip, unskip). */
|
|
133
|
+
export type SubscriptionChange =
|
|
134
|
+
| { contract: { id: string | null; status: string | null } }
|
|
135
|
+
| { billingCycle: { cycleIndex: number | null; skipped: boolean } };
|
|
136
|
+
|
|
137
|
+
function toMailingAddress(value: unknown): MailingAddress {
|
|
138
|
+
const text = (name: string) => textOrNull(field(value, name));
|
|
139
|
+
return {
|
|
140
|
+
id: text('id'),
|
|
141
|
+
firstName: text('firstName'),
|
|
142
|
+
lastName: text('lastName'),
|
|
143
|
+
company: text('company'),
|
|
144
|
+
address1: text('address1'),
|
|
145
|
+
address2: text('address2'),
|
|
146
|
+
city: text('city'),
|
|
147
|
+
province: text('province'),
|
|
148
|
+
zoneCode: text('zoneCode'),
|
|
149
|
+
zip: text('zip'),
|
|
150
|
+
country: text('country'),
|
|
151
|
+
countryCode: text('countryCode'),
|
|
152
|
+
phoneNumber: text('phoneNumber'),
|
|
153
|
+
formatted: textList(field(value, 'formatted')),
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const mailingAddressOrNull = (value: unknown): MailingAddress | null =>
|
|
158
|
+
isRecord(value) ? toMailingAddress(value) : null;
|
|
159
|
+
|
|
160
|
+
/** @throws {TetherError} Without an id. */
|
|
161
|
+
export const toAccountAddress = (value: unknown): AccountAddress => ({
|
|
162
|
+
...toMailingAddress(value),
|
|
163
|
+
id: requiredId(field(value, 'id'), 'address id'),
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* @param value The addresses operation's list, each address with the `isDefault` it adds.
|
|
168
|
+
* @throws {TetherError} For an address without an id.
|
|
169
|
+
*/
|
|
170
|
+
export const toCustomerAddresses = (value: unknown): CustomerAddress[] =>
|
|
171
|
+
listOf(value, (address) => ({ ...toAccountAddress(address), isDefault: field(address, 'isDefault') === true }));
|
|
172
|
+
|
|
173
|
+
const toOrderDetailLine = (value: unknown): OrderDetailLine => ({
|
|
174
|
+
...toOrderLine(value),
|
|
175
|
+
id: requiredId(field(value, 'id'), 'line item id'),
|
|
176
|
+
name: textOrNull(field(value, 'name')),
|
|
177
|
+
sku: textOrNull(field(value, 'sku')),
|
|
178
|
+
price: moneyOrNull(field(value, 'price')),
|
|
179
|
+
totalPrice: moneyOrNull(field(value, 'totalPrice')),
|
|
180
|
+
suggestedReturnReasonDefinitions: nodesOf(field(value, 'suggestedReturnReasonDefinitions'), (reason) => ({
|
|
181
|
+
id: requiredId(field(reason, 'id'), 'return reason id'),
|
|
182
|
+
name: textOrNull(field(reason, 'name')),
|
|
183
|
+
handle: textOrNull(field(reason, 'handle')),
|
|
184
|
+
})),
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
const toFulfillment = (value: unknown): Fulfillment => ({
|
|
188
|
+
id: textOrNull(field(value, 'id')),
|
|
189
|
+
status: textOrNull(field(value, 'status')),
|
|
190
|
+
createdAt: textOrNull(field(value, 'createdAt')),
|
|
191
|
+
estimatedDeliveryAt: textOrNull(field(value, 'estimatedDeliveryAt')),
|
|
192
|
+
latestShipmentStatus: textOrNull(field(value, 'latestShipmentStatus')),
|
|
193
|
+
trackingInformation: listOf(field(value, 'trackingInformation'), (tracking) => ({
|
|
194
|
+
company: textOrNull(field(tracking, 'company')),
|
|
195
|
+
number: textOrNull(field(tracking, 'number')),
|
|
196
|
+
url: textOrNull(field(tracking, 'url')),
|
|
197
|
+
})),
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
function toReturnableLine(value: unknown): ReturnableLine {
|
|
201
|
+
const lineId = textOrNull(field(field(value, 'lineItem'), 'id'));
|
|
202
|
+
return { quantity: countOrZero(field(value, 'quantity')), lineItem: lineId === null ? null : { id: lineId } };
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function toReturnInformation(value: unknown): OrderDetail['returnInformation'] {
|
|
206
|
+
if (!isRecord(value)) return null;
|
|
207
|
+
return { returnableLineItems: nodesOf(value['returnableLineItems'], toReturnableLine) };
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* @param value The order operation's `order`.
|
|
212
|
+
* @returns `null` when the customer has no such order.
|
|
213
|
+
* @throws {TetherError} For an order, line, reason or return without an id.
|
|
214
|
+
*/
|
|
215
|
+
export function toOrderDetail(value: unknown): OrderDetail | null {
|
|
216
|
+
if (!isRecord(value)) return null;
|
|
217
|
+
const text = (name: string) => textOrNull(value[name]);
|
|
218
|
+
const money = (name: string) => moneyOrNull(value[name]);
|
|
219
|
+
const requiresShipping = value['requiresShipping'];
|
|
220
|
+
return {
|
|
221
|
+
id: requiredId(value['id'], 'order id'),
|
|
222
|
+
name: text('name'),
|
|
223
|
+
number: numberOrNull(value['number']),
|
|
224
|
+
processedAt: text('processedAt'),
|
|
225
|
+
cancelledAt: text('cancelledAt'),
|
|
226
|
+
financialStatus: text('financialStatus'),
|
|
227
|
+
fulfillmentStatus: text('fulfillmentStatus'),
|
|
228
|
+
statusPageUrl: text('statusPageUrl'),
|
|
229
|
+
requiresShipping: typeof requiresShipping === 'boolean' ? requiresShipping : null,
|
|
230
|
+
totalPrice: money('totalPrice'),
|
|
231
|
+
subtotal: money('subtotal'),
|
|
232
|
+
totalShipping: money('totalShipping'),
|
|
233
|
+
totalTax: money('totalTax'),
|
|
234
|
+
shippingAddress: mailingAddressOrNull(value['shippingAddress']),
|
|
235
|
+
billingAddress: mailingAddressOrNull(value['billingAddress']),
|
|
236
|
+
lineItems: nodesOf(value['lineItems'], toOrderDetailLine),
|
|
237
|
+
fulfillments: nodesOf(value['fulfillments'], toFulfillment),
|
|
238
|
+
returnInformation: toReturnInformation(value['returnInformation']),
|
|
239
|
+
returns: nodesOf(value['returns'], toReturn),
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const toSubscriptionLine = (value: unknown): SubscriptionLine => ({
|
|
244
|
+
id: textOrNull(field(value, 'id')),
|
|
245
|
+
name: textOrNull(field(value, 'name')),
|
|
246
|
+
title: textOrNull(field(value, 'title')),
|
|
247
|
+
variantTitle: textOrNull(field(value, 'variantTitle')),
|
|
248
|
+
quantity: countOrZero(field(value, 'quantity')),
|
|
249
|
+
sku: textOrNull(field(value, 'sku')),
|
|
250
|
+
currentPrice: moneyOrNull(field(value, 'currentPrice')),
|
|
251
|
+
lineDiscountedPrice: moneyOrNull(field(value, 'lineDiscountedPrice')),
|
|
252
|
+
image: imageOrNull(field(value, 'image')),
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
const toBillingCycle = (value: unknown): BillingCycle => ({
|
|
256
|
+
cycleIndex: numberOrNull(field(value, 'cycleIndex')),
|
|
257
|
+
skipped: field(value, 'skipped') === true,
|
|
258
|
+
billingAttemptExpectedDate: textOrNull(field(value, 'billingAttemptExpectedDate')),
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
function toDeliveryPolicy(value: unknown): SubscriptionContract['deliveryPolicy'] {
|
|
262
|
+
if (!isRecord(value)) return null;
|
|
263
|
+
const intervalCount = value['intervalCount'];
|
|
264
|
+
return {
|
|
265
|
+
interval: textOrNull(value['interval']),
|
|
266
|
+
intervalCount: isRecord(intervalCount) ? { count: countOrZero(intervalCount['count']) } : null,
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/** @throws {TetherError} Without an id. */
|
|
271
|
+
const toSubscriptionContract = (value: unknown): SubscriptionContract => ({
|
|
272
|
+
id: requiredId(field(value, 'id'), 'subscription id'),
|
|
273
|
+
status: textOrNull(field(value, 'status')),
|
|
274
|
+
nextBillingDate: textOrNull(field(value, 'nextBillingDate')),
|
|
275
|
+
currencyCode: textOrNull(field(value, 'currencyCode')),
|
|
276
|
+
deliveryPrice: moneyOrNull(field(value, 'deliveryPrice')),
|
|
277
|
+
deliveryPolicy: toDeliveryPolicy(field(value, 'deliveryPolicy')),
|
|
278
|
+
lines: nodesOf(field(value, 'lines'), toSubscriptionLine),
|
|
279
|
+
upcomingBillingCycles: nodesOf(field(value, 'upcomingBillingCycles'), toBillingCycle),
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* @param value The subscriptions operation's list.
|
|
284
|
+
* @throws {TetherError} For a contract without an id.
|
|
285
|
+
*/
|
|
286
|
+
export const toSubscriptionContracts = (value: unknown): SubscriptionContract[] =>
|
|
287
|
+
listOf(value, toSubscriptionContract);
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* @param result The subscription operation's result.
|
|
291
|
+
* @throws {TetherError} With neither a contract nor a billing cycle.
|
|
292
|
+
*/
|
|
293
|
+
export function toSubscriptionChange(result: Record<string, unknown>): SubscriptionChange {
|
|
294
|
+
const { contract, billingCycle } = result;
|
|
295
|
+
if (isRecord(contract)) {
|
|
296
|
+
return { contract: { id: textOrNull(contract['id']), status: textOrNull(contract['status']) } };
|
|
297
|
+
}
|
|
298
|
+
if (isRecord(billingCycle)) {
|
|
299
|
+
const { cycleIndex, skipped } = toBillingCycle(billingCycle);
|
|
300
|
+
return { billingCycle: { cycleIndex, skipped } };
|
|
301
|
+
}
|
|
302
|
+
throw unexpected('changed subscription');
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/** @throws {TetherError} Without an id. */
|
|
306
|
+
export function toReturn(value: unknown): OpenedReturn {
|
|
307
|
+
return { id: requiredId(field(value, 'id'), 'return id'), status: textOrNull(field(value, 'status')) };
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/** @throws {TetherError} Without an id. */
|
|
311
|
+
export const toDeletedId = (value: unknown): string => requiredId(value, 'deleted address id');
|
|
312
|
+
|
|
313
|
+
/** @param value The profile operation's `customer`. */
|
|
314
|
+
export const toProfile = (value: unknown): Profile => ({
|
|
315
|
+
firstName: textOrNull(field(value, 'firstName')),
|
|
316
|
+
lastName: textOrNull(field(value, 'lastName')),
|
|
317
|
+
});
|