@redotech/redo-hydrogen 1.2.2 → 1.3.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/CHANGELOG.md +9 -0
- package/dist/cjs/index.js +2 -2
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/index.js +2 -2
- package/dist/esm/index.js.map +1 -1
- package/dist/types.d.ts +4 -5
- package/package.json +2 -1
- package/rollup.config.js +2 -0
- package/src/components/redo-checkout-buttons.tsx +89 -44
- package/src/providers/redo-coverage-client.tsx +4 -4
- package/src/svg.d.ts +4 -0
- package/src/types.ts +3 -3
- package/src/utils/cart.ts +22 -14
- package/src/utils/circle-spinner.svg +24 -0
- package/src/utils/timeout.ts +12 -0
- package/tsconfig.json +1 -1
package/dist/types.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CartReturn } from '@shopify/hydrogen';
|
|
1
|
+
import { CartReturn, OptimisticCart } from '@shopify/hydrogen';
|
|
2
2
|
import { ReactNode, DependencyList } from 'react';
|
|
3
3
|
import { CartWithActionsDocs } from '@shopify/hydrogen-react/dist/types/cart-types';
|
|
4
4
|
import { ProductVariant } from '@shopify/hydrogen-react/storefront-api-types';
|
|
@@ -14,7 +14,7 @@ interface RedoCoverageClient {
|
|
|
14
14
|
get eligible(): boolean;
|
|
15
15
|
get price(): number | undefined;
|
|
16
16
|
get storeId(): string | undefined;
|
|
17
|
-
get cart(): CartReturn | CartWithActionsDocs | undefined;
|
|
17
|
+
get cart(): CartReturn | CartWithActionsDocs | OptimisticCart | undefined;
|
|
18
18
|
get cartProduct(): CartProductVariantFragment | undefined;
|
|
19
19
|
get cartAttribute(): CartAttributeKey | undefined;
|
|
20
20
|
get errors(): RedoError[] | undefined;
|
|
@@ -30,7 +30,7 @@ type RedoContextValue = {
|
|
|
30
30
|
loading: boolean;
|
|
31
31
|
storeId?: string;
|
|
32
32
|
cartInfoToEnable?: CartInfoToEnable;
|
|
33
|
-
cart?: CartReturn | CartWithActionsDocs;
|
|
33
|
+
cart?: CartReturn | CartWithActionsDocs | OptimisticCart;
|
|
34
34
|
errors?: RedoError[];
|
|
35
35
|
};
|
|
36
36
|
declare enum RedoErrorType {
|
|
@@ -45,14 +45,13 @@ type RedoError = {
|
|
|
45
45
|
};
|
|
46
46
|
|
|
47
47
|
declare const RedoProvider: ({ cart, storeId, children }: {
|
|
48
|
-
cart: CartReturn | CartWithActionsDocs;
|
|
48
|
+
cart: CartReturn | CartWithActionsDocs | OptimisticCart;
|
|
49
49
|
storeId: string;
|
|
50
50
|
children: ReactNode;
|
|
51
51
|
}) => ReactNode;
|
|
52
52
|
declare const useRedoCoverageClient: () => RedoCoverageClient;
|
|
53
53
|
|
|
54
54
|
declare const RedoCheckoutButtons: (props: {
|
|
55
|
-
cart: CartReturn | CartWithActionsDocs;
|
|
56
55
|
children?: ReactNode;
|
|
57
56
|
onClick?: (enabled: boolean) => void;
|
|
58
57
|
}) => react_jsx_runtime.JSX.Element;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@redotech/redo-hydrogen",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.1",
|
|
4
4
|
"description": "Utilities to enable and disable Redo coverage on Hydrogen stores",
|
|
5
5
|
"main": "dist/cjs/index.js",
|
|
6
6
|
"module": "dist/esm/index.js",
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
"@rollup/plugin-node-resolve": "^16.0.0",
|
|
27
27
|
"@rollup/plugin-terser": "^0.4.4",
|
|
28
28
|
"@rollup/plugin-typescript": "^12.1.2",
|
|
29
|
+
"@svgr/rollup": "^8.1.0",
|
|
29
30
|
"@types/react": "^19.0.8",
|
|
30
31
|
"nodemon": "^3.1.9",
|
|
31
32
|
"react": "^18.3.1",
|
package/rollup.config.js
CHANGED
|
@@ -4,6 +4,7 @@ import typescript from "@rollup/plugin-typescript";
|
|
|
4
4
|
import dts from "rollup-plugin-dts";
|
|
5
5
|
import terser from "@rollup/plugin-terser";
|
|
6
6
|
import peerDepsExternal from "rollup-plugin-peer-deps-external";
|
|
7
|
+
import svgr from '@svgr/rollup';
|
|
7
8
|
|
|
8
9
|
const packageJson = require("./package.json");
|
|
9
10
|
|
|
@@ -30,6 +31,7 @@ export default [
|
|
|
30
31
|
commonjs(),
|
|
31
32
|
typescript({ tsconfig: "./tsconfig.json" }),
|
|
32
33
|
terser(),
|
|
34
|
+
svgr(),
|
|
33
35
|
],
|
|
34
36
|
external: ["react", "react-dom"],
|
|
35
37
|
},
|
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
import React, { MouseEvent, ReactNode, useEffect, useState } from "react";
|
|
2
|
-
import {
|
|
3
|
-
CartForm,
|
|
4
|
-
CartActionInput,
|
|
5
|
-
CartReturn,
|
|
6
|
-
} from "@shopify/hydrogen";
|
|
2
|
+
import { CartForm, CartActionInput, CartReturn, OptimisticCart } from "@shopify/hydrogen";
|
|
7
3
|
import { useRedoCoverageClient } from "../providers/redo-coverage-client";
|
|
8
4
|
import { CartInfoToEnable, RedoCoverageClient } from "../types";
|
|
9
5
|
import { REDO_PUBLIC_API_HOSTNAME } from "../utils/security";
|
|
@@ -11,6 +7,9 @@ import { CurrencyCode } from "@shopify/hydrogen-react/storefront-api-types";
|
|
|
11
7
|
import { CartWithActionsDocs } from "@shopify/hydrogen-react/dist/types/cart-types";
|
|
12
8
|
import { getCartLines, isCartWithActionsDocs } from "../utils/cart";
|
|
13
9
|
|
|
10
|
+
import CircleSpinner from "../utils/circle-spinner.svg";
|
|
11
|
+
import { executeWithTimeout } from "../utils/timeout";
|
|
12
|
+
|
|
14
13
|
type CheckoutButtonUIResponse = {
|
|
15
14
|
html: string;
|
|
16
15
|
css: string;
|
|
@@ -22,7 +21,7 @@ const getButtonsToShow = ({
|
|
|
22
21
|
storeId
|
|
23
22
|
}: {
|
|
24
23
|
redoCoverageClient: RedoCoverageClient,
|
|
25
|
-
cart: CartReturn | CartWithActionsDocs,
|
|
24
|
+
cart: CartReturn | CartWithActionsDocs | OptimisticCart,
|
|
26
25
|
storeId: string;
|
|
27
26
|
}): Promise<CheckoutButtonUIResponse | null> => {
|
|
28
27
|
return new Promise<CheckoutButtonUIResponse | null>((resolve, reject) => {
|
|
@@ -47,7 +46,7 @@ const getButtonsToShow = ({
|
|
|
47
46
|
ui: json
|
|
48
47
|
});
|
|
49
48
|
|
|
50
|
-
if(!ui) {
|
|
49
|
+
if (!ui) {
|
|
51
50
|
return reject(null);
|
|
52
51
|
}
|
|
53
52
|
|
|
@@ -62,15 +61,15 @@ const applyButtonVariables = ({
|
|
|
62
61
|
ui
|
|
63
62
|
}: {
|
|
64
63
|
redoCoverageClient: RedoCoverageClient,
|
|
65
|
-
cart: CartReturn | CartWithActionsDocs,
|
|
64
|
+
cart: CartReturn | CartWithActionsDocs | OptimisticCart,
|
|
66
65
|
ui: CheckoutButtonUIResponse
|
|
67
66
|
}): CheckoutButtonUIResponse | null => {
|
|
68
|
-
if(!redoCoverageClient.eligible || !redoCoverageClient.price || !cart?.cost) {
|
|
67
|
+
if (!redoCoverageClient.eligible || !redoCoverageClient.price || !cart?.cost) {
|
|
69
68
|
return null;
|
|
70
69
|
}
|
|
71
70
|
|
|
72
|
-
let currencyCode: CurrencyCode = cart.cost.
|
|
73
|
-
if(currencyCode === 'XXX') {
|
|
71
|
+
let currencyCode: CurrencyCode = cart.cost.subtotalAmount.currencyCode;
|
|
72
|
+
if (currencyCode === 'XXX') {
|
|
74
73
|
currencyCode = 'USD';
|
|
75
74
|
}
|
|
76
75
|
|
|
@@ -78,9 +77,9 @@ const applyButtonVariables = ({
|
|
|
78
77
|
const combinedPrice = new Intl.NumberFormat('en-US', {
|
|
79
78
|
style: 'currency',
|
|
80
79
|
currency: currencyCode
|
|
81
|
-
}).format(Number(cart.cost.
|
|
80
|
+
}).format(Number(cart.cost.subtotalAmount.amount) + (cartContainsRedo ? 0 : redoCoverageClient.price));
|
|
82
81
|
|
|
83
|
-
if(!combinedPrice || !combinedPrice.length || combinedPrice.includes('NaN')) {
|
|
82
|
+
if (!combinedPrice || !combinedPrice.length || combinedPrice.includes('NaN')) {
|
|
84
83
|
return null;
|
|
85
84
|
}
|
|
86
85
|
|
|
@@ -103,7 +102,6 @@ const findAncestor = (
|
|
|
103
102
|
};
|
|
104
103
|
|
|
105
104
|
const RedoCheckoutButtons = (props: {
|
|
106
|
-
cart: CartReturn | CartWithActionsDocs;
|
|
107
105
|
children?: ReactNode;
|
|
108
106
|
onClick?: (enabled: boolean) => void;
|
|
109
107
|
}) => {
|
|
@@ -112,23 +110,54 @@ const RedoCheckoutButtons = (props: {
|
|
|
112
110
|
let checkoutUrl = redoCoverageClient.cart?.checkoutUrl || '/checkout';
|
|
113
111
|
let [redoProductToAdd, setRedoProductToAdd] =
|
|
114
112
|
useState<CartInfoToEnable | null>(null);
|
|
115
|
-
let [checkoutButtonsUI, setCheckoutButtonsUI] =
|
|
116
|
-
null
|
|
117
|
-
|
|
113
|
+
let [checkoutButtonsUI, setCheckoutButtonsUI] =
|
|
114
|
+
useState<CheckoutButtonUIResponse | null>(null);
|
|
115
|
+
|
|
116
|
+
const [buttonPending, setButtonPending] = useState(false);
|
|
118
117
|
|
|
119
118
|
useEffect(() => {
|
|
120
119
|
(async () => {
|
|
121
|
-
if(!redoCoverageClient.eligible || !cart || !redoCoverageClient.storeId) {
|
|
120
|
+
if (!redoCoverageClient.eligible || !cart || !redoCoverageClient.storeId) {
|
|
122
121
|
return;
|
|
123
122
|
}
|
|
124
123
|
|
|
125
124
|
const buttons = await getButtonsToShow({ redoCoverageClient, cart, storeId: redoCoverageClient.storeId });
|
|
126
|
-
if(buttons) {
|
|
125
|
+
if (buttons) {
|
|
127
126
|
setCheckoutButtonsUI(buttons);
|
|
128
127
|
}
|
|
129
128
|
})();
|
|
130
129
|
}, [cart, redoCoverageClient.eligible, redoCoverageClient.price, redoCoverageClient.storeId]);
|
|
131
130
|
|
|
131
|
+
/** To avoid the inevitable spammers trying to checkout faster by clicking over and over, between the time the promise resolves and the new tab opens (or errors) */
|
|
132
|
+
const DELAY_TO_ALLOW_CLICKING_AGAIN = 2000;
|
|
133
|
+
const TIMEOUT_FOR_CHECKOUTS = 8000;
|
|
134
|
+
|
|
135
|
+
const handleCoverageCheckoutClick = async (isCoverage: boolean) => {
|
|
136
|
+
if (!redoCoverageClient || !redoCoverageClient.enable || !redoCoverageClient.disable) {
|
|
137
|
+
console.error('Required redoCoverageClient methods not available');
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
setButtonPending(true);
|
|
142
|
+
try {
|
|
143
|
+
const functionToCall = isCoverage ? redoCoverageClient.enable : redoCoverageClient.disable;
|
|
144
|
+
const result = await executeWithTimeout(
|
|
145
|
+
functionToCall(),
|
|
146
|
+
TIMEOUT_FOR_CHECKOUTS
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
if (props.onClick) {
|
|
150
|
+
await props.onClick(result);
|
|
151
|
+
}
|
|
152
|
+
} catch (e) {
|
|
153
|
+
console.error(e);
|
|
154
|
+
} finally {
|
|
155
|
+
setTimeout(() => {
|
|
156
|
+
setButtonPending(false);
|
|
157
|
+
}, DELAY_TO_ALLOW_CLICKING_AGAIN);
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
|
|
132
161
|
const wrapperClickHandler = async (e: MouseEvent) => {
|
|
133
162
|
let clickedElement = e.target as HTMLElement;
|
|
134
163
|
|
|
@@ -136,26 +165,21 @@ const RedoCheckoutButtons = (props: {
|
|
|
136
165
|
return;
|
|
137
166
|
}
|
|
138
167
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
(
|
|
154
|
-
)
|
|
155
|
-
) {
|
|
156
|
-
await redoCoverageClient.disable();
|
|
157
|
-
if (props.onClick) {
|
|
158
|
-
await props.onClick(false);
|
|
168
|
+
const isCoverageButton = findAncestor(
|
|
169
|
+
clickedElement,
|
|
170
|
+
(el) => el.dataset?.target == "coverage-button"
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
const isNonCoverageButton = findAncestor(
|
|
174
|
+
clickedElement,
|
|
175
|
+
(el) => el.dataset?.target == "non-coverage-button",
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
if (isCoverageButton || isNonCoverageButton) {
|
|
179
|
+
try {
|
|
180
|
+
await handleCoverageCheckoutClick(isCoverageButton ? true : false);
|
|
181
|
+
} catch (error) {
|
|
182
|
+
console.error('Failed to update coverage state:', error);
|
|
159
183
|
}
|
|
160
184
|
window.location.href = checkoutUrl;
|
|
161
185
|
}
|
|
@@ -164,11 +188,32 @@ const RedoCheckoutButtons = (props: {
|
|
|
164
188
|
return (
|
|
165
189
|
<div>
|
|
166
190
|
{checkoutButtonsUI ? (
|
|
167
|
-
<div onClick={wrapperClickHandler}>
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
191
|
+
<div onClick={wrapperClickHandler} style={{ position: "relative" }}>
|
|
192
|
+
{checkoutButtonsUI.css ? <style>{checkoutButtonsUI.css}</style> : ''}
|
|
193
|
+
<div
|
|
194
|
+
dangerouslySetInnerHTML={{ __html: checkoutButtonsUI.html }}
|
|
195
|
+
style={{
|
|
196
|
+
opacity: (buttonPending) ? 0.25 : 1,
|
|
197
|
+
transition: 'opacity 0.2s ease-in-out'
|
|
198
|
+
}}
|
|
199
|
+
/>
|
|
200
|
+
{(buttonPending) && (
|
|
201
|
+
<div
|
|
202
|
+
style={{
|
|
203
|
+
position: "absolute",
|
|
204
|
+
top: 0,
|
|
205
|
+
left: 0,
|
|
206
|
+
width: "100%",
|
|
207
|
+
height: "100%",
|
|
208
|
+
display: "flex",
|
|
209
|
+
justifyContent: "center",
|
|
210
|
+
alignItems: "center",
|
|
211
|
+
zIndex: 1,
|
|
212
|
+
}}
|
|
213
|
+
>
|
|
214
|
+
<CircleSpinner />
|
|
215
|
+
</div>
|
|
216
|
+
)}
|
|
172
217
|
</div>
|
|
173
218
|
) : (
|
|
174
219
|
props.children
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { useFetcher } from "@remix-run/react";
|
|
2
|
-
import { CartReturn } from "@shopify/hydrogen";
|
|
2
|
+
import { CartReturn, OptimisticCart } from "@shopify/hydrogen";
|
|
3
3
|
import { createContext, ReactNode, useCallback, useContext, useEffect, useRef, useState } from "react";
|
|
4
4
|
import { CartProductVariantFragment, CartAttributeKey, CartInfoToEnable, RedoContextValue, RedoCoverageClient, RedoError, RedoErrorType } from "../types";
|
|
5
5
|
import { REDO_PUBLIC_API_HOSTNAME } from "../utils/security";
|
|
6
|
-
import { addProductToCartIfNeeded, removeProductFromCartIfNeeded, setCartRedoEnabledAttribute, useFetcherWithPromise, isCartWithActionsDocs, getCartLines, useWaitCartIdle } from "../utils/cart";
|
|
6
|
+
import { addProductToCartIfNeeded, removeProductFromCartIfNeeded, setCartRedoEnabledAttribute, useFetcherWithPromise, isCartWithActionsDocs, getCartLines, useWaitCartIdle, isOptimisticCart } from "../utils/cart";
|
|
7
7
|
import { CartWithActionsDocs } from "@shopify/hydrogen-react/dist/types/cart-types";
|
|
8
8
|
|
|
9
9
|
const DEFAULT_REDO_CONTEXT_VALUE: RedoContextValue = {
|
|
@@ -18,7 +18,7 @@ const RedoProvider = ({
|
|
|
18
18
|
storeId,
|
|
19
19
|
children
|
|
20
20
|
}: {
|
|
21
|
-
cart: CartReturn | CartWithActionsDocs,
|
|
21
|
+
cart: CartReturn | CartWithActionsDocs | OptimisticCart,
|
|
22
22
|
storeId: string,
|
|
23
23
|
children: ReactNode,
|
|
24
24
|
}): ReactNode => {
|
|
@@ -37,7 +37,7 @@ const RedoProvider = ({
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
useEffect(() => {
|
|
40
|
-
if(!cart || !storeId) {
|
|
40
|
+
if(!cart || !storeId || isOptimisticCart(cart)) {
|
|
41
41
|
return;
|
|
42
42
|
}
|
|
43
43
|
|
package/src/svg.d.ts
ADDED
package/src/types.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CartReturn } from "@shopify/hydrogen";
|
|
1
|
+
import { CartReturn, OptimisticCart } from "@shopify/hydrogen";
|
|
2
2
|
import { CartWithActionsDocs } from "@shopify/hydrogen-react/dist/types/cart-types";
|
|
3
3
|
import { ProductVariant } from "@shopify/hydrogen-react/storefront-api-types";
|
|
4
4
|
|
|
@@ -16,7 +16,7 @@ interface RedoCoverageClient {
|
|
|
16
16
|
get eligible(): boolean;
|
|
17
17
|
get price(): number | undefined;
|
|
18
18
|
get storeId(): string | undefined;
|
|
19
|
-
get cart(): CartReturn | CartWithActionsDocs | undefined;
|
|
19
|
+
get cart(): CartReturn | CartWithActionsDocs | OptimisticCart | undefined;
|
|
20
20
|
get cartProduct(): CartProductVariantFragment | undefined;
|
|
21
21
|
get cartAttribute(): CartAttributeKey | undefined;
|
|
22
22
|
get errors(): RedoError[] | undefined;
|
|
@@ -34,7 +34,7 @@ type RedoContextValue = {
|
|
|
34
34
|
loading: boolean,
|
|
35
35
|
storeId?: string,
|
|
36
36
|
cartInfoToEnable?: CartInfoToEnable,
|
|
37
|
-
cart?: CartReturn | CartWithActionsDocs,
|
|
37
|
+
cart?: CartReturn | CartWithActionsDocs | OptimisticCart,
|
|
38
38
|
errors?: RedoError[],
|
|
39
39
|
};
|
|
40
40
|
|
package/src/utils/cart.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { FetcherWithComponents, useFetcher } from "@remix-run/react";
|
|
2
2
|
import { CartInfoToEnable } from "../types";
|
|
3
|
-
import { CartForm, CartReturn } from "@shopify/hydrogen";
|
|
3
|
+
import { CartForm, CartReturn, OptimisticCart, OptimisticCartLine } from "@shopify/hydrogen";
|
|
4
4
|
import type { AppData } from '@remix-run/react/dist/data';
|
|
5
5
|
import React, { useCallback, useEffect, useRef } from 'react'
|
|
6
6
|
import { CartWithActionsDocs } from "@shopify/hydrogen-react/dist/types/cart-types";
|
|
@@ -8,22 +8,29 @@ import { CartLine, ComponentizableCartLine } from "@shopify/hydrogen-react/store
|
|
|
8
8
|
|
|
9
9
|
const DEFAULT_REDO_ENABLED_CART_ATTRIBUTE = 'redo_opted_in_from_cart';
|
|
10
10
|
|
|
11
|
-
const isCartWithActionsDocs = (cart: CartReturn | CartWithActionsDocs): cart is CartWithActionsDocs => {
|
|
11
|
+
const isCartWithActionsDocs = (cart: CartReturn | CartWithActionsDocs| OptimisticCart): cart is CartWithActionsDocs => {
|
|
12
12
|
return (Array.isArray(cart.lines) && 'linesAdd' in cart && typeof cart.linesAdd === 'function');
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
const getCartLines = (cart: CartReturn | CartWithActionsDocs): Array<CartLine | ComponentizableCartLine> => {
|
|
16
|
-
if(
|
|
15
|
+
const getCartLines = (cart: CartReturn | CartWithActionsDocs | OptimisticCart): Array<CartLine | ComponentizableCartLine> => {
|
|
16
|
+
if (isOptimisticCart(cart)) {
|
|
17
|
+
return cart.lines.nodes;
|
|
18
|
+
} else if (isCartWithActionsDocs(cart)) {
|
|
17
19
|
return cart.lines;
|
|
18
20
|
} else {
|
|
19
21
|
return cart.lines.nodes ?? cart.lines.edges.map((edge) => edge.node);
|
|
20
22
|
}
|
|
21
23
|
}
|
|
22
24
|
|
|
25
|
+
// https://shopify.dev/docs/api/hydrogen/2025-01/hooks/useoptimisticcart
|
|
26
|
+
const isOptimisticCart = (cart: CartReturn | CartWithActionsDocs | OptimisticCart): cart is OptimisticCart => {
|
|
27
|
+
return 'isOptimistic' in cart && (cart.isOptimistic ?? false);
|
|
28
|
+
}
|
|
29
|
+
|
|
23
30
|
const isRedoInCart = ({
|
|
24
31
|
cart
|
|
25
32
|
}: {
|
|
26
|
-
cart: CartReturn | CartWithActionsDocs
|
|
33
|
+
cart: CartReturn | CartWithActionsDocs | OptimisticCart
|
|
27
34
|
}): boolean => {
|
|
28
35
|
if(!cart) {
|
|
29
36
|
return false;
|
|
@@ -65,7 +72,7 @@ const addProductToCartIfNeeded = async ({
|
|
|
65
72
|
waitCartIdle,
|
|
66
73
|
cartInfoToEnable
|
|
67
74
|
}: {
|
|
68
|
-
cart: CartReturn | CartWithActionsDocs | undefined,
|
|
75
|
+
cart: CartReturn | CartWithActionsDocs | OptimisticCart | undefined,
|
|
69
76
|
fetcher: FetcherWithComponents<unknown>,
|
|
70
77
|
waitCartIdle: WaitCartIdleCallback;
|
|
71
78
|
cartInfoToEnable: CartInfoToEnable
|
|
@@ -101,7 +108,7 @@ const removeLinesFromCart = async ({
|
|
|
101
108
|
waitCartIdle,
|
|
102
109
|
lineIds
|
|
103
110
|
}: {
|
|
104
|
-
cart: CartReturn | CartWithActionsDocs | undefined;
|
|
111
|
+
cart: CartReturn | CartWithActionsDocs | OptimisticCart | undefined;
|
|
105
112
|
fetcher: FetcherWithComponents<unknown>;
|
|
106
113
|
waitCartIdle: WaitCartIdleCallback;
|
|
107
114
|
lineIds: string[];
|
|
@@ -132,7 +139,7 @@ const removeProductFromCartIfNeeded = async ({
|
|
|
132
139
|
waitCartIdle,
|
|
133
140
|
cartInfoToEnable
|
|
134
141
|
}: {
|
|
135
|
-
cart: CartReturn | CartWithActionsDocs | undefined,
|
|
142
|
+
cart: CartReturn | CartWithActionsDocs | OptimisticCart | undefined,
|
|
136
143
|
fetcher: FetcherWithComponents<unknown>,
|
|
137
144
|
waitCartIdle: WaitCartIdleCallback
|
|
138
145
|
cartInfoToEnable: CartInfoToEnable
|
|
@@ -159,7 +166,7 @@ const addProductToCart = async ({
|
|
|
159
166
|
cartInfoToEnable,
|
|
160
167
|
}: {
|
|
161
168
|
waitCartIdle: WaitCartIdleCallback;
|
|
162
|
-
cart: CartReturn | CartWithActionsDocs | undefined,
|
|
169
|
+
cart: CartReturn | CartWithActionsDocs | OptimisticCart | undefined,
|
|
163
170
|
fetcher: FetcherWithComponents<unknown>,
|
|
164
171
|
cartInfoToEnable: CartInfoToEnable
|
|
165
172
|
}) => {
|
|
@@ -197,7 +204,7 @@ const setCartRedoEnabledAttribute = async ({
|
|
|
197
204
|
cartInfoToEnable,
|
|
198
205
|
enabled
|
|
199
206
|
}: {
|
|
200
|
-
cart: CartReturn | CartWithActionsDocs | undefined;
|
|
207
|
+
cart: CartReturn | CartWithActionsDocs | OptimisticCart | undefined;
|
|
201
208
|
fetcher: FetcherWithComponents<unknown>;
|
|
202
209
|
waitCartIdle: WaitCartIdleCallback;
|
|
203
210
|
cartInfoToEnable: CartInfoToEnable | null;
|
|
@@ -270,17 +277,17 @@ function useFetcherWithPromise<TData = AppData>(opts?: Parameters<typeof useFetc
|
|
|
270
277
|
return { ...fetcher, submit }
|
|
271
278
|
}
|
|
272
279
|
|
|
273
|
-
type WaitCartIdleCallback = () => Promise<CartReturn | CartWithActionsDocs>;
|
|
280
|
+
type WaitCartIdleCallback = () => Promise<CartReturn | CartWithActionsDocs | OptimisticCart>;
|
|
274
281
|
|
|
275
282
|
// This function allows us to await a cart idle state without breaking React rules.
|
|
276
283
|
// It returns a function, which returns a promise, which will resolve once the cart value passed in reaches an idle state.
|
|
277
284
|
// Not intended for use with CartReturn, but will accept that value if passed in to avoid breaking rules of hooks
|
|
278
|
-
const useWaitCartIdle = (cart: CartReturn | CartWithActionsDocs | undefined) => {
|
|
285
|
+
const useWaitCartIdle = (cart: CartReturn | CartWithActionsDocs | OptimisticCart | undefined) => {
|
|
279
286
|
const resolveRef = useRef<any>(null)
|
|
280
287
|
const promiseRef = useRef<any>(null)
|
|
281
288
|
|
|
282
289
|
if (!promiseRef.current) {
|
|
283
|
-
promiseRef.current = new Promise<CartReturn | CartWithActionsDocs>((resolve) => {
|
|
290
|
+
promiseRef.current = new Promise<CartReturn | CartWithActionsDocs | OptimisticCart>((resolve) => {
|
|
284
291
|
resolveRef.current = resolve
|
|
285
292
|
})
|
|
286
293
|
}
|
|
@@ -327,5 +334,6 @@ export {
|
|
|
327
334
|
useFetcherWithPromise,
|
|
328
335
|
useWaitCartIdle,
|
|
329
336
|
isCartWithActionsDocs,
|
|
330
|
-
getCartLines
|
|
337
|
+
getCartLines,
|
|
338
|
+
isOptimisticCart
|
|
331
339
|
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<svg
|
|
2
|
+
width="24"
|
|
3
|
+
height="24"
|
|
4
|
+
viewBox="0 0 24 24"
|
|
5
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
6
|
+
>
|
|
7
|
+
<path
|
|
8
|
+
d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z"
|
|
9
|
+
fill="currentColor"
|
|
10
|
+
opacity=".25"
|
|
11
|
+
/>
|
|
12
|
+
<path
|
|
13
|
+
d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z"
|
|
14
|
+
fill="currentColor"
|
|
15
|
+
>
|
|
16
|
+
<animateTransform
|
|
17
|
+
attributeName="transform"
|
|
18
|
+
type="rotate"
|
|
19
|
+
dur="0.75s"
|
|
20
|
+
values="0 12 12;360 12 12"
|
|
21
|
+
repeatCount="indefinite"
|
|
22
|
+
/>
|
|
23
|
+
</path>
|
|
24
|
+
</svg>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export async function executeWithTimeout<T, E extends Error>(
|
|
2
|
+
promise: Promise<T>,
|
|
3
|
+
timeoutMs: number,
|
|
4
|
+
error: E = new Error("timeout") as E,
|
|
5
|
+
): Promise<T> {
|
|
6
|
+
return Promise.race([
|
|
7
|
+
promise,
|
|
8
|
+
new Promise<never>((_, reject) =>
|
|
9
|
+
setTimeout(() => reject(error), timeoutMs),
|
|
10
|
+
),
|
|
11
|
+
]);
|
|
12
|
+
}
|
package/tsconfig.json
CHANGED