@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.
Files changed (215) hide show
  1. package/README.md +28 -28
  2. package/dist/esm/astro.js +2 -2
  3. package/dist/esm/chunks/account-CXMA5RPZ.js +1 -0
  4. package/dist/esm/chunks/account-pages-IZO7FEOZ.js +1 -0
  5. package/dist/esm/chunks/analytics-PG5FPRDN.js +1 -0
  6. package/dist/esm/chunks/chunk-4HD4J5JC.js +1 -0
  7. package/dist/esm/chunks/chunk-6TFMLT4A.js +4 -0
  8. package/dist/esm/chunks/chunk-BBVUVPMO.js +1 -0
  9. package/dist/esm/chunks/chunk-E5YO6274.js +1 -0
  10. package/dist/esm/chunks/chunk-EW5SYO6L.js +1 -0
  11. package/dist/esm/chunks/chunk-HJY2JSNB.js +1 -0
  12. package/dist/esm/chunks/chunk-MQYB6BH4.js +1 -0
  13. package/dist/esm/chunks/chunk-N3V45JPS.js +1 -0
  14. package/dist/esm/chunks/chunk-RPMBCAGS.js +1 -0
  15. package/dist/esm/chunks/chunk-VFNHIGCS.js +1 -0
  16. package/dist/esm/chunks/chunk-WT7RGGJU.js +1 -0
  17. package/dist/esm/chunks/chunk-YN6LEBEN.js +1 -0
  18. package/dist/esm/chunks/chunk-Z6HS3H7K.js +1 -0
  19. package/dist/esm/chunks/diagnose-XCEBQKBO.js +14 -0
  20. package/dist/esm/chunks/extras-4HU3VSBQ.js +1 -0
  21. package/dist/esm/chunks/markets-ui-M4IQ62CT.js +1 -0
  22. package/dist/esm/chunks/search-A5DS2IOS.js +1 -0
  23. package/dist/esm/index.js +1 -1
  24. package/dist/esm/server.js +2 -2
  25. package/dist/presets/barba.esm.js +19 -23
  26. package/dist/tether.account-pages.js +332 -367
  27. package/dist/tether.account-pages.min.js +1 -1
  28. package/dist/tether.account.js +253 -492
  29. package/dist/tether.account.min.js +1 -1
  30. package/dist/tether.analytics.js +82 -89
  31. package/dist/tether.analytics.min.js +1 -1
  32. package/dist/tether.diagnose.js +52 -34
  33. package/dist/tether.diagnose.min.js +2 -2
  34. package/dist/tether.extras.js +42 -37
  35. package/dist/tether.extras.min.js +1 -1
  36. package/dist/tether.js +1361 -935
  37. package/dist/tether.markets.js +89 -91
  38. package/dist/tether.markets.min.js +1 -1
  39. package/dist/tether.min.js +0 -0
  40. package/dist/tether.min.js.map +4 -4
  41. package/dist/tether.search.js +60 -69
  42. package/dist/tether.search.min.js +1 -1
  43. package/dist/types/server/src/shared/account-orders.d.ts +44 -0
  44. package/dist/types/server/src/shared/account-routes.d.ts +153 -0
  45. package/dist/types/server/src/shared/api-versions.d.ts +13 -0
  46. package/dist/types/server/src/shared/cart-id.d.ts +2 -0
  47. package/dist/types/server/src/shared/error-message.d.ts +2 -0
  48. package/dist/types/server/src/shared/json.d.ts +25 -0
  49. package/dist/types/server/src/shared/market-codes.d.ts +16 -0
  50. package/dist/types/src/account/addresses-view.d.ts +5 -0
  51. package/dist/types/src/account/answers.d.ts +40 -0
  52. package/dist/types/src/account/markup.d.ts +19 -0
  53. package/dist/types/src/account/operations.d.ts +41 -0
  54. package/dist/types/src/account/order-lines.d.ts +13 -0
  55. package/dist/types/src/account/order-list.d.ts +12 -0
  56. package/dist/types/src/account/order-page.d.ts +6 -0
  57. package/dist/types/src/account/page-answers.d.ts +182 -0
  58. package/dist/types/src/account/page-events.d.ts +2 -0
  59. package/dist/types/src/account/server-client.d.ts +60 -0
  60. package/dist/types/src/account/subscriptions-view.d.ts +2 -0
  61. package/dist/types/src/account-api.d.ts +18 -0
  62. package/dist/types/src/account-marks.d.ts +15 -0
  63. package/dist/types/src/account-pages.d.ts +8 -0
  64. package/dist/types/src/account.d.ts +64 -0
  65. package/dist/types/src/amounts.d.ts +12 -0
  66. package/dist/types/src/analytics-shopify.d.ts +41 -0
  67. package/dist/types/src/analytics.d.ts +2 -0
  68. package/dist/types/src/api-version.d.ts +28 -0
  69. package/dist/types/src/astro.d.ts +42 -0
  70. package/dist/types/src/cart/cart-shape.d.ts +43 -0
  71. package/dist/types/src/cart/checkout.d.ts +16 -0
  72. package/dist/types/src/cart/drawer.d.ts +18 -0
  73. package/dist/types/src/cart/mutations.d.ts +85 -0
  74. package/dist/types/src/cart/render.d.ts +17 -0
  75. package/dist/types/src/cart/store.d.ts +185 -0
  76. package/dist/types/src/cart.d.ts +106 -0
  77. package/dist/types/src/client.d.ts +134 -0
  78. package/dist/types/src/collection.d.ts +25 -0
  79. package/dist/types/src/config-sections.d.ts +40 -0
  80. package/dist/types/src/config.d.ts +190 -0
  81. package/dist/types/src/controls/cart-controls.d.ts +2 -0
  82. package/dist/types/src/controls/discount.d.ts +2 -0
  83. package/dist/types/src/controls/extras.d.ts +5 -0
  84. package/dist/types/src/controls/navigation.d.ts +12 -0
  85. package/dist/types/src/diagnose-markup.d.ts +19 -0
  86. package/dist/types/src/diagnose.d.ts +60 -0
  87. package/dist/types/src/dom.d.ts +75 -0
  88. package/dist/types/src/extras.d.ts +21 -0
  89. package/dist/types/src/fragments.d.ts +158 -0
  90. package/dist/types/src/globals.d.ts +15 -0
  91. package/dist/types/src/images.d.ts +20 -0
  92. package/dist/types/src/index.d.ts +167 -0
  93. package/dist/types/src/initial.d.ts +56 -0
  94. package/dist/types/src/lazy.d.ts +28 -0
  95. package/dist/types/src/loader.d.ts +84 -0
  96. package/dist/types/src/market-codes.d.ts +6 -0
  97. package/dist/types/src/markets-ui.d.ts +32 -0
  98. package/dist/types/src/markets.d.ts +72 -0
  99. package/dist/types/src/markup.d.ts +131 -0
  100. package/dist/types/src/mounting.d.ts +46 -0
  101. package/dist/types/src/page-market.d.ts +27 -0
  102. package/dist/types/src/presets/barba.d.ts +56 -0
  103. package/dist/types/src/product-events.d.ts +2 -0
  104. package/dist/types/src/product-render.d.ts +17 -0
  105. package/dist/types/src/product-shape.d.ts +90 -0
  106. package/dist/types/src/product-state.d.ts +110 -0
  107. package/dist/types/src/product.d.ts +66 -0
  108. package/dist/types/src/repeaters.d.ts +16 -0
  109. package/dist/types/src/search.d.ts +33 -0
  110. package/dist/types/src/selling-plans.d.ts +64 -0
  111. package/dist/types/src/server.d.ts +88 -0
  112. package/dist/types/src/shopify-values.d.ts +46 -0
  113. package/dist/types/src/status.d.ts +33 -0
  114. package/dist/types/src/storage.d.ts +12 -0
  115. package/dist/types/src/store-html.d.ts +39 -0
  116. package/dist/types/src/storefront/catalog.d.ts +38 -0
  117. package/dist/types/src/storefront/json-ld.d.ts +20 -0
  118. package/dist/types/src/variant-render.d.ts +14 -0
  119. package/package.json +24 -24
  120. package/dist/esm/chunks/account-C3NP6HFB.js +0 -1
  121. package/dist/esm/chunks/account-pages-WLK374FO.js +0 -1
  122. package/dist/esm/chunks/analytics-G3SAWMNL.js +0 -1
  123. package/dist/esm/chunks/chunk-27PJJZGC.js +0 -1
  124. package/dist/esm/chunks/chunk-2FSXS55U.js +0 -1
  125. package/dist/esm/chunks/chunk-FCSWQDVV.js +0 -1
  126. package/dist/esm/chunks/chunk-FOMSMIAM.js +0 -1
  127. package/dist/esm/chunks/chunk-GZJTCLLW.js +0 -1
  128. package/dist/esm/chunks/chunk-HEQILZVR.js +0 -1
  129. package/dist/esm/chunks/chunk-JOOEXYF3.js +0 -4
  130. package/dist/esm/chunks/chunk-MPTQYLSM.js +0 -1
  131. package/dist/esm/chunks/chunk-NEHZ37TU.js +0 -3
  132. package/dist/esm/chunks/chunk-V74KGEJF.js +0 -1
  133. package/dist/esm/chunks/chunk-VT4CDUJJ.js +0 -1
  134. package/dist/esm/chunks/chunk-YJLWX2HK.js +0 -1
  135. package/dist/esm/chunks/diagnose-6VVGZNTY.js +0 -14
  136. package/dist/esm/chunks/extras-GJVRDFM3.js +0 -1
  137. package/dist/esm/chunks/markets-ui-UNB4PTA7.js +0 -1
  138. package/dist/esm/chunks/search-OU75RNQL.js +0 -1
  139. package/src/account/addresses-view.js +0 -92
  140. package/src/account/browser-session.js +0 -359
  141. package/src/account/markup.js +0 -48
  142. package/src/account/operations.js +0 -180
  143. package/src/account/order-lines.js +0 -96
  144. package/src/account/order-list.js +0 -91
  145. package/src/account/order-page.js +0 -192
  146. package/src/account/page-events.js +0 -288
  147. package/src/account/server-client.js +0 -172
  148. package/src/account/shared.js +0 -111
  149. package/src/account/shopify-auth.js +0 -150
  150. package/src/account/subscriptions-view.js +0 -158
  151. package/src/account-api.js +0 -118
  152. package/src/account-links.js +0 -35
  153. package/src/account-pages.js +0 -89
  154. package/src/account.js +0 -342
  155. package/src/amounts.js +0 -36
  156. package/src/analytics-shopify.js +0 -241
  157. package/src/analytics.js +0 -352
  158. package/src/api-version.js +0 -85
  159. package/src/astro.js +0 -266
  160. package/src/cart/checkout.js +0 -97
  161. package/src/cart/drawer.js +0 -138
  162. package/src/cart/mutations.js +0 -234
  163. package/src/cart/render.js +0 -294
  164. package/src/cart/store.js +0 -326
  165. package/src/cart.js +0 -391
  166. package/src/client.js +0 -267
  167. package/src/collection.js +0 -183
  168. package/src/config.js +0 -349
  169. package/src/controls/cart-controls.js +0 -155
  170. package/src/controls/discount.js +0 -96
  171. package/src/controls/extras.js +0 -63
  172. package/src/controls/navigation.js +0 -83
  173. package/src/diagnose-markup.js +0 -333
  174. package/src/diagnose.js +0 -295
  175. package/src/dom.js +0 -287
  176. package/src/extras.js +0 -145
  177. package/src/fragments.js +0 -237
  178. package/src/images.js +0 -104
  179. package/src/index.js +0 -353
  180. package/src/initial.js +0 -87
  181. package/src/lazy-iife.js +0 -116
  182. package/src/lazy.js +0 -39
  183. package/src/loader.js +0 -337
  184. package/src/market-codes.js +0 -35
  185. package/src/markets-ui.js +0 -204
  186. package/src/markets.js +0 -334
  187. package/src/markup.js +0 -64
  188. package/src/mounting.js +0 -277
  189. package/src/page-market.js +0 -116
  190. package/src/parts/account-pages.js +0 -6
  191. package/src/parts/account.js +0 -6
  192. package/src/parts/analytics.js +0 -6
  193. package/src/parts/diagnose.js +0 -6
  194. package/src/parts/extras.js +0 -6
  195. package/src/parts/markets.js +0 -6
  196. package/src/parts/search.js +0 -6
  197. package/src/presets/barba.js +0 -190
  198. package/src/product-events.js +0 -130
  199. package/src/product-render.js +0 -257
  200. package/src/product-state.js +0 -318
  201. package/src/product.js +0 -344
  202. package/src/repeaters.js +0 -173
  203. package/src/search.js +0 -323
  204. package/src/selling-plans.js +0 -154
  205. package/src/server.js +0 -215
  206. package/src/shopify-values.js +0 -89
  207. package/src/status.js +0 -126
  208. package/src/store-html.js +0 -0
  209. package/src/storefront/catalog.js +0 -199
  210. package/src/storefront/json-ld.js +0 -167
  211. package/src/variant-render.js +0 -165
  212. package/types/astro.d.ts +0 -28
  213. package/types/barba.d.ts +0 -16
  214. package/types/index.d.ts +0 -577
  215. 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
- }