@redotech/redo-hydrogen 1.4.7 → 1.4.8
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/.claude/settings.local.json +34 -0
- package/.github/workflows/ci.yml +56 -0
- package/.github/workflows/e2e.yml +72 -0
- package/.github/workflows/publish.yml +36 -0
- package/.prettierrc +7 -0
- package/.vscode/settings.json +10 -0
- package/CHANGELOG.md +4 -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 +5 -4
- package/eslint.config.mjs +22 -0
- package/package.json +13 -3
- package/src/components/redo-checkout-buttons.tsx +58 -74
- package/src/components/redo-info-modal.tsx +486 -345
- package/src/hooks/use-purple-dot-preorder.ts +1 -4
- package/src/index.ts +12 -4
- package/src/providers/redo-coverage-client.tsx +69 -70
- package/src/svg.d.ts +2 -2
- package/src/types.ts +30 -23
- package/src/utils/cart.ts +137 -147
- package/src/utils/purple-dot.ts +5 -11
- package/src/utils/react-utils.ts +9 -14
- package/src/utils/security.ts +3 -8
- package/src/utils/timeout.ts +1 -6
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import { useEffect } from "react";
|
|
2
|
-
import {
|
|
3
|
-
updatePurpleDotButtons,
|
|
4
|
-
cleanupPurpleDotButtons,
|
|
5
|
-
} from "../utils/purple-dot";
|
|
2
|
+
import { updatePurpleDotButtons, cleanupPurpleDotButtons } from "../utils/purple-dot";
|
|
6
3
|
|
|
7
4
|
/**
|
|
8
5
|
* React hook to disable add-to-cart buttons when a Purple Dot preorder element is present.
|
package/src/index.ts
CHANGED
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
import { RedoProvider, useRedoCoverageClient } from "./providers/redo-coverage-client";
|
|
2
2
|
import { RedoCheckoutButtons } from "./components/redo-checkout-buttons";
|
|
3
3
|
import { REDO_REQUIRED_HOSTNAMES } from "./utils/security";
|
|
4
|
-
import {
|
|
5
|
-
|
|
4
|
+
import {
|
|
5
|
+
CartProductVariantFragment,
|
|
6
|
+
CartAttributeKey,
|
|
7
|
+
CartInfoToEnable,
|
|
8
|
+
RedoContextValue,
|
|
9
|
+
RedoCoverageClient,
|
|
10
|
+
RedoError,
|
|
11
|
+
RedoErrorType,
|
|
12
|
+
} from "./types";
|
|
13
|
+
import { LoadState, Loader, useLoad } from "./utils/react-utils";
|
|
6
14
|
import { RedoInfoCard } from "./components/redo-info-modal";
|
|
7
15
|
import { useDisablePurpleDotPreorder } from "./hooks/use-purple-dot-preorder";
|
|
8
16
|
|
|
@@ -14,7 +22,7 @@ export {
|
|
|
14
22
|
REDO_REQUIRED_HOSTNAMES,
|
|
15
23
|
RedoErrorType,
|
|
16
24
|
RedoInfoCard,
|
|
17
|
-
useDisablePurpleDotPreorder
|
|
25
|
+
useDisablePurpleDotPreorder,
|
|
18
26
|
};
|
|
19
27
|
|
|
20
28
|
export type {
|
|
@@ -25,5 +33,5 @@ export type {
|
|
|
25
33
|
RedoCoverageClient,
|
|
26
34
|
LoadState,
|
|
27
35
|
Loader,
|
|
28
|
-
RedoError
|
|
36
|
+
RedoError,
|
|
29
37
|
};
|
|
@@ -1,52 +1,57 @@
|
|
|
1
|
-
import { useFetcher } from "@remix-run/react";
|
|
2
1
|
import { CartReturn, OptimisticCart } from "@shopify/hydrogen";
|
|
3
|
-
import { createContext, ReactNode,
|
|
4
|
-
import {
|
|
2
|
+
import { createContext, ReactNode, useContext, useEffect, useState } from "react";
|
|
3
|
+
import { CartInfoToEnable, RedoContextValue, RedoCoverageClient, RedoError, RedoErrorType } from "../types";
|
|
5
4
|
import { REDO_PUBLIC_API_HOSTNAME } from "../utils/security";
|
|
6
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
addProductToCartIfNeeded,
|
|
7
|
+
removeProductFromCartIfNeeded,
|
|
8
|
+
setCartRedoEnabledAttribute,
|
|
9
|
+
useFetcherWithPromise,
|
|
10
|
+
getCartLines,
|
|
11
|
+
getCartEligibilityPriceKey,
|
|
12
|
+
useWaitCartIdle,
|
|
13
|
+
isOptimisticCart,
|
|
14
|
+
} from "../utils/cart";
|
|
7
15
|
import { CartWithActionsDocs } from "@shopify/hydrogen-react/dist/types/cart-types";
|
|
8
16
|
|
|
9
17
|
const DEFAULT_REDO_CONTEXT_VALUE: RedoContextValue = {
|
|
10
18
|
enabled: false,
|
|
11
19
|
loading: true,
|
|
12
|
-
}
|
|
20
|
+
};
|
|
13
21
|
|
|
14
22
|
const RedoContext = createContext<RedoContextValue>(DEFAULT_REDO_CONTEXT_VALUE);
|
|
15
23
|
|
|
16
24
|
const RedoProvider = ({
|
|
17
25
|
cart,
|
|
18
26
|
storeId,
|
|
19
|
-
children
|
|
27
|
+
children,
|
|
20
28
|
}: {
|
|
21
|
-
cart: CartReturn | CartWithActionsDocs | OptimisticCart
|
|
22
|
-
storeId: string
|
|
23
|
-
children: ReactNode
|
|
29
|
+
cart: CartReturn | CartWithActionsDocs | OptimisticCart;
|
|
30
|
+
storeId: string;
|
|
31
|
+
children: ReactNode;
|
|
24
32
|
}): ReactNode => {
|
|
25
|
-
const [cartProduct, setCartProduct] = useState();
|
|
26
|
-
const [cartAttribute, setCartAttribute] = useState<CartAttributeKey>();
|
|
27
33
|
const [cartInfoToEnable, setCartInfoToEnable] = useState<CartInfoToEnable>();
|
|
28
34
|
const [loading, setLoading] = useState<boolean>(true);
|
|
29
35
|
const [errors, setErrors] = useState<RedoError[]>([]);
|
|
30
36
|
|
|
31
37
|
const logUniqueError = (newError: RedoError) => {
|
|
32
|
-
if(errors.find((err) => err.type === newError.type)) {
|
|
33
|
-
} else {
|
|
38
|
+
if (!errors.find((err) => err.type === newError.type)) {
|
|
34
39
|
setErrors([...errors, newError]);
|
|
35
40
|
}
|
|
36
41
|
return newError;
|
|
37
|
-
}
|
|
42
|
+
};
|
|
38
43
|
|
|
39
44
|
useEffect(() => {
|
|
40
|
-
if(!cart || !storeId || isOptimisticCart(cart)) {
|
|
45
|
+
if (!cart || !storeId || isOptimisticCart(cart)) {
|
|
41
46
|
return;
|
|
42
47
|
}
|
|
43
48
|
|
|
44
|
-
|
|
49
|
+
const cartLines = getCartLines(cart);
|
|
45
50
|
|
|
46
51
|
fetch(`https://${REDO_PUBLIC_API_HOSTNAME}/v2.2/stores/${storeId}/coverage-products`, {
|
|
47
|
-
method:
|
|
52
|
+
method: "POST",
|
|
48
53
|
headers: {
|
|
49
|
-
"Content-Type": "application/json"
|
|
54
|
+
"Content-Type": "application/json",
|
|
50
55
|
},
|
|
51
56
|
body: JSON.stringify({
|
|
52
57
|
cart: {
|
|
@@ -54,88 +59,85 @@ const RedoProvider = ({
|
|
|
54
59
|
id: cartLine.id,
|
|
55
60
|
originalPrice: {
|
|
56
61
|
amount: cartLine.merchandise?.price?.amount,
|
|
57
|
-
currency: cartLine.merchandise?.price?.currencyCode
|
|
62
|
+
currency: cartLine.merchandise?.price?.currencyCode,
|
|
58
63
|
},
|
|
59
64
|
priceTotal: {
|
|
60
65
|
amount: cartLine.cost?.totalAmount?.amount,
|
|
61
|
-
currency: cartLine.cost?.totalAmount?.currencyCode
|
|
66
|
+
currency: cartLine.cost?.totalAmount?.currencyCode,
|
|
62
67
|
},
|
|
63
68
|
product: {
|
|
64
|
-
id: cartLine.merchandise?.product?.id
|
|
69
|
+
id: cartLine.merchandise?.product?.id,
|
|
65
70
|
},
|
|
66
71
|
variant: {
|
|
67
|
-
id: cartLine.merchandise?.id
|
|
72
|
+
id: cartLine.merchandise?.id,
|
|
68
73
|
},
|
|
69
74
|
quantity: cartLine.quantity,
|
|
70
75
|
})),
|
|
71
76
|
priceTotal: {
|
|
72
77
|
amount: cart.cost?.totalAmount?.amount,
|
|
73
|
-
currency: cart.cost?.totalAmount?.currencyCode
|
|
78
|
+
currency: cart.cost?.totalAmount?.currencyCode,
|
|
74
79
|
},
|
|
75
80
|
},
|
|
76
81
|
customer: {
|
|
77
|
-
id: cart.buyerIdentity?.customer?.id ||
|
|
78
|
-
country: cart.buyerIdentity?.countryCode
|
|
79
|
-
}
|
|
80
|
-
})
|
|
81
|
-
})
|
|
82
|
-
|
|
83
|
-
if(res.status === 500) {
|
|
82
|
+
id: cart.buyerIdentity?.customer?.id || "",
|
|
83
|
+
country: cart.buyerIdentity?.countryCode,
|
|
84
|
+
},
|
|
85
|
+
}),
|
|
86
|
+
}).then(async (res) => {
|
|
87
|
+
if (res.status === 500) {
|
|
84
88
|
logUniqueError({
|
|
85
89
|
type: RedoErrorType.ApiServerError,
|
|
86
|
-
message:
|
|
90
|
+
message:
|
|
91
|
+
"Internal server error occured when getting available coverage products from Redo API.. Check your inputs are correct and storeId have been configured. Reach out to Redo support if the issue persists.",
|
|
87
92
|
context: {
|
|
88
|
-
json: await res.json()
|
|
89
|
-
}
|
|
93
|
+
json: await res.json(),
|
|
94
|
+
},
|
|
90
95
|
});
|
|
91
96
|
return;
|
|
92
|
-
} else if(res.status === 400) {
|
|
97
|
+
} else if (res.status === 400) {
|
|
93
98
|
logUniqueError({
|
|
94
99
|
type: RedoErrorType.ApiBadRequest,
|
|
95
|
-
message:
|
|
100
|
+
message:
|
|
101
|
+
"Bad request when getting available coverage products from Redo API. Check that the passed in cart is of the correct type Cart/CartReturn and includes all of the correct cart information.",
|
|
96
102
|
context: {
|
|
97
|
-
json: await res.json()
|
|
98
|
-
}
|
|
103
|
+
json: await res.json(),
|
|
104
|
+
},
|
|
99
105
|
});
|
|
100
106
|
return;
|
|
101
|
-
} else if(res.status !== 200) {
|
|
107
|
+
} else if (res.status !== 200) {
|
|
102
108
|
logUniqueError({
|
|
103
109
|
type: RedoErrorType.ApiUnknownError,
|
|
104
110
|
message: "Unkown error occured while getting available coverage products from Redo API.",
|
|
105
111
|
context: {
|
|
106
112
|
status: res.status,
|
|
107
|
-
json: await res.json()
|
|
108
|
-
}
|
|
113
|
+
json: await res.json(),
|
|
114
|
+
},
|
|
109
115
|
});
|
|
110
116
|
return;
|
|
111
117
|
}
|
|
112
118
|
|
|
113
|
-
|
|
119
|
+
const json = await res.json();
|
|
114
120
|
|
|
115
121
|
setLoading(false);
|
|
116
|
-
|
|
117
|
-
if(!json?.coverageProducts?.[0]?.cartInfoToEnable) {
|
|
122
|
+
|
|
123
|
+
if (!json?.coverageProducts?.[0]?.cartInfoToEnable) {
|
|
118
124
|
return;
|
|
119
125
|
}
|
|
120
126
|
|
|
121
127
|
setCartInfoToEnable(json.coverageProducts[0].cartInfoToEnable);
|
|
122
|
-
})
|
|
123
|
-
}, [cart, storeId]);
|
|
124
|
-
|
|
128
|
+
});
|
|
129
|
+
}, [getCartEligibilityPriceKey(cart), storeId]);
|
|
130
|
+
|
|
125
131
|
const contextVal: RedoContextValue = {
|
|
126
132
|
enabled: true,
|
|
127
133
|
loading,
|
|
128
134
|
storeId,
|
|
129
135
|
cartInfoToEnable,
|
|
130
136
|
cart,
|
|
131
|
-
errors:
|
|
137
|
+
errors: errors?.length && errors.length > 0 ? errors : undefined,
|
|
132
138
|
};
|
|
133
139
|
|
|
134
|
-
return
|
|
135
|
-
<RedoContext.Provider value={contextVal}>
|
|
136
|
-
{children}
|
|
137
|
-
</RedoContext.Provider>
|
|
138
|
-
);
|
|
140
|
+
return <RedoContext.Provider value={contextVal}>{children}</RedoContext.Provider>;
|
|
139
141
|
};
|
|
140
142
|
|
|
141
143
|
const useRedoCoverageClient = (): RedoCoverageClient => {
|
|
@@ -144,23 +146,23 @@ const useRedoCoverageClient = (): RedoCoverageClient => {
|
|
|
144
146
|
const waitCartIdle = useWaitCartIdle(redoContext.cart);
|
|
145
147
|
|
|
146
148
|
useEffect(() => {
|
|
147
|
-
if(redoContext.loading || !redoContext.cartInfoToEnable) {
|
|
149
|
+
if (redoContext.loading || !redoContext.cartInfoToEnable) {
|
|
148
150
|
return;
|
|
149
151
|
}
|
|
150
152
|
removeProductFromCartIfNeeded({
|
|
151
153
|
cart: redoContext.cart,
|
|
152
154
|
fetcher,
|
|
153
155
|
waitCartIdle,
|
|
154
|
-
cartInfoToEnable: redoContext.cartInfoToEnable
|
|
156
|
+
cartInfoToEnable: redoContext.cartInfoToEnable,
|
|
155
157
|
});
|
|
156
158
|
}, [redoContext.loading]);
|
|
157
|
-
|
|
159
|
+
|
|
158
160
|
return {
|
|
159
161
|
enable: async () => {
|
|
160
|
-
if(redoContext.loading || !redoContext.cartInfoToEnable) {
|
|
162
|
+
if (redoContext.loading || !redoContext.cartInfoToEnable) {
|
|
161
163
|
return false;
|
|
162
164
|
}
|
|
163
|
-
|
|
165
|
+
await addProductToCartIfNeeded({
|
|
164
166
|
fetcher,
|
|
165
167
|
waitCartIdle,
|
|
166
168
|
cart: redoContext.cart,
|
|
@@ -171,26 +173,26 @@ const useRedoCoverageClient = (): RedoCoverageClient => {
|
|
|
171
173
|
fetcher,
|
|
172
174
|
waitCartIdle,
|
|
173
175
|
cartInfoToEnable: redoContext.cartInfoToEnable,
|
|
174
|
-
enabled: true
|
|
176
|
+
enabled: true,
|
|
175
177
|
});
|
|
176
178
|
return true;
|
|
177
179
|
},
|
|
178
180
|
disable: async () => {
|
|
179
|
-
if(!redoContext.cartInfoToEnable) {
|
|
181
|
+
if (!redoContext.cartInfoToEnable) {
|
|
180
182
|
return false;
|
|
181
183
|
}
|
|
182
184
|
await removeProductFromCartIfNeeded({
|
|
183
185
|
fetcher,
|
|
184
186
|
waitCartIdle,
|
|
185
187
|
cart: redoContext.cart,
|
|
186
|
-
cartInfoToEnable: redoContext.cartInfoToEnable
|
|
188
|
+
cartInfoToEnable: redoContext.cartInfoToEnable,
|
|
187
189
|
});
|
|
188
190
|
await setCartRedoEnabledAttribute({
|
|
189
191
|
cart: redoContext.cart,
|
|
190
192
|
fetcher,
|
|
191
193
|
waitCartIdle,
|
|
192
194
|
cartInfoToEnable: redoContext.cartInfoToEnable,
|
|
193
|
-
enabled: false
|
|
195
|
+
enabled: false,
|
|
194
196
|
});
|
|
195
197
|
return true;
|
|
196
198
|
},
|
|
@@ -204,8 +206,8 @@ const useRedoCoverageClient = (): RedoCoverageClient => {
|
|
|
204
206
|
return redoContext.enabled;
|
|
205
207
|
},
|
|
206
208
|
get price() {
|
|
207
|
-
|
|
208
|
-
if(!priceToEnable || Number(priceToEnable).toString() ===
|
|
209
|
+
const priceToEnable = redoContext.cartInfoToEnable?.selectedVariant?.price?.amount;
|
|
210
|
+
if (!priceToEnable || Number(priceToEnable).toString() === "NaN") {
|
|
209
211
|
return undefined;
|
|
210
212
|
}
|
|
211
213
|
|
|
@@ -218,18 +220,15 @@ const useRedoCoverageClient = (): RedoCoverageClient => {
|
|
|
218
220
|
return redoContext.cartInfoToEnable?.selectedVariant;
|
|
219
221
|
},
|
|
220
222
|
get cartAttribute() {
|
|
221
|
-
return redoContext.cartInfoToEnable?.cartAttribute
|
|
223
|
+
return redoContext.cartInfoToEnable?.cartAttribute;
|
|
222
224
|
},
|
|
223
225
|
get storeId() {
|
|
224
226
|
return redoContext.storeId;
|
|
225
227
|
},
|
|
226
228
|
get errors() {
|
|
227
229
|
return redoContext.errors;
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
+
},
|
|
231
|
+
};
|
|
230
232
|
};
|
|
231
233
|
|
|
232
|
-
export {
|
|
233
|
-
RedoProvider,
|
|
234
|
-
useRedoCoverageClient
|
|
235
|
-
}
|
|
234
|
+
export { RedoProvider, useRedoCoverageClient };
|
package/src/svg.d.ts
CHANGED
package/src/types.ts
CHANGED
|
@@ -2,8 +2,17 @@ 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
|
|
|
5
|
-
type CartProductVariantFragment = Omit<
|
|
6
|
-
|
|
5
|
+
type CartProductVariantFragment = Omit<
|
|
6
|
+
ProductVariant,
|
|
7
|
+
| "components"
|
|
8
|
+
| "metafields"
|
|
9
|
+
| "quantityPriceBreaks"
|
|
10
|
+
| "quantityRule"
|
|
11
|
+
| "requiresComponents"
|
|
12
|
+
| "requiresShipping"
|
|
13
|
+
| "storeAvailability"
|
|
14
|
+
| "taxable"
|
|
15
|
+
| "weightUnit"
|
|
7
16
|
>;
|
|
8
17
|
|
|
9
18
|
type CartAttributeKey = string;
|
|
@@ -23,36 +32,34 @@ interface RedoCoverageClient {
|
|
|
23
32
|
}
|
|
24
33
|
|
|
25
34
|
type CartInfoToEnable = {
|
|
26
|
-
productId: string
|
|
27
|
-
variantId: string
|
|
28
|
-
cartAttribute: CartAttributeKey
|
|
29
|
-
selectedVariant: CartProductVariantFragment
|
|
30
|
-
}
|
|
35
|
+
productId: string;
|
|
36
|
+
variantId: string;
|
|
37
|
+
cartAttribute: CartAttributeKey;
|
|
38
|
+
selectedVariant: CartProductVariantFragment;
|
|
39
|
+
};
|
|
31
40
|
|
|
32
41
|
type RedoContextValue = {
|
|
33
|
-
enabled: boolean
|
|
34
|
-
loading: boolean
|
|
35
|
-
storeId?: string
|
|
36
|
-
cartInfoToEnable?: CartInfoToEnable
|
|
37
|
-
cart?: CartReturn | CartWithActionsDocs | OptimisticCart
|
|
38
|
-
errors?: RedoError[]
|
|
42
|
+
enabled: boolean;
|
|
43
|
+
loading: boolean;
|
|
44
|
+
storeId?: string;
|
|
45
|
+
cartInfoToEnable?: CartInfoToEnable;
|
|
46
|
+
cart?: CartReturn | CartWithActionsDocs | OptimisticCart;
|
|
47
|
+
errors?: RedoError[];
|
|
39
48
|
};
|
|
40
49
|
|
|
41
50
|
enum RedoErrorType {
|
|
42
51
|
ApiBadRequest = "API_BAD_REQUEST",
|
|
43
52
|
ApiServerError = "API_SERVER_ERROR",
|
|
44
|
-
ApiUnknownError = "API_UNKNOWN_ERROR"
|
|
45
|
-
}
|
|
53
|
+
ApiUnknownError = "API_UNKNOWN_ERROR",
|
|
54
|
+
}
|
|
46
55
|
|
|
47
56
|
type RedoError = {
|
|
48
|
-
type: RedoErrorType
|
|
49
|
-
message: string
|
|
50
|
-
context:
|
|
57
|
+
type: RedoErrorType;
|
|
58
|
+
message: string;
|
|
59
|
+
context: Record<string, unknown>;
|
|
51
60
|
};
|
|
52
61
|
|
|
53
|
-
export {
|
|
54
|
-
RedoErrorType,
|
|
55
|
-
}
|
|
62
|
+
export { RedoErrorType };
|
|
56
63
|
|
|
57
64
|
export type {
|
|
58
65
|
CartAttributeKey,
|
|
@@ -60,5 +67,5 @@ export type {
|
|
|
60
67
|
RedoContextValue,
|
|
61
68
|
RedoCoverageClient,
|
|
62
69
|
CartProductVariantFragment,
|
|
63
|
-
RedoError
|
|
64
|
-
}
|
|
70
|
+
RedoError,
|
|
71
|
+
};
|