@occluze/tether 0.6.8 → 0.7.1
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 +28 -28
- package/dist/esm/astro.js +2 -2
- package/dist/esm/chunks/account-CXMA5RPZ.js +1 -0
- package/dist/esm/chunks/account-pages-IZO7FEOZ.js +1 -0
- package/dist/esm/chunks/analytics-PG5FPRDN.js +1 -0
- package/dist/esm/chunks/chunk-4HD4J5JC.js +1 -0
- package/dist/esm/chunks/chunk-6TFMLT4A.js +4 -0
- package/dist/esm/chunks/chunk-BBVUVPMO.js +1 -0
- package/dist/esm/chunks/chunk-E5YO6274.js +1 -0
- package/dist/esm/chunks/chunk-EW5SYO6L.js +1 -0
- package/dist/esm/chunks/chunk-HJY2JSNB.js +1 -0
- package/dist/esm/chunks/chunk-MQYB6BH4.js +1 -0
- package/dist/esm/chunks/chunk-N3V45JPS.js +1 -0
- package/dist/esm/chunks/chunk-RPMBCAGS.js +1 -0
- package/dist/esm/chunks/chunk-VFNHIGCS.js +1 -0
- package/dist/esm/chunks/chunk-WT7RGGJU.js +1 -0
- package/dist/esm/chunks/chunk-YN6LEBEN.js +1 -0
- package/dist/esm/chunks/chunk-Z6HS3H7K.js +1 -0
- package/dist/esm/chunks/diagnose-XCEBQKBO.js +14 -0
- package/dist/esm/chunks/extras-4HU3VSBQ.js +1 -0
- package/dist/esm/chunks/markets-ui-M4IQ62CT.js +1 -0
- package/dist/esm/chunks/search-A5DS2IOS.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 +332 -367
- package/dist/tether.account-pages.min.js +1 -1
- package/dist/tether.account.js +253 -492
- package/dist/tether.account.min.js +1 -1
- package/dist/tether.analytics.js +82 -89
- package/dist/tether.analytics.min.js +1 -1
- package/dist/tether.diagnose.js +52 -34
- package/dist/tether.diagnose.min.js +2 -2
- package/dist/tether.extras.js +42 -37
- package/dist/tether.extras.min.js +1 -1
- package/dist/tether.js +1361 -935
- package/dist/tether.markets.js +89 -91
- 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 +60 -69
- 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/cart-id.d.ts +2 -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 +85 -0
- package/dist/types/src/cart/render.d.ts +17 -0
- package/dist/types/src/cart/store.d.ts +185 -0
- package/dist/types/src/cart.d.ts +106 -0
- package/dist/types/src/client.d.ts +134 -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 +190 -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 +90 -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 -24
- 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-lines.js +0 -96
- package/src/account/order-list.js +0 -91
- package/src/account/order-page.js +0 -192
- package/src/account/page-events.js +0 -288
- 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/subscriptions-view.js +0 -158
- package/src/account-api.js +0 -118
- package/src/account-links.js +0 -35
- package/src/account-pages.js +0 -89
- package/src/account.js +0 -342
- package/src/amounts.js +0 -36
- package/src/analytics-shopify.js +0 -241
- package/src/analytics.js +0 -352
- package/src/api-version.js +0 -85
- package/src/astro.js +0 -266
- package/src/cart/checkout.js +0 -97
- package/src/cart/drawer.js +0 -138
- package/src/cart/mutations.js +0 -234
- package/src/cart/render.js +0 -294
- package/src/cart/store.js +0 -326
- package/src/cart.js +0 -391
- package/src/client.js +0 -267
- package/src/collection.js +0 -183
- package/src/config.js +0 -349
- package/src/controls/cart-controls.js +0 -155
- package/src/controls/discount.js +0 -96
- package/src/controls/extras.js +0 -63
- package/src/controls/navigation.js +0 -83
- package/src/diagnose-markup.js +0 -333
- package/src/diagnose.js +0 -295
- package/src/dom.js +0 -287
- package/src/extras.js +0 -145
- package/src/fragments.js +0 -237
- package/src/images.js +0 -104
- 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/loader.js +0 -337
- package/src/market-codes.js +0 -35
- package/src/markets-ui.js +0 -204
- package/src/markets.js +0 -334
- package/src/markup.js +0 -64
- package/src/mounting.js +0 -277
- package/src/page-market.js +0 -116
- 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-events.js +0 -130
- package/src/product-render.js +0 -257
- package/src/product-state.js +0 -318
- package/src/product.js +0 -344
- package/src/repeaters.js +0 -173
- package/src/search.js +0 -323
- package/src/selling-plans.js +0 -154
- package/src/server.js +0 -215
- package/src/shopify-values.js +0 -89
- package/src/status.js +0 -126
- package/src/store-html.js +0 -0
- package/src/storefront/catalog.js +0 -199
- package/src/storefront/json-ld.js +0 -167
- package/src/variant-render.js +0 -165
- 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
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The account pages' operations, the same fixed ones in both modes: the Worker's routes in server mode, the Customer
|
|
3
|
-
* Account API directly in browser mode, with the documents and input checks of account-ops.js either way. Each
|
|
4
|
-
* result has the shape of its document there, which the typedefs of the views that render it state.
|
|
5
|
-
*/
|
|
6
|
-
import { TetherError } from '../client.js';
|
|
7
|
-
import { emit } from '../dom.js';
|
|
8
|
-
import { ops, InputError } from '../../server/src/shared/account-ops.js';
|
|
9
|
-
/** @import { ServerAnswer, ServerRequest } from './server-client.js' */
|
|
10
|
-
/** @import { AccountCustomer } from './browser-session.js' */
|
|
11
|
-
/** @import { OrderDetail } from './order-page.js' */
|
|
12
|
-
/** @import { CustomerAddress } from './addresses-view.js' */
|
|
13
|
-
/** @import { SubscriptionContract } from './subscriptions-view.js' */
|
|
14
|
-
/** @import { SubscriptionAction } from '../../server/src/shared/account-ops.js' */
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* @typedef {object} PagesContext The account part's session, shared so both parts act as one customer.
|
|
18
|
-
* @property {() => string} serverBase `''` in browser mode.
|
|
19
|
-
* @property {(path: string, request?: ServerRequest) => Promise<ServerAnswer>} serverFetch
|
|
20
|
-
* @property {(document: string, variables?: Record<string, unknown>) => Promise<unknown>} query
|
|
21
|
-
* @property {() => Promise<AccountCustomer | null>} customer
|
|
22
|
-
* @property {(root: ParentNode) => Promise<void>} bindCustomer
|
|
23
|
-
* @property {() => void} reset Forgets the cached customer after a change of their data.
|
|
24
|
-
*/
|
|
25
|
-
/** @typedef {Record<string, unknown>} AddressFields `CustomerAddressInput` fields; account-ops.js keeps only those. */
|
|
26
|
-
/**
|
|
27
|
-
* @typedef {object} ReturnLine
|
|
28
|
-
* @property {string} lineItemId
|
|
29
|
-
* @property {number} quantity
|
|
30
|
-
* @property {string} [reasonId]
|
|
31
|
-
* @property {string} [note]
|
|
32
|
-
*/
|
|
33
|
-
/** @typedef {'order' | 'addresses' | 'subscriptions' | 'profile' | 'saveAddress' | 'deleteAddress' | 'subscription' | 'requestReturn'} OperationName */
|
|
34
|
-
/** @typedef {'profile' | 'addresses' | 'subscriptions' | 'orders'} ChangedData `tether:account-updated`'s `what`. */
|
|
35
|
-
|
|
36
|
-
/** @type {PagesContext | null} */
|
|
37
|
-
let context = null;
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Called by the account part once, when it loads this part.
|
|
41
|
-
*
|
|
42
|
-
* @param {PagesContext} shared
|
|
43
|
-
*/
|
|
44
|
-
export function setup(shared) {
|
|
45
|
-
context = shared;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* @returns {PagesContext}
|
|
50
|
-
* @throws {Error} Before `setup`: this part is only ever loaded by the account part, which calls it first.
|
|
51
|
-
*/
|
|
52
|
-
export function pagesContext() {
|
|
53
|
-
if (!context) throw new Error('account-pages: loaded without the account part');
|
|
54
|
-
return context;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/** @type {Record<OperationName, (input: Record<string, unknown>) => ['GET' | 'POST', string]>} */
|
|
58
|
-
const ROUTE = {
|
|
59
|
-
order: (input) => ['GET', `/order?id=${encodeURIComponent(String(input['id']))}`],
|
|
60
|
-
addresses: () => ['GET', '/addresses'],
|
|
61
|
-
subscriptions: () => ['GET', '/subscriptions'],
|
|
62
|
-
profile: () => ['POST', '/profile'],
|
|
63
|
-
saveAddress: () => ['POST', '/address'],
|
|
64
|
-
deleteAddress: () => ['POST', '/address/delete'],
|
|
65
|
-
subscription: () => ['POST', '/subscription'],
|
|
66
|
-
requestReturn: () => ['POST', '/return'],
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* @param {OperationName} name
|
|
71
|
-
* @param {Record<string, unknown>} [input]
|
|
72
|
-
* @returns {Promise<Record<string, unknown>>} The operation's result fields.
|
|
73
|
-
* @throws {TetherError} `NOT_LOGGED_IN` (status 401), `INVALID` for input account-ops.js refuses (with Shopify's
|
|
74
|
-
* `userErrors`), or the request's failure.
|
|
75
|
-
*/
|
|
76
|
-
async function run(name, input = {}) {
|
|
77
|
-
const { serverBase, serverFetch, query } = pagesContext();
|
|
78
|
-
if (serverBase()) {
|
|
79
|
-
const [method, path] = ROUTE[name](input);
|
|
80
|
-
const answer = await serverFetch(path, method === 'POST' ? { method, body: input } : {});
|
|
81
|
-
if (answer.kind === 'logged-out') throw new TetherError('Not logged in', { code: 'NOT_LOGGED_IN', status: 401 });
|
|
82
|
-
return answer.data;
|
|
83
|
-
}
|
|
84
|
-
const operation = ops[name];
|
|
85
|
-
if (!operation) throw new TetherError(`Customer accounts: no operation ${name}`);
|
|
86
|
-
try {
|
|
87
|
-
// account-ops.js's `Ask` reads every answer field by field, so the answer's shape needn't be stated here.
|
|
88
|
-
return await operation(
|
|
89
|
-
async (document, variables) => /** @type {Record<string, unknown> | null} */ (await query(document, variables)),
|
|
90
|
-
input,
|
|
91
|
-
);
|
|
92
|
-
} catch (error) {
|
|
93
|
-
if (!(error instanceof InputError)) throw error;
|
|
94
|
-
throw new TetherError(error.message, { code: 'INVALID', userErrors: error.userErrors });
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/** @param {ChangedData} what */
|
|
99
|
-
function changed(what) {
|
|
100
|
-
pagesContext().reset();
|
|
101
|
-
emit('account-updated', { what });
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* One of the customer's orders, in full (lines, totals, addresses, fulfillments, returns).
|
|
106
|
-
*
|
|
107
|
-
* @param {string | number} id A gid or the numeric order id.
|
|
108
|
-
* @returns {Promise<OrderDetail | null>} `null` when the customer has no such order.
|
|
109
|
-
*/
|
|
110
|
-
export async function order(id) {
|
|
111
|
-
const result = await run('order', { id });
|
|
112
|
-
return /** @type {OrderDetail | null} */ (result['order'] ?? null);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* @param {{ firstName?: string, lastName?: string }} [fields]
|
|
117
|
-
* @returns {Promise<{ firstName: string | null, lastName: string | null }>}
|
|
118
|
-
*/
|
|
119
|
-
export async function updateProfile({ firstName, lastName } = {}) {
|
|
120
|
-
const result = await run('profile', { firstName, lastName });
|
|
121
|
-
changed('profile');
|
|
122
|
-
return /** @type {{ firstName: string | null, lastName: string | null }} */ (result['customer']);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/** @returns {Promise<CustomerAddress[]>} The customer's addresses, the default one marked `isDefault`. */
|
|
126
|
-
export async function addresses() {
|
|
127
|
-
const result = await run('addresses');
|
|
128
|
-
return /** @type {CustomerAddress[]} */ (result['addresses']);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* Creates an address, or updates the one with `id`; without fields, only makes it the default.
|
|
133
|
-
*
|
|
134
|
-
* @param {AddressFields | null} address
|
|
135
|
-
* @param {{ id?: string | undefined, isDefault?: boolean }} [options]
|
|
136
|
-
* @returns {Promise<CustomerAddress>}
|
|
137
|
-
*/
|
|
138
|
-
export async function saveAddress(address, { id, isDefault } = {}) {
|
|
139
|
-
const result = await run('saveAddress', { id, address, isDefault });
|
|
140
|
-
changed('addresses');
|
|
141
|
-
return /** @type {CustomerAddress} */ (result['address']);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* @param {string} id
|
|
146
|
-
* @returns {Promise<string>} The deleted address's id.
|
|
147
|
-
*/
|
|
148
|
-
export async function deleteAddress(id) {
|
|
149
|
-
const result = await run('deleteAddress', { id });
|
|
150
|
-
changed('addresses');
|
|
151
|
-
return /** @type {string} */ (result['deleted']);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/** @returns {Promise<SubscriptionContract[]>} */
|
|
155
|
-
export async function subscriptions() {
|
|
156
|
-
const result = await run('subscriptions');
|
|
157
|
-
return /** @type {SubscriptionContract[]} */ (result['subscriptions']);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
/**
|
|
161
|
-
* @param {string | number} id
|
|
162
|
-
* @param {SubscriptionAction} action
|
|
163
|
-
* @returns {Promise<Record<string, unknown>>} The changed contract or billing cycle.
|
|
164
|
-
*/
|
|
165
|
-
export async function subscriptionAction(id, action) {
|
|
166
|
-
const result = await run('subscription', { id, action });
|
|
167
|
-
changed('subscriptions');
|
|
168
|
-
return result;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
/**
|
|
172
|
-
* @param {string | number} orderId
|
|
173
|
-
* @param {ReturnLine[]} lines
|
|
174
|
-
* @returns {Promise<{ id: string, status: string }>} The return Shopify opened.
|
|
175
|
-
*/
|
|
176
|
-
export async function requestReturn(orderId, lines) {
|
|
177
|
-
const result = await run('requestReturn', { orderId, lines });
|
|
178
|
-
changed('orders');
|
|
179
|
-
return /** @type {{ id: string, status: string }} */ (result['return']);
|
|
180
|
-
}
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The products of an order (`tether-order-lines`), for the order list and the order page alike. The order page's
|
|
3
|
-
* lines also carry price, total, sku and what can be returned; the order list's document has none of those, so they
|
|
4
|
-
* render empty there, and its rows get no return controls.
|
|
5
|
-
*/
|
|
6
|
-
import { formatMoney, get, qsa, repeat, sel, setImage, setText } from '../dom.js';
|
|
7
|
-
import { bindsIn, ownValue, showIn } from './markup.js';
|
|
8
|
-
/** @import { Money } from '../shopify-values.js' */
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* @typedef {object} OrderLine A line of `ORDERS_QUERY` or of the order page's document (account-ops.js).
|
|
12
|
-
* @property {string} [id] Only on the order page.
|
|
13
|
-
* @property {string} [name]
|
|
14
|
-
* @property {string} title
|
|
15
|
-
* @property {string | null} [variantTitle]
|
|
16
|
-
* @property {number} quantity
|
|
17
|
-
* @property {string | null} [sku]
|
|
18
|
-
* @property {{ url: string, altText: string | null } | null} [image]
|
|
19
|
-
* @property {Money | null} [price]
|
|
20
|
-
* @property {Money | null} [totalPrice]
|
|
21
|
-
* @property {{ nodes: { id: string, name: string }[] }} [suggestedReturnReasonDefinitions]
|
|
22
|
-
*/
|
|
23
|
-
/** @typedef {ReadonlyMap<string, number>} Returnable Line id → how many of it can still be returned. */
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* @param {OrderLine} line
|
|
27
|
-
* @param {number | null} returnable `null` in the order list, which knows nothing of returns.
|
|
28
|
-
* @returns {Record<string, string | undefined>} The text of each `tether-order-line` value.
|
|
29
|
-
*/
|
|
30
|
-
const lineTexts = (line, returnable) => ({
|
|
31
|
-
title: line.title || line.name,
|
|
32
|
-
'variant-title': line.variantTitle || '',
|
|
33
|
-
quantity: String(line.quantity),
|
|
34
|
-
price: formatMoney(line.price),
|
|
35
|
-
total: formatMoney(line.totalPrice),
|
|
36
|
-
sku: line.sku || '',
|
|
37
|
-
returnable: returnable === null ? undefined : String(returnable),
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* @param {HTMLElement} el
|
|
42
|
-
* @returns {el is HTMLInputElement}
|
|
43
|
-
*/
|
|
44
|
-
const isInput = (el) => el.tagName === 'INPUT';
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* The return controls of one line: how many can be returned, and Shopify's suggested reasons.
|
|
48
|
-
*
|
|
49
|
-
* @param {HTMLElement} row
|
|
50
|
-
* @param {OrderLine & { id: string }} line
|
|
51
|
-
* @param {number} returnable
|
|
52
|
-
*/
|
|
53
|
-
function fillReturnControls(row, line, returnable) {
|
|
54
|
-
showIn(row, 'order-line-show', { returnable: returnable > 0 });
|
|
55
|
-
qsa(row, sel('return-quantity')).forEach((input) => {
|
|
56
|
-
if (!isInput(input)) return;
|
|
57
|
-
input.max = String(returnable);
|
|
58
|
-
input.min = '0';
|
|
59
|
-
if (!input.value) input.value = '0';
|
|
60
|
-
input.disabled = returnable < 1;
|
|
61
|
-
});
|
|
62
|
-
qsa(row, `select${sel('return-reason')}`).forEach((select) => {
|
|
63
|
-
// A select keeps its options across renders of the same line: they are added once.
|
|
64
|
-
if (select.dataset['tetherFilled'] === line.id) return;
|
|
65
|
-
select.dataset['tetherFilled'] = line.id;
|
|
66
|
-
(line.suggestedReturnReasonDefinitions?.nodes ?? []).forEach((reason) => {
|
|
67
|
-
select.appendChild(new Option(reason.name, reason.id));
|
|
68
|
-
});
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Renders `lines` into a `tether-order-lines` repeater.
|
|
74
|
-
*
|
|
75
|
-
* @param {HTMLElement} box
|
|
76
|
-
* @param {readonly OrderLine[]} lines
|
|
77
|
-
* @param {Returnable | null} returns `null` for the order list.
|
|
78
|
-
*/
|
|
79
|
-
export function fillLines(box, lines, returns) {
|
|
80
|
-
repeat(box, lines, (row, line) => {
|
|
81
|
-
const id = line.id;
|
|
82
|
-
const returnable = returns && id ? (returns.get(id) ?? 0) : null;
|
|
83
|
-
if (id) row.dataset['tetherLine'] = id;
|
|
84
|
-
const texts = lineTexts(line, returnable);
|
|
85
|
-
bindsIn(row, 'order-line').forEach((el) => {
|
|
86
|
-
const key = get(el, 'order-line') ?? '';
|
|
87
|
-
if (key === 'image') {
|
|
88
|
-
if (line.image) setImage(el, line.image.url, line.image.altText || line.title);
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
const text = ownValue(texts, key);
|
|
92
|
-
if (text !== undefined) setText(el, text);
|
|
93
|
-
});
|
|
94
|
-
if (id && returnable !== null) fillReturnControls(row, { ...line, id }, returnable);
|
|
95
|
-
});
|
|
96
|
-
}
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The customer's recent orders (`tether-orders`), with `tether-show="orders-loading"` while they load and
|
|
3
|
-
* `orders-empty` when there are none yet.
|
|
4
|
-
*/
|
|
5
|
-
import { reportError } from '../config.js';
|
|
6
|
-
import { orderUrl, localDate } from '../account-links.js';
|
|
7
|
-
import { formatMoney, get, qsa, repeat, sel, setImage, setText } from '../dom.js';
|
|
8
|
-
import { ownValue, showIn } from './markup.js';
|
|
9
|
-
import { fillLines } from './order-lines.js';
|
|
10
|
-
/** @import { Money } from '../shopify-values.js' */
|
|
11
|
-
/** @import { OrderLine } from './order-lines.js' */
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* @typedef {object} AccountOrder A node of `ORDERS_QUERY` (account-ops.js).
|
|
15
|
-
* @property {string} id
|
|
16
|
-
* @property {string} name
|
|
17
|
-
* @property {string} processedAt
|
|
18
|
-
* @property {string | null} financialStatus
|
|
19
|
-
* @property {string | null} fulfillmentStatus
|
|
20
|
-
* @property {string | null} statusPageUrl
|
|
21
|
-
* @property {Money} totalPrice
|
|
22
|
-
* @property {{ nodes: OrderLine[] }} lineItems
|
|
23
|
-
*/
|
|
24
|
-
|
|
25
|
-
/** How many orders a `tether-orders` list shows without `tether-limit`. */
|
|
26
|
-
const DEFAULT_ORDER_COUNT = 10;
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* @param {AccountOrder} order
|
|
30
|
-
* @returns {Record<string, string | undefined>} The text of each `tether-order-bind` value.
|
|
31
|
-
*/
|
|
32
|
-
const orderTexts = (order) => ({
|
|
33
|
-
name: order.name,
|
|
34
|
-
date: localDate(order.processedAt),
|
|
35
|
-
total: formatMoney(order.totalPrice),
|
|
36
|
-
status: order.financialStatus?.toLowerCase(),
|
|
37
|
-
fulfillment: order.fulfillmentStatus?.toLowerCase(),
|
|
38
|
-
items: order.lineItems.nodes.map((line) => `${line.quantity}× ${line.title}`).join(', '),
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* @param {HTMLElement} node A copy of the list's template.
|
|
43
|
-
* @param {AccountOrder} order
|
|
44
|
-
*/
|
|
45
|
-
function fillOrder(node, order) {
|
|
46
|
-
const lines = order.lineItems.nodes;
|
|
47
|
-
const url = orderUrl(order);
|
|
48
|
-
const texts = orderTexts(order);
|
|
49
|
-
qsa(node, sel('order-bind')).forEach((el) => {
|
|
50
|
-
const key = get(el, 'order-bind') ?? '';
|
|
51
|
-
if (key === 'url') {
|
|
52
|
-
if (url) el.setAttribute('href', url);
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
if (key === 'image') {
|
|
56
|
-
const image = lines.find((line) => line.image)?.image;
|
|
57
|
-
if (image) setImage(el, image.url, image.altText || lines[0]?.title || '');
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
const text = ownValue(texts, key);
|
|
61
|
-
if (text !== undefined) setText(el, text);
|
|
62
|
-
});
|
|
63
|
-
qsa(node, sel('order-lines')).forEach((box) => {
|
|
64
|
-
fillLines(box, lines, null);
|
|
65
|
-
});
|
|
66
|
-
if (get(node, 'order-bind') === 'url' && url) node.setAttribute('href', url);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Fills every `tether-orders` list in `root`. A failure is reported and leaves the lists empty, without claiming the
|
|
71
|
-
* customer has no orders.
|
|
72
|
-
*
|
|
73
|
-
* @param {ParentNode} root
|
|
74
|
-
* @param {(first: number) => Promise<AccountOrder[]>} loadOrders
|
|
75
|
-
*/
|
|
76
|
-
export async function renderOrderList(root, loadOrders) {
|
|
77
|
-
const lists = qsa(root, sel('orders'));
|
|
78
|
-
const first = lists[0];
|
|
79
|
-
if (!first) return;
|
|
80
|
-
showIn(root, 'show', { 'orders-loading': true, 'orders-empty': false });
|
|
81
|
-
const limit = Number.parseInt(get(first, 'limit') || String(DEFAULT_ORDER_COUNT), 10) || DEFAULT_ORDER_COUNT;
|
|
82
|
-
/** @type {AccountOrder[] | null} `null` after a failure. */
|
|
83
|
-
const orders = await loadOrders(limit).catch((/** @type {unknown} */ error) => {
|
|
84
|
-
reportError(error, 'orders');
|
|
85
|
-
return null;
|
|
86
|
-
});
|
|
87
|
-
showIn(root, 'show', { 'orders-loading': false, 'orders-empty': orders?.length === 0 });
|
|
88
|
-
lists.forEach((list) => {
|
|
89
|
-
repeat(list, orders ?? [], fillOrder);
|
|
90
|
-
});
|
|
91
|
-
}
|
|
@@ -1,192 +0,0 @@
|
|
|
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.js';
|
|
7
|
-
import { isHttpsUrl, localDate } from '../account-links.js';
|
|
8
|
-
import { attr, formatMoney, get, qsa, repeat, sel, setText, show } from '../dom.js';
|
|
9
|
-
import { addressText, bindsIn, enumText, ownValue, showIn } from './markup.js';
|
|
10
|
-
import { fillLines } from './order-lines.js';
|
|
11
|
-
import { order } from './operations.js';
|
|
12
|
-
/** @import { Money } from '../shopify-values.js' */
|
|
13
|
-
/** @import { OrderLine } from './order-lines.js' */
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* @typedef {object} Fulfillment
|
|
17
|
-
* @property {string} status
|
|
18
|
-
* @property {string} createdAt
|
|
19
|
-
* @property {string | null} estimatedDeliveryAt
|
|
20
|
-
* @property {string | null} latestShipmentStatus
|
|
21
|
-
* @property {{ company: string | null, number: string | null, url: string | null }[]} trackingInformation
|
|
22
|
-
*/
|
|
23
|
-
/**
|
|
24
|
-
* @typedef {object} OrderDetail The order page's document in account-ops.js.
|
|
25
|
-
* @property {string} id
|
|
26
|
-
* @property {string} name
|
|
27
|
-
* @property {string} processedAt
|
|
28
|
-
* @property {string | null} cancelledAt
|
|
29
|
-
* @property {string | null} financialStatus
|
|
30
|
-
* @property {string | null} fulfillmentStatus
|
|
31
|
-
* @property {string | null} statusPageUrl
|
|
32
|
-
* @property {Money} totalPrice
|
|
33
|
-
* @property {Money | null} subtotal
|
|
34
|
-
* @property {Money | null} totalShipping
|
|
35
|
-
* @property {Money | null} totalTax
|
|
36
|
-
* @property {{ formatted: string[] } | null} shippingAddress
|
|
37
|
-
* @property {{ formatted: string[] } | null} billingAddress
|
|
38
|
-
* @property {{ nodes: (OrderLine & { id: string })[] }} lineItems
|
|
39
|
-
* @property {{ nodes: Fulfillment[] }} fulfillments
|
|
40
|
-
* @property {{ returnableLineItems: { nodes: { quantity: number, lineItem: { id: string } | null }[] } } | null}
|
|
41
|
-
* returnInformation
|
|
42
|
-
* @property {{ nodes: { id: string, status: string }[] }} returns
|
|
43
|
-
*/
|
|
44
|
-
/** @typedef {'loading' | 'ready' | 'not-found' | 'error'} OrderState */
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* @param {HTMLElement} el
|
|
48
|
-
* @returns {string | null} The order `tether-order` names: an id, or `url:<param>` for one in the page's URL.
|
|
49
|
-
*/
|
|
50
|
-
function orderKey(el) {
|
|
51
|
-
const raw = get(el, 'order') || '';
|
|
52
|
-
if (!raw.startsWith('url:')) return raw;
|
|
53
|
-
return new URL(window.location.href).searchParams.get(raw.slice('url:'.length) || 'id');
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Every state but `ready`, which shows with the order.
|
|
58
|
-
*
|
|
59
|
-
* @param {HTMLElement} el
|
|
60
|
-
* @param {Exclude<OrderState, 'ready'>} state
|
|
61
|
-
*/
|
|
62
|
-
export function showOrderState(el, state) {
|
|
63
|
-
el.setAttribute(attr('order-state'), state);
|
|
64
|
-
showIn(el, 'order-show', {
|
|
65
|
-
loading: state === 'loading',
|
|
66
|
-
ready: false,
|
|
67
|
-
'not-found': state === 'not-found',
|
|
68
|
-
error: state === 'error',
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* @param {OrderDetail} detail
|
|
74
|
-
* @returns {Record<string, string>} The text of each `tether-order-bind` value.
|
|
75
|
-
*/
|
|
76
|
-
const orderTexts = (detail) => ({
|
|
77
|
-
name: detail.name,
|
|
78
|
-
date: localDate(detail.processedAt),
|
|
79
|
-
total: formatMoney(detail.totalPrice),
|
|
80
|
-
subtotal: formatMoney(detail.subtotal),
|
|
81
|
-
shipping: formatMoney(detail.totalShipping),
|
|
82
|
-
tax: formatMoney(detail.totalTax),
|
|
83
|
-
status: enumText(detail.financialStatus),
|
|
84
|
-
fulfillment: enumText(detail.fulfillmentStatus),
|
|
85
|
-
'shipping-address': addressText(detail.shippingAddress),
|
|
86
|
-
'billing-address': addressText(detail.billingAddress),
|
|
87
|
-
items: detail.lineItems.nodes.map((line) => `${line.quantity}× ${line.title || line.name}`).join(', '),
|
|
88
|
-
cancelled: detail.cancelledAt ? localDate(detail.cancelledAt) : '',
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* One shipment row: its status and dates, and its tracking link, hidden without an https one.
|
|
93
|
-
*
|
|
94
|
-
* @param {HTMLElement} row
|
|
95
|
-
* @param {Fulfillment} fulfillment
|
|
96
|
-
*/
|
|
97
|
-
function fillFulfillment(row, fulfillment) {
|
|
98
|
-
const tracking = fulfillment.trackingInformation[0];
|
|
99
|
-
/** @type {Record<string, string>} */
|
|
100
|
-
const texts = {
|
|
101
|
-
status: enumText(fulfillment.latestShipmentStatus || fulfillment.status),
|
|
102
|
-
date: localDate(fulfillment.createdAt),
|
|
103
|
-
eta: fulfillment.estimatedDeliveryAt ? localDate(fulfillment.estimatedDeliveryAt) : '',
|
|
104
|
-
company: tracking?.company || '',
|
|
105
|
-
number: tracking?.number || '',
|
|
106
|
-
};
|
|
107
|
-
bindsIn(row, 'fulfillment-bind').forEach((el) => {
|
|
108
|
-
const key = get(el, 'fulfillment-bind') ?? '';
|
|
109
|
-
if (key === 'url') {
|
|
110
|
-
const url = tracking?.url;
|
|
111
|
-
const linked = isHttpsUrl(url);
|
|
112
|
-
if (linked && url) el.setAttribute('href', url);
|
|
113
|
-
show(el, linked);
|
|
114
|
-
return;
|
|
115
|
-
}
|
|
116
|
-
const text = ownValue(texts, key);
|
|
117
|
-
if (text !== undefined) setText(el, text);
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* @param {HTMLElement} el
|
|
123
|
-
* @param {OrderDetail} detail
|
|
124
|
-
*/
|
|
125
|
-
function fillOrder(el, detail) {
|
|
126
|
-
const returnable = new Map(
|
|
127
|
-
(detail.returnInformation?.returnableLineItems.nodes ?? []).map((entry) => [
|
|
128
|
-
entry.lineItem?.id ?? '',
|
|
129
|
-
entry.quantity,
|
|
130
|
-
]),
|
|
131
|
-
);
|
|
132
|
-
const shipments = detail.fulfillments.nodes;
|
|
133
|
-
const texts = orderTexts(detail);
|
|
134
|
-
qsa(el, sel('order-bind')).forEach((bind) => {
|
|
135
|
-
const key = get(bind, 'order-bind') ?? '';
|
|
136
|
-
if (key === 'url') {
|
|
137
|
-
if (detail.statusPageUrl && isHttpsUrl(detail.statusPageUrl)) bind.setAttribute('href', detail.statusPageUrl);
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
140
|
-
const text = ownValue(texts, key);
|
|
141
|
-
if (text !== undefined) setText(bind, text);
|
|
142
|
-
});
|
|
143
|
-
qsa(el, sel('order-lines')).forEach((box) => {
|
|
144
|
-
fillLines(box, detail.lineItems.nodes, returnable);
|
|
145
|
-
});
|
|
146
|
-
qsa(el, sel('order-fulfillments')).forEach((box) => {
|
|
147
|
-
repeat(box, shipments, fillFulfillment);
|
|
148
|
-
});
|
|
149
|
-
const returnRequested = el.dataset['tetherReturned'] === '1';
|
|
150
|
-
showIn(el, 'order-show', {
|
|
151
|
-
loading: false,
|
|
152
|
-
ready: true,
|
|
153
|
-
error: false,
|
|
154
|
-
'not-found': false,
|
|
155
|
-
cancelled: Boolean(detail.cancelledAt),
|
|
156
|
-
shipped: shipments.length > 0,
|
|
157
|
-
returnable: [...returnable.values()].some((quantity) => quantity > 0),
|
|
158
|
-
'has-returns': !returnRequested && detail.returns.nodes.length > 0,
|
|
159
|
-
'return-requested': returnRequested,
|
|
160
|
-
});
|
|
161
|
-
el.setAttribute(attr('order-state'), 'ready');
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* Loads and renders the order `el` names. A failure is reported and shows the error state.
|
|
166
|
-
*
|
|
167
|
-
* @param {HTMLElement} el
|
|
168
|
-
*/
|
|
169
|
-
export async function renderOrder(el) {
|
|
170
|
-
const key = orderKey(el);
|
|
171
|
-
if (!key) {
|
|
172
|
-
el.setAttribute(attr('order-state'), 'not-found');
|
|
173
|
-
showIn(el, 'order-show', { 'not-found': true });
|
|
174
|
-
return;
|
|
175
|
-
}
|
|
176
|
-
showOrderState(el, 'loading');
|
|
177
|
-
/** @type {OrderDetail | null} */
|
|
178
|
-
let detail;
|
|
179
|
-
try {
|
|
180
|
-
detail = await order(key);
|
|
181
|
-
} catch (error) {
|
|
182
|
-
reportError(error, 'order');
|
|
183
|
-
showOrderState(el, 'error');
|
|
184
|
-
return;
|
|
185
|
-
}
|
|
186
|
-
if (!detail) {
|
|
187
|
-
showOrderState(el, 'not-found');
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
el.dataset['tetherOrderId'] = detail.id;
|
|
191
|
-
fillOrder(el, detail);
|
|
192
|
-
}
|