@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,279 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The cart on this page: what Shopify last said about it (`CartStatus`), the quantities quick clicks asked for and
|
|
3
|
+
* the one queue every change goes through. A change of what the page shows goes through
|
|
4
|
+
* here, so the document (`renderCart`) and the listeners (`tether:cart-updated`) always see the store's cart.
|
|
5
|
+
*/
|
|
6
|
+
import { cfg } from '../config.ts';
|
|
7
|
+
import { emit } from '../dom.ts';
|
|
8
|
+
import { isOpen } from './drawer.ts';
|
|
9
|
+
import { renderCart, quantityOf, showSavedCount } from './render.ts';
|
|
10
|
+
import { getStored, removeStored, setStored } from '../storage.ts';
|
|
11
|
+
import type { CartWarning } from '../config.ts';
|
|
12
|
+
import type { Image } from '../fragments.ts';
|
|
13
|
+
import type { Money } from '../shopify-values.ts';
|
|
14
|
+
|
|
15
|
+
export interface Attribute {
|
|
16
|
+
key: string;
|
|
17
|
+
value: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** A line's `merchandise`: Shopify's `Merchandise` union has only `ProductVariant`. */
|
|
21
|
+
export interface CartVariant {
|
|
22
|
+
id: string;
|
|
23
|
+
title: string;
|
|
24
|
+
sku: string | null;
|
|
25
|
+
availableForSale: boolean;
|
|
26
|
+
requiresShipping: boolean;
|
|
27
|
+
/** Absent when the store's token can't read inventory. */
|
|
28
|
+
quantityAvailable?: number | null;
|
|
29
|
+
image: Image | null;
|
|
30
|
+
price: Money;
|
|
31
|
+
compareAtPrice: Money | null;
|
|
32
|
+
selectedOptions: { name: string; value: string }[];
|
|
33
|
+
product: { id: string; title: string; handle: string; vendor: string; featuredImage: Image | null };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface LinePlanAllocation {
|
|
37
|
+
sellingPlan: {
|
|
38
|
+
id: string;
|
|
39
|
+
name: string;
|
|
40
|
+
description: string | null;
|
|
41
|
+
recurringDeliveries: boolean;
|
|
42
|
+
options: { name: string; value: string }[];
|
|
43
|
+
};
|
|
44
|
+
checkoutChargeAmount: Money;
|
|
45
|
+
priceAdjustments: { price: Money; compareAtPrice: Money; perDeliveryPrice: Money }[];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface ShopifyCartLine {
|
|
49
|
+
id: string;
|
|
50
|
+
quantity: number;
|
|
51
|
+
attributes: Attribute[];
|
|
52
|
+
cost: {
|
|
53
|
+
totalAmount: Money;
|
|
54
|
+
subtotalAmount: Money;
|
|
55
|
+
amountPerQuantity: Money;
|
|
56
|
+
compareAtAmountPerQuantity: Money | null;
|
|
57
|
+
};
|
|
58
|
+
discountAllocations: { discountedAmount: Money }[];
|
|
59
|
+
sellingPlanAllocation: LinePlanAllocation | null;
|
|
60
|
+
merchandise: CartVariant;
|
|
61
|
+
/** Only on a bundle (`ComponentizableCartLine`). */
|
|
62
|
+
lineComponents?: {
|
|
63
|
+
id: string;
|
|
64
|
+
quantity: number;
|
|
65
|
+
merchandise: { id: string; title: string; product: { title: string; handle: string } };
|
|
66
|
+
}[];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** The fields of `fragment TetherCart`. */
|
|
70
|
+
export interface ShopifyCart {
|
|
71
|
+
id: string;
|
|
72
|
+
checkoutUrl: string;
|
|
73
|
+
totalQuantity: number;
|
|
74
|
+
note: string | null;
|
|
75
|
+
createdAt: string;
|
|
76
|
+
updatedAt: string;
|
|
77
|
+
attributes: Attribute[];
|
|
78
|
+
buyerIdentity: { countryCode: string | null; email: string | null };
|
|
79
|
+
discountCodes: { code: string; applicable: boolean }[];
|
|
80
|
+
cost: { subtotalAmount: Money; totalAmount: Money; checkoutChargeAmount: Money };
|
|
81
|
+
lines: { nodes: ShopifyCartLine[] };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* `unknown` until the saved cart has loaded (the page's own markup shows); `empty` without a cart (none saved, or
|
|
86
|
+
* Shopify no longer has it); `error` after a failed load or change, with the cart as Shopify last sent it.
|
|
87
|
+
*/
|
|
88
|
+
export type CartStatus =
|
|
89
|
+
| { kind: 'unknown' }
|
|
90
|
+
| { kind: 'empty' }
|
|
91
|
+
| { kind: 'ready'; cart: ShopifyCart }
|
|
92
|
+
| { kind: 'error'; error: unknown; cart: ShopifyCart | null };
|
|
93
|
+
|
|
94
|
+
/** What one quick +/- click asked for; a newer click replaces it. */
|
|
95
|
+
export interface PendingQuantity {
|
|
96
|
+
quantity: number;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** The second argument of a `subscribe` listener. */
|
|
100
|
+
interface CartMeta {
|
|
101
|
+
open: boolean;
|
|
102
|
+
count: number;
|
|
103
|
+
loaded: boolean;
|
|
104
|
+
/** A copy of Shopify's notes on the last change. */
|
|
105
|
+
warnings: CartWarning[];
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
let status: CartStatus = { kind: 'unknown' };
|
|
109
|
+
|
|
110
|
+
/** Shopify's notes on the last change. */
|
|
111
|
+
let warnings: readonly CartWarning[] = [];
|
|
112
|
+
|
|
113
|
+
/** Line id → what quick +/- clicks asked for, until Shopify has answered. */
|
|
114
|
+
export const pendingQuantities = new Map<string, PendingQuantity>();
|
|
115
|
+
|
|
116
|
+
export const cartStatus = (): CartStatus => status;
|
|
117
|
+
|
|
118
|
+
/** The cart as Shopify last sent it. */
|
|
119
|
+
export function currentCart(): ShopifyCart | null {
|
|
120
|
+
switch (status.kind) {
|
|
121
|
+
case 'ready':
|
|
122
|
+
case 'error':
|
|
123
|
+
return status.cart;
|
|
124
|
+
case 'unknown':
|
|
125
|
+
case 'empty':
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export const currentWarnings = (): readonly CartWarning[] => warnings;
|
|
131
|
+
|
|
132
|
+
const statusOf = (cart: ShopifyCart | null): CartStatus => (cart ? { kind: 'ready', cart } : { kind: 'empty' });
|
|
133
|
+
|
|
134
|
+
const seenKey = () => `${cfg.storageKey}:seen`;
|
|
135
|
+
|
|
136
|
+
/** The id of the visitor's cart, kept across pages. */
|
|
137
|
+
export const savedCartId = (): string | null => getStored(cfg.storageKey) || null;
|
|
138
|
+
|
|
139
|
+
/** Forgets the saved cart, which Shopify no longer has. */
|
|
140
|
+
export function forgetSavedCart(): void {
|
|
141
|
+
removeStored(cfg.storageKey);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** Keeps a cart Shopify sent without showing it, for an answer a newer change will replace. */
|
|
145
|
+
export function keepQuietly(cart: ShopifyCart | null): void {
|
|
146
|
+
status = statusOf(cart);
|
|
147
|
+
if (cart) setStored(cfg.storageKey, cart.id);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** Renders the document with the store's cart. */
|
|
151
|
+
export function render(): void {
|
|
152
|
+
renderCart({
|
|
153
|
+
cart: currentCart(),
|
|
154
|
+
warnings,
|
|
155
|
+
pending: pendingQuantities,
|
|
156
|
+
failed: status.kind === 'error',
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** @param reason The `reason` of `tether:cart-updated`. */
|
|
161
|
+
export function announce(reason: string): void {
|
|
162
|
+
render();
|
|
163
|
+
emit('cart-updated', { cart: currentCart(), reason });
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Keeps a cart Shopify sent after a change, shows it, and tells this page and the visitor's other tabs.
|
|
168
|
+
*
|
|
169
|
+
* @returns `cart`.
|
|
170
|
+
*/
|
|
171
|
+
export function commit(cart: ShopifyCart | null, reason: string): ShopifyCart | null {
|
|
172
|
+
keepQuietly(cart);
|
|
173
|
+
// Other tabs reload the cart on this storage event; the next page shows the count before its cart has loaded.
|
|
174
|
+
setStored(seenKey(), `${Date.now()}|${cart?.totalQuantity ?? 0}`);
|
|
175
|
+
announce(reason);
|
|
176
|
+
return cart;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** The outcome of loading the saved cart: the cart, `null` when there is none, or the failure. */
|
|
180
|
+
export function settleLoad(
|
|
181
|
+
outcome: { kind: 'loaded'; cart: ShopifyCart | null } | { kind: 'failed'; error: unknown },
|
|
182
|
+
): void {
|
|
183
|
+
status = outcome.kind === 'loaded' ? statusOf(outcome.cart) : { kind: 'error', error: outcome.error, cart: null };
|
|
184
|
+
announce('load');
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* The cart's count on the last page, from `<storageKey>:seen` (`<time>|<count>`).
|
|
189
|
+
*
|
|
190
|
+
* @returns `null` unless the count is a whole number: other scripts and other Tether versions write storage too.
|
|
191
|
+
*/
|
|
192
|
+
function lastKnownCount(): number | null {
|
|
193
|
+
const text = getStored(seenKey())?.split('|')[1] ?? '';
|
|
194
|
+
const count = Number(text);
|
|
195
|
+
return /^\d+$/.test(text) && Number.isSafeInteger(count) ? count : null;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** Until the saved cart has loaded, the counts show the count it had on the last page instead of the page's 0. */
|
|
199
|
+
export function showLastKnownCount(): void {
|
|
200
|
+
if (status.kind !== 'unknown') return;
|
|
201
|
+
const count = lastKnownCount();
|
|
202
|
+
if (count !== null) showSavedCount(count);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/** Sets Shopify's notes on the last change: each message once, in the site's words (`warningText`). */
|
|
206
|
+
export function setWarnings(notes: readonly CartWarning[]): void {
|
|
207
|
+
warnings = notes
|
|
208
|
+
.filter((note, index) => notes.findIndex((other) => other.message === note.message) === index)
|
|
209
|
+
.map((note) => ({ ...note, message: cfg.warningText?.(note) || note.message }));
|
|
210
|
+
if (warnings.length) emit('cart-warning', { warnings });
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/** The queue only waits for a task: its result, or failure, reaches the caller through the promise `run` returns. */
|
|
214
|
+
const waitOnly = () => undefined;
|
|
215
|
+
|
|
216
|
+
/** @returns A runner that starts each task once the one before has settled, whether it succeeded or failed. */
|
|
217
|
+
export function serial(): <T>(task: () => Promise<T>) => Promise<T> {
|
|
218
|
+
let previous: Promise<unknown> = Promise.resolve();
|
|
219
|
+
return <T>(task: () => Promise<T>) => {
|
|
220
|
+
const run = previous.then(task);
|
|
221
|
+
previous = run.then(waitOnly, waitOnly);
|
|
222
|
+
return run;
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const runInOrder = serial();
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Runs a cart change after the ones before it, so quick clicks never race. A failure sets the `error` status (shown by
|
|
230
|
+
* `tether-show="cart-error"` until the next change succeeds), emits `tether:cart-error` and rejects.
|
|
231
|
+
*/
|
|
232
|
+
export function enqueue<T>(task: () => Promise<T>): Promise<T> {
|
|
233
|
+
return runInOrder(task).then(
|
|
234
|
+
(value) => {
|
|
235
|
+
if (status.kind === 'error') {
|
|
236
|
+
status = statusOf(status.cart);
|
|
237
|
+
render();
|
|
238
|
+
}
|
|
239
|
+
return value;
|
|
240
|
+
},
|
|
241
|
+
(error: unknown) => {
|
|
242
|
+
status = { kind: 'error', error, cart: currentCart() };
|
|
243
|
+
render();
|
|
244
|
+
emit('cart-error', { error });
|
|
245
|
+
throw error;
|
|
246
|
+
},
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/** @returns The quantity to show for `line`. */
|
|
251
|
+
export const pendingQuantityOf = (line: ShopifyCartLine): number => quantityOf(line, pendingQuantities);
|
|
252
|
+
|
|
253
|
+
const meta = (): CartMeta => ({
|
|
254
|
+
open: isOpen(),
|
|
255
|
+
count: currentCart()?.totalQuantity ?? 0,
|
|
256
|
+
loaded: status.kind !== 'unknown',
|
|
257
|
+
warnings: [...warnings],
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Calls `listener` now and after every change of the cart or the drawer.
|
|
262
|
+
*
|
|
263
|
+
* @returns Stops listening.
|
|
264
|
+
*/
|
|
265
|
+
export function subscribe(listener: (cart: ShopifyCart | null, meta: CartMeta) => void): () => void {
|
|
266
|
+
const events = ['cart-updated', 'cart-toggled'].map((name) => `${cfg.prefix}:${name}`);
|
|
267
|
+
const call = () => {
|
|
268
|
+
listener(currentCart(), meta());
|
|
269
|
+
};
|
|
270
|
+
events.forEach((name) => {
|
|
271
|
+
document.addEventListener(name, call);
|
|
272
|
+
});
|
|
273
|
+
call();
|
|
274
|
+
return () => {
|
|
275
|
+
events.forEach((name) => {
|
|
276
|
+
document.removeEventListener(name, call);
|
|
277
|
+
});
|
|
278
|
+
};
|
|
279
|
+
}
|
package/src/{cart.js → cart.ts}
RENAMED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The cart, as one object: `Tether.cart`. Every change goes through one queue (fast clicks never race) and resolves
|
|
3
|
-
* with the cart Shopify sent back. The state lives in `cart/store.
|
|
4
|
-
* drawer in `cart/drawer.
|
|
3
|
+
* with the cart Shopify sent back. The state lives in `cart/store.ts`, the Cart API calls in `cart/mutations.ts`, the
|
|
4
|
+
* drawer in `cart/drawer.ts`, the document rendering in `cart/render.ts` and the checkout in `cart/checkout.ts`.
|
|
5
5
|
*/
|
|
6
|
-
import { cfg, reportError } from './config.
|
|
7
|
-
import { TetherError } from './client.
|
|
8
|
-
import { emit, toAttributes } from './dom.
|
|
6
|
+
import { cfg, reportError } from './config.ts';
|
|
7
|
+
import { TetherError } from './client.ts';
|
|
8
|
+
import { emit, toAttributes } from './dom.ts';
|
|
9
9
|
import {
|
|
10
10
|
fetchCart,
|
|
11
11
|
isCartGone,
|
|
@@ -15,16 +15,15 @@ import {
|
|
|
15
15
|
toLineInput,
|
|
16
16
|
toLineUpdate,
|
|
17
17
|
updateLines,
|
|
18
|
-
} from './cart/mutations.
|
|
19
|
-
import { checkout } from './cart/checkout.
|
|
20
|
-
import { drawers, isOpen, openCart, closeCart, toggleCart, trapFocus } from './cart/drawer.
|
|
18
|
+
} from './cart/mutations.ts';
|
|
19
|
+
import { checkout, setCheckoutResolver } from './cart/checkout.ts';
|
|
20
|
+
import { drawers, isOpen, openCart, closeCart, toggleCart, trapFocus } from './cart/drawer.ts';
|
|
21
21
|
import {
|
|
22
22
|
announce,
|
|
23
23
|
cartStatus,
|
|
24
24
|
commit,
|
|
25
25
|
currentCart,
|
|
26
26
|
currentWarnings,
|
|
27
|
-
customer,
|
|
28
27
|
enqueue,
|
|
29
28
|
forgetSavedCart,
|
|
30
29
|
keepQuietly,
|
|
@@ -32,30 +31,26 @@ import {
|
|
|
32
31
|
pendingQuantityOf,
|
|
33
32
|
render,
|
|
34
33
|
savedCartId,
|
|
35
|
-
setFailure,
|
|
36
34
|
settleLoad,
|
|
37
35
|
showLastKnownCount,
|
|
38
36
|
subscribe,
|
|
39
|
-
} from './cart/store.
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
37
|
+
} from './cart/store.ts';
|
|
38
|
+
import type { CartWarning } from './config.ts';
|
|
39
|
+
import type { Attributes, LineSpec, CartLineInput, LinePatch } from './cart/mutations.ts';
|
|
40
|
+
import type { ShopifyCart, ShopifyCartLine, CartStatus } from './cart/store.ts';
|
|
43
41
|
|
|
44
42
|
export { trapFocus };
|
|
45
43
|
|
|
46
|
-
|
|
47
|
-
|
|
44
|
+
type DiscountCodes = string[] | string | ((current: string[]) => string[]);
|
|
45
|
+
|
|
46
|
+
const currentLines = (): readonly ShopifyCartLine[] => currentCart()?.lines.nodes ?? [];
|
|
48
47
|
|
|
49
48
|
/**
|
|
50
49
|
* +1 / -1 for a line. Quick clicks add up: each builds on the quantity the click before asked for, which shows at once
|
|
51
50
|
* (`tether-line="quantity"`, `quantityOf`) until Shopify has answered. Only the latest target is sent; the ones it
|
|
52
51
|
* replaced are skipped.
|
|
53
|
-
*
|
|
54
|
-
* @param {string} lineId
|
|
55
|
-
* @param {number} delta
|
|
56
|
-
* @returns {Promise<ShopifyCart | null>}
|
|
57
52
|
*/
|
|
58
|
-
function changeQuantity(lineId, delta) {
|
|
53
|
+
function changeQuantity(lineId: string, delta: number): Promise<ShopifyCart | null> {
|
|
59
54
|
const line = currentLines().find((candidate) => candidate.id === lineId);
|
|
60
55
|
if (!line) return Promise.resolve(currentCart());
|
|
61
56
|
const target = Math.max(0, pendingQuantityOf(line) + delta);
|
|
@@ -87,20 +82,16 @@ function changeQuantity(lineId, delta) {
|
|
|
87
82
|
/**
|
|
88
83
|
* The codes the cart has once a change is its turn in the queue.
|
|
89
84
|
*
|
|
90
|
-
* @param
|
|
91
|
-
* @returns {string[]}
|
|
85
|
+
* @param codes A function gets the codes as they are by then.
|
|
92
86
|
*/
|
|
93
|
-
function discountCodesFrom(codes) {
|
|
87
|
+
function discountCodesFrom(codes: DiscountCodes): string[] {
|
|
94
88
|
const current = (currentCart()?.discountCodes ?? []).map((discount) => discount.code);
|
|
95
89
|
const list = typeof codes === 'function' ? codes(current) : codes;
|
|
96
90
|
return (Array.isArray(list) ? list : [list]).map((code) => code.trim()).filter(Boolean);
|
|
97
91
|
}
|
|
98
92
|
|
|
99
|
-
/**
|
|
100
|
-
|
|
101
|
-
* @returns {{ key: string, value: string }[]} The cart's attributes with `attributes` merged in; `''` removes a key.
|
|
102
|
-
*/
|
|
103
|
-
function mergedAttributes(attributes) {
|
|
93
|
+
/** @returns The cart's attributes with `attributes` merged in; `''` removes a key. */
|
|
94
|
+
function mergedAttributes(attributes: Attributes): { key: string; value: string }[] {
|
|
104
95
|
const merged = new Map((currentCart()?.attributes ?? []).map((attribute) => [attribute.key, attribute.value]));
|
|
105
96
|
toAttributes(attributes).forEach((attribute) => merged.set(attribute.key, attribute.value));
|
|
106
97
|
return [...merged].filter(([, value]) => value !== '').map(([key, value]) => ({ key, value }));
|
|
@@ -109,10 +100,8 @@ function mergedAttributes(attributes) {
|
|
|
109
100
|
/**
|
|
110
101
|
* Loads the saved cart. A cart Shopify no longer has is forgotten; one it can't be asked about (the network, the
|
|
111
102
|
* token) is kept, because it is probably fine.
|
|
112
|
-
*
|
|
113
|
-
* @returns {Promise<ShopifyCart | null>}
|
|
114
103
|
*/
|
|
115
|
-
async function init() {
|
|
104
|
+
async function init(): Promise<ShopifyCart | null> {
|
|
116
105
|
const id = savedCartId();
|
|
117
106
|
if (!id) {
|
|
118
107
|
settleLoad({ kind: 'loaded', cart: null });
|
|
@@ -133,73 +122,59 @@ async function init() {
|
|
|
133
122
|
|
|
134
123
|
export const Cart = {
|
|
135
124
|
init,
|
|
136
|
-
/**
|
|
125
|
+
/** The saved cart's id. */
|
|
137
126
|
id: savedCartId,
|
|
138
127
|
|
|
139
|
-
|
|
140
|
-
get status() {
|
|
128
|
+
get status(): CartStatus {
|
|
141
129
|
return cartStatus();
|
|
142
130
|
},
|
|
143
|
-
|
|
144
|
-
get state() {
|
|
131
|
+
get state(): ShopifyCart | null {
|
|
145
132
|
return currentCart();
|
|
146
133
|
},
|
|
147
|
-
/**
|
|
148
|
-
get lines() {
|
|
149
|
-
return currentLines();
|
|
134
|
+
/** The cart's lines, a copy: changing it changes nothing in the cart. */
|
|
135
|
+
get lines(): ShopifyCartLine[] {
|
|
136
|
+
return [...currentLines()];
|
|
150
137
|
},
|
|
151
|
-
|
|
152
|
-
get count() {
|
|
138
|
+
get count(): number {
|
|
153
139
|
return currentCart()?.totalQuantity ?? 0;
|
|
154
140
|
},
|
|
155
|
-
/**
|
|
156
|
-
get loaded() {
|
|
141
|
+
/** True once the saved cart, or that there is none, is known. */
|
|
142
|
+
get loaded(): boolean {
|
|
157
143
|
return cartStatus().kind !== 'unknown';
|
|
158
144
|
},
|
|
159
|
-
/**
|
|
160
|
-
|
|
145
|
+
/**
|
|
146
|
+
* The failure of the last load or change, until a change succeeds. Requests fail with an `Error`; anything else
|
|
147
|
+
* thrown is wrapped, so the public type holds.
|
|
148
|
+
*/
|
|
149
|
+
get lastError(): Error | null {
|
|
161
150
|
const current = cartStatus();
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
set lastError(error) {
|
|
165
|
-
setFailure(error);
|
|
151
|
+
if (current.kind !== 'error') return null;
|
|
152
|
+
return current.error instanceof Error ? current.error : new Error(String(current.error));
|
|
166
153
|
},
|
|
167
|
-
/**
|
|
168
|
-
get warnings() {
|
|
169
|
-
return currentWarnings();
|
|
154
|
+
/** Shopify's notes on the last change, a copy. */
|
|
155
|
+
get warnings(): CartWarning[] {
|
|
156
|
+
return [...currentWarnings()];
|
|
170
157
|
},
|
|
171
|
-
/**
|
|
172
|
-
get pendingQty() {
|
|
158
|
+
/** Line id → the quantity quick clicks asked for. */
|
|
159
|
+
get pendingQty(): ReadonlyMap<string, { quantity: number }> {
|
|
173
160
|
return pendingQuantities;
|
|
174
161
|
},
|
|
175
162
|
|
|
176
|
-
/** @returns {string | null} The logged-in customer's Customer Account API token (set by the account part). */
|
|
177
|
-
get customerAccessToken() {
|
|
178
|
-
return customer.accessToken;
|
|
179
|
-
},
|
|
180
|
-
set customerAccessToken(token) {
|
|
181
|
-
customer.accessToken = token || null;
|
|
182
|
-
},
|
|
183
|
-
/** @returns {(() => Promise<string | null>) | null} The account's own checkout URL (set by the account part). */
|
|
184
|
-
get beforeCheckout() {
|
|
185
|
-
return customer.beforeCheckout;
|
|
186
|
-
},
|
|
187
|
-
set beforeCheckout(fetchUrl) {
|
|
188
|
-
customer.beforeCheckout = typeof fetchUrl === 'function' ? fetchUrl : null;
|
|
189
|
-
},
|
|
190
163
|
/**
|
|
191
|
-
*
|
|
192
|
-
* customer
|
|
193
|
-
*
|
|
194
|
-
* @param {Partial<CustomerLink>} link Fields left out keep their value.
|
|
164
|
+
* Sets where a logged-in customer checks out, or clears it with `null`: the account part's resolver answers the
|
|
165
|
+
* customer's checkout URL for the cart (`null` uses the cart's own).
|
|
195
166
|
*/
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
167
|
+
setCheckoutResolver,
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Forgets the saved cart on this device, as if there were none: the page shows an empty cart and
|
|
171
|
+
* `tether:cart-updated` fires with reason `load`. The next add starts a new cart.
|
|
172
|
+
*/
|
|
173
|
+
forgetSaved(): void {
|
|
174
|
+
forgetSavedCart();
|
|
175
|
+
settleLoad({ kind: 'loaded', cart: null });
|
|
199
176
|
},
|
|
200
177
|
|
|
201
|
-
/** Keeps a cart and shows it, as a change does; tests seed the cart with it. */
|
|
202
|
-
_commit: commit,
|
|
203
178
|
render,
|
|
204
179
|
subscribe,
|
|
205
180
|
|
|
@@ -207,14 +182,16 @@ export const Cart = {
|
|
|
207
182
|
* `add(variantId, 2)`, `add({ variantId, quantity, sellingPlanId, attributes })`, or `add([line, line])` for
|
|
208
183
|
* bundles in one request. Creates the cart when there is none.
|
|
209
184
|
*
|
|
210
|
-
* @
|
|
211
|
-
* @param {number} [quantity]
|
|
212
|
-
* @param {Omit<LineSpec, 'variantId' | 'quantity'>} [options]
|
|
213
|
-
* @returns {Promise<ShopifyCart | null>}
|
|
185
|
+
* @throws {TetherError} (rejected) When the add fails, or Shopify answers it without a cart.
|
|
214
186
|
*/
|
|
215
|
-
add(
|
|
216
|
-
|
|
217
|
-
|
|
187
|
+
add(
|
|
188
|
+
input: string | number | LineSpec | (string | number | LineSpec)[],
|
|
189
|
+
quantity = 1,
|
|
190
|
+
options: Omit<LineSpec, 'variantId' | 'quantity'> = {},
|
|
191
|
+
): Promise<ShopifyCart> {
|
|
192
|
+
const lines: CartLineInput[] = (Array.isArray(input) ? input : [input]).map((line) =>
|
|
193
|
+
toLineInput(line, quantity, options),
|
|
194
|
+
);
|
|
218
195
|
return enqueue(async () => {
|
|
219
196
|
const cart = await mutateCart({
|
|
220
197
|
name: 'cartLinesAdd',
|
|
@@ -223,6 +200,8 @@ export const Cart = {
|
|
|
223
200
|
variables: { lines },
|
|
224
201
|
createInput: { lines },
|
|
225
202
|
});
|
|
203
|
+
// A mutation answers without a cart only beside user errors, which reject above; an add always has one.
|
|
204
|
+
if (!cart) throw new TetherError('Shopify answered the add without a cart');
|
|
226
205
|
commit(cart, 'add');
|
|
227
206
|
emit('added', { cart, lines });
|
|
228
207
|
if (cfg.openCartOnAdd) openCart();
|
|
@@ -233,11 +212,9 @@ export const Cart = {
|
|
|
233
212
|
/**
|
|
234
213
|
* `updateLine(lineId, 3)` or `updateLine(lineId, { quantity, sellingPlanId, attributes })`.
|
|
235
214
|
*
|
|
236
|
-
* @param
|
|
237
|
-
* @param {number | LinePatch} patch A quantity of 0 or less removes the line.
|
|
238
|
-
* @returns {Promise<ShopifyCart | null>}
|
|
215
|
+
* @param patch A quantity of 0 or less removes the line.
|
|
239
216
|
*/
|
|
240
|
-
updateLine(lineId, patch) {
|
|
217
|
+
updateLine(lineId: string, patch: number | LinePatch): Promise<ShopifyCart | null> {
|
|
241
218
|
const quantity = typeof patch === 'number' ? patch : patch.quantity;
|
|
242
219
|
if (quantity !== undefined && quantity <= 0) return Cart.removeLine(lineId);
|
|
243
220
|
const line = toLineUpdate(lineId, patch);
|
|
@@ -246,33 +223,21 @@ export const Cart = {
|
|
|
246
223
|
|
|
247
224
|
changeQuantity,
|
|
248
225
|
|
|
249
|
-
/**
|
|
250
|
-
* @param {ShopifyCartLine} line
|
|
251
|
-
* @returns {number} The quantity to show: the one asked for while an update is on its way.
|
|
252
|
-
*/
|
|
226
|
+
/** The quantity to show: the one asked for while an update is on its way. */
|
|
253
227
|
quantityOf: pendingQuantityOf,
|
|
254
228
|
|
|
255
|
-
|
|
256
|
-
* @param {string | string[]} lineIds
|
|
257
|
-
* @returns {Promise<ShopifyCart | null>}
|
|
258
|
-
*/
|
|
259
|
-
removeLine(lineIds) {
|
|
229
|
+
removeLine(lineIds: string | string[]): Promise<ShopifyCart | null> {
|
|
260
230
|
const ids = Array.isArray(lineIds) ? lineIds : [lineIds];
|
|
261
231
|
return enqueue(async () => commit(await removeLines(ids), 'remove'));
|
|
262
232
|
},
|
|
263
233
|
|
|
264
|
-
|
|
265
|
-
clear() {
|
|
234
|
+
clear(): Promise<ShopifyCart | null> {
|
|
266
235
|
const ids = currentLines().map((line) => line.id);
|
|
267
236
|
if (!ids.length) return Promise.resolve(currentCart());
|
|
268
237
|
return Cart.removeLine(ids);
|
|
269
238
|
},
|
|
270
239
|
|
|
271
|
-
|
|
272
|
-
* @param {string | null | undefined} note
|
|
273
|
-
* @returns {Promise<ShopifyCart | null>}
|
|
274
|
-
*/
|
|
275
|
-
setNote(note) {
|
|
240
|
+
setNote(note: string | null | undefined): Promise<ShopifyCart | null> {
|
|
276
241
|
const variables = { note: note ?? '' };
|
|
277
242
|
return enqueue(async () =>
|
|
278
243
|
commit(
|
|
@@ -291,10 +256,9 @@ export const Cart = {
|
|
|
291
256
|
/**
|
|
292
257
|
* Cart attributes, merged with the cart's (as it is when the change runs, so quick calls don't drop each other).
|
|
293
258
|
*
|
|
294
|
-
* @param
|
|
295
|
-
* @returns {Promise<ShopifyCart | null>}
|
|
259
|
+
* @param attributes The value `''` removes a key.
|
|
296
260
|
*/
|
|
297
|
-
setAttributes(attributes) {
|
|
261
|
+
setAttributes(attributes: Attributes): Promise<ShopifyCart | null> {
|
|
298
262
|
return enqueue(async () => {
|
|
299
263
|
const variables = { attributes: mergedAttributes(attributes) };
|
|
300
264
|
const cart = await mutateCart({
|
|
@@ -308,13 +272,8 @@ export const Cart = {
|
|
|
308
272
|
});
|
|
309
273
|
},
|
|
310
274
|
|
|
311
|
-
/**
|
|
312
|
-
|
|
313
|
-
*
|
|
314
|
-
* @param {string[] | string | ((current: string[]) => string[])} codes
|
|
315
|
-
* @returns {Promise<ShopifyCart | null>}
|
|
316
|
-
*/
|
|
317
|
-
setDiscountCodes(codes) {
|
|
275
|
+
/** Replaces the discount codes; `[]` removes them all. */
|
|
276
|
+
setDiscountCodes(codes: DiscountCodes): Promise<ShopifyCart | null> {
|
|
318
277
|
return enqueue(async () => {
|
|
319
278
|
const variables = { discountCodes: discountCodesFrom(codes) };
|
|
320
279
|
const cart = await mutateCart({
|
|
@@ -331,11 +290,9 @@ export const Cart = {
|
|
|
331
290
|
/**
|
|
332
291
|
* Adds a code. A code Shopify doesn't accept is removed again, so it doesn't warn on every later change.
|
|
333
292
|
*
|
|
334
|
-
* @param {string} code
|
|
335
|
-
* @returns {Promise<ShopifyCart | null>}
|
|
336
293
|
* @throws {TetherError} `INVALID_DISCOUNT` (rejected) when Shopify doesn't accept the code.
|
|
337
294
|
*/
|
|
338
|
-
async applyDiscount(code) {
|
|
295
|
+
async applyDiscount(code: string): Promise<ShopifyCart | null> {
|
|
339
296
|
const cart = await Cart.setDiscountCodes((current) => [...new Set([...current, code])]);
|
|
340
297
|
const wanted = code.toLowerCase();
|
|
341
298
|
const applied = (cart?.discountCodes ?? []).find((discount) => discount.code.toLowerCase() === wanted);
|
|
@@ -348,11 +305,7 @@ export const Cart = {
|
|
|
348
305
|
throw new TetherError(`Discount code not valid: ${code}`, { code: 'INVALID_DISCOUNT' });
|
|
349
306
|
},
|
|
350
307
|
|
|
351
|
-
|
|
352
|
-
* @param {string} code
|
|
353
|
-
* @returns {Promise<ShopifyCart | null>}
|
|
354
|
-
*/
|
|
355
|
-
removeDiscount(code) {
|
|
308
|
+
removeDiscount(code: string): Promise<ShopifyCart | null> {
|
|
356
309
|
const unwanted = code.toLowerCase();
|
|
357
310
|
return Cart.setDiscountCodes((current) => current.filter((existing) => existing.toLowerCase() !== unwanted));
|
|
358
311
|
},
|
|
@@ -360,11 +313,12 @@ export const Cart = {
|
|
|
360
313
|
/**
|
|
361
314
|
* Sets who is buying; a country changes the cart's currency and prices to that market's.
|
|
362
315
|
*
|
|
363
|
-
* @param
|
|
364
|
-
* @param {{ create?: boolean }} [options] `create: false` forgets an expired cart instead of creating an empty one.
|
|
365
|
-
* @returns {Promise<ShopifyCart | null>}
|
|
316
|
+
* @param options `create: false` forgets an expired cart instead of creating an empty one.
|
|
366
317
|
*/
|
|
367
|
-
setBuyerIdentity(
|
|
318
|
+
setBuyerIdentity(
|
|
319
|
+
buyerIdentity: { countryCode?: string; email?: string; phone?: string },
|
|
320
|
+
{ create = true }: { create?: boolean } = {},
|
|
321
|
+
): Promise<ShopifyCart | null> {
|
|
368
322
|
return enqueue(async () => {
|
|
369
323
|
const cart = await mutateCart({
|
|
370
324
|
name: 'cartBuyerIdentityUpdate',
|
|
@@ -372,7 +326,7 @@ export const Cart = {
|
|
|
372
326
|
inner: 'buyerIdentity: $buyerIdentity',
|
|
373
327
|
variables: { buyerIdentity },
|
|
374
328
|
createInput: create ? { buyerIdentity } : null,
|
|
375
|
-
}).catch((
|
|
329
|
+
}).catch((error: unknown) => {
|
|
376
330
|
// mutateCart has forgotten the cart Shopify no longer has; without `create` that leaves no cart.
|
|
377
331
|
if (create || !isCartGone(error)) throw error;
|
|
378
332
|
return null;
|