@redotech/redo-hydrogen 1.4.6 → 2.0.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.
@@ -0,0 +1,39 @@
1
+ import { useEffect } from "react";
2
+ import { updatePurpleDotButtons, cleanupPurpleDotButtons } from "../utils/purple-dot";
3
+
4
+ /**
5
+ * React hook to disable add-to-cart buttons when a Purple Dot preorder element is present.
6
+ * Watches for DOM changes and automatically disables/enables buttons based on the presence
7
+ * of the <purple-dot-learn-more> element.
8
+ *
9
+ * @param disablePreorderButtons - When true, enables the preorder button disabling logic.
10
+ * When false, the hook does nothing.
11
+ *
12
+ * Usage:
13
+ * ```tsx
14
+ * function ProductPage() {
15
+ * const isShopOnSiteActive = true; // your condition here
16
+ * useDisablePurpleDotPreorder(isShopOnSiteActive);
17
+ * return <div>...</div>;
18
+ * }
19
+ * ```
20
+ */
21
+ export function useDisablePurpleDotPreorder(disablePreorderButtons: boolean): void {
22
+ useEffect(() => {
23
+ if (!disablePreorderButtons) {
24
+ return;
25
+ }
26
+
27
+ // Initial check
28
+ updatePurpleDotButtons();
29
+
30
+ // Watch for DOM changes (variant selection, page navigation, etc.)
31
+ const observer = new MutationObserver(updatePurpleDotButtons);
32
+ observer.observe(document.body, { childList: true, subtree: true });
33
+
34
+ return () => {
35
+ observer.disconnect();
36
+ cleanupPurpleDotButtons();
37
+ };
38
+ }, [disablePreorderButtons]);
39
+ }
package/src/index.ts CHANGED
@@ -1,9 +1,18 @@
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 { CartProductVariantFragment, CartAttributeKey, CartInfoToEnable, RedoContextValue, RedoCoverageClient, RedoError, RedoErrorType } from "./types";
5
- import { LoadState, Loader, useLoad } from './utils/react-utils'
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";
15
+ import { useDisablePurpleDotPreorder } from "./hooks/use-purple-dot-preorder";
7
16
 
8
17
  export {
9
18
  RedoCheckoutButtons,
@@ -12,7 +21,8 @@ export {
12
21
  useLoad,
13
22
  REDO_REQUIRED_HOSTNAMES,
14
23
  RedoErrorType,
15
- RedoInfoCard
24
+ RedoInfoCard,
25
+ useDisablePurpleDotPreorder,
16
26
  };
17
27
 
18
28
  export type {
@@ -23,5 +33,5 @@ export type {
23
33
  RedoCoverageClient,
24
34
  LoadState,
25
35
  Loader,
26
- RedoError
36
+ RedoError,
27
37
  };
@@ -1,52 +1,56 @@
1
- import { useFetcher } from "@remix-run/react";
2
1
  import { CartReturn, OptimisticCart } from "@shopify/hydrogen";
3
- import { createContext, ReactNode, useCallback, useContext, useEffect, useRef, useState } from "react";
4
- import { CartProductVariantFragment, CartAttributeKey, CartInfoToEnable, RedoContextValue, RedoCoverageClient, RedoError, RedoErrorType } from "../types";
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 { addProductToCartIfNeeded, removeProductFromCartIfNeeded, setCartRedoEnabledAttribute, useFetcherWithPromise, isCartWithActionsDocs, getCartLines, useWaitCartIdle, isOptimisticCart } from "../utils/cart";
5
+ import {
6
+ addProductToCartIfNeeded,
7
+ removeProductFromCartIfNeeded,
8
+ setCartRedoEnabledAttribute,
9
+ useFetcherWithPromise,
10
+ getCartLines,
11
+ useWaitCartIdle,
12
+ isOptimisticCart,
13
+ } from "../utils/cart";
7
14
  import { CartWithActionsDocs } from "@shopify/hydrogen-react/dist/types/cart-types";
8
15
 
9
16
  const DEFAULT_REDO_CONTEXT_VALUE: RedoContextValue = {
10
17
  enabled: false,
11
18
  loading: true,
12
- }
19
+ };
13
20
 
14
21
  const RedoContext = createContext<RedoContextValue>(DEFAULT_REDO_CONTEXT_VALUE);
15
22
 
16
23
  const RedoProvider = ({
17
24
  cart,
18
25
  storeId,
19
- children
26
+ children,
20
27
  }: {
21
- cart: CartReturn | CartWithActionsDocs | OptimisticCart,
22
- storeId: string,
23
- children: ReactNode,
28
+ cart: CartReturn | CartWithActionsDocs | OptimisticCart;
29
+ storeId: string;
30
+ children: ReactNode;
24
31
  }): ReactNode => {
25
- const [cartProduct, setCartProduct] = useState();
26
- const [cartAttribute, setCartAttribute] = useState<CartAttributeKey>();
27
32
  const [cartInfoToEnable, setCartInfoToEnable] = useState<CartInfoToEnable>();
28
33
  const [loading, setLoading] = useState<boolean>(true);
29
34
  const [errors, setErrors] = useState<RedoError[]>([]);
30
35
 
31
36
  const logUniqueError = (newError: RedoError) => {
32
- if(errors.find((err) => err.type === newError.type)) {
33
- } else {
37
+ if (!errors.find((err) => err.type === newError.type)) {
34
38
  setErrors([...errors, newError]);
35
39
  }
36
40
  return newError;
37
- }
41
+ };
38
42
 
39
43
  useEffect(() => {
40
- if(!cart || !storeId || isOptimisticCart(cart)) {
44
+ if (!cart || !storeId || isOptimisticCart(cart)) {
41
45
  return;
42
46
  }
43
47
 
44
- let cartLines = getCartLines(cart);
48
+ const cartLines = getCartLines(cart);
45
49
 
46
50
  fetch(`https://${REDO_PUBLIC_API_HOSTNAME}/v2.2/stores/${storeId}/coverage-products`, {
47
- method: 'POST',
51
+ method: "POST",
48
52
  headers: {
49
- "Content-Type": "application/json"
53
+ "Content-Type": "application/json",
50
54
  },
51
55
  body: JSON.stringify({
52
56
  cart: {
@@ -54,88 +58,85 @@ const RedoProvider = ({
54
58
  id: cartLine.id,
55
59
  originalPrice: {
56
60
  amount: cartLine.merchandise?.price?.amount,
57
- currency: cartLine.merchandise?.price?.currencyCode
61
+ currency: cartLine.merchandise?.price?.currencyCode,
58
62
  },
59
63
  priceTotal: {
60
64
  amount: cartLine.cost?.totalAmount?.amount,
61
- currency: cartLine.cost?.totalAmount?.currencyCode
65
+ currency: cartLine.cost?.totalAmount?.currencyCode,
62
66
  },
63
67
  product: {
64
- id: cartLine.merchandise?.product?.id
68
+ id: cartLine.merchandise?.product?.id,
65
69
  },
66
70
  variant: {
67
- id: cartLine.merchandise?.id
71
+ id: cartLine.merchandise?.id,
68
72
  },
69
73
  quantity: cartLine.quantity,
70
74
  })),
71
75
  priceTotal: {
72
76
  amount: cart.cost?.totalAmount?.amount,
73
- currency: cart.cost?.totalAmount?.currencyCode
77
+ currency: cart.cost?.totalAmount?.currencyCode,
74
78
  },
75
79
  },
76
80
  customer: {
77
- id: cart.buyerIdentity?.customer?.id || '',
78
- country: cart.buyerIdentity?.countryCode
79
- }
80
- })
81
- })
82
- .then(async (res) => {
83
- if(res.status === 500) {
81
+ id: cart.buyerIdentity?.customer?.id || "",
82
+ country: cart.buyerIdentity?.countryCode,
83
+ },
84
+ }),
85
+ }).then(async (res) => {
86
+ if (res.status === 500) {
84
87
  logUniqueError({
85
88
  type: RedoErrorType.ApiServerError,
86
- message: "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.",
89
+ message:
90
+ "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
91
  context: {
88
- json: await res.json()
89
- }
92
+ json: await res.json(),
93
+ },
90
94
  });
91
95
  return;
92
- } else if(res.status === 400) {
96
+ } else if (res.status === 400) {
93
97
  logUniqueError({
94
98
  type: RedoErrorType.ApiBadRequest,
95
- message: "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.",
99
+ message:
100
+ "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
101
  context: {
97
- json: await res.json()
98
- }
102
+ json: await res.json(),
103
+ },
99
104
  });
100
105
  return;
101
- } else if(res.status !== 200) {
106
+ } else if (res.status !== 200) {
102
107
  logUniqueError({
103
108
  type: RedoErrorType.ApiUnknownError,
104
109
  message: "Unkown error occured while getting available coverage products from Redo API.",
105
110
  context: {
106
111
  status: res.status,
107
- json: await res.json()
108
- }
112
+ json: await res.json(),
113
+ },
109
114
  });
110
115
  return;
111
116
  }
112
117
 
113
- let json = await res.json();
118
+ const json = await res.json();
114
119
 
115
120
  setLoading(false);
116
-
117
- if(!json?.coverageProducts?.[0]?.cartInfoToEnable) {
121
+
122
+ if (!json?.coverageProducts?.[0]?.cartInfoToEnable) {
118
123
  return;
119
124
  }
120
125
 
121
126
  setCartInfoToEnable(json.coverageProducts[0].cartInfoToEnable);
122
- })
127
+ });
123
128
  }, [cart, storeId]);
124
-
129
+
125
130
  const contextVal: RedoContextValue = {
126
131
  enabled: true,
127
132
  loading,
128
133
  storeId,
129
134
  cartInfoToEnable,
130
135
  cart,
131
- errors: (errors?.length && errors.length > 0) ? errors : undefined
136
+ errors: errors?.length && errors.length > 0 ? errors : undefined,
132
137
  };
133
138
 
134
- return (
135
- <RedoContext.Provider value={contextVal}>
136
- {children}
137
- </RedoContext.Provider>
138
- );
139
+ return <RedoContext.Provider value={contextVal}>{children}</RedoContext.Provider>;
139
140
  };
140
141
 
141
142
  const useRedoCoverageClient = (): RedoCoverageClient => {
@@ -144,23 +145,23 @@ const useRedoCoverageClient = (): RedoCoverageClient => {
144
145
  const waitCartIdle = useWaitCartIdle(redoContext.cart);
145
146
 
146
147
  useEffect(() => {
147
- if(redoContext.loading || !redoContext.cartInfoToEnable) {
148
+ if (redoContext.loading || !redoContext.cartInfoToEnable) {
148
149
  return;
149
150
  }
150
151
  removeProductFromCartIfNeeded({
151
152
  cart: redoContext.cart,
152
153
  fetcher,
153
154
  waitCartIdle,
154
- cartInfoToEnable: redoContext.cartInfoToEnable
155
+ cartInfoToEnable: redoContext.cartInfoToEnable,
155
156
  });
156
157
  }, [redoContext.loading]);
157
-
158
+
158
159
  return {
159
160
  enable: async () => {
160
- if(redoContext.loading || !redoContext.cartInfoToEnable) {
161
+ if (redoContext.loading || !redoContext.cartInfoToEnable) {
161
162
  return false;
162
163
  }
163
- let addProductResult = await addProductToCartIfNeeded({
164
+ await addProductToCartIfNeeded({
164
165
  fetcher,
165
166
  waitCartIdle,
166
167
  cart: redoContext.cart,
@@ -171,26 +172,26 @@ const useRedoCoverageClient = (): RedoCoverageClient => {
171
172
  fetcher,
172
173
  waitCartIdle,
173
174
  cartInfoToEnable: redoContext.cartInfoToEnable,
174
- enabled: true
175
+ enabled: true,
175
176
  });
176
177
  return true;
177
178
  },
178
179
  disable: async () => {
179
- if(!redoContext.cartInfoToEnable) {
180
+ if (!redoContext.cartInfoToEnable) {
180
181
  return false;
181
182
  }
182
183
  await removeProductFromCartIfNeeded({
183
184
  fetcher,
184
185
  waitCartIdle,
185
186
  cart: redoContext.cart,
186
- cartInfoToEnable: redoContext.cartInfoToEnable
187
+ cartInfoToEnable: redoContext.cartInfoToEnable,
187
188
  });
188
189
  await setCartRedoEnabledAttribute({
189
190
  cart: redoContext.cart,
190
191
  fetcher,
191
192
  waitCartIdle,
192
193
  cartInfoToEnable: redoContext.cartInfoToEnable,
193
- enabled: false
194
+ enabled: false,
194
195
  });
195
196
  return true;
196
197
  },
@@ -204,8 +205,8 @@ const useRedoCoverageClient = (): RedoCoverageClient => {
204
205
  return redoContext.enabled;
205
206
  },
206
207
  get price() {
207
- let priceToEnable = redoContext.cartInfoToEnable?.selectedVariant?.price?.amount;
208
- if(!priceToEnable || Number(priceToEnable).toString() === 'NaN') {
208
+ const priceToEnable = redoContext.cartInfoToEnable?.selectedVariant?.price?.amount;
209
+ if (!priceToEnable || Number(priceToEnable).toString() === "NaN") {
209
210
  return undefined;
210
211
  }
211
212
 
@@ -218,18 +219,15 @@ const useRedoCoverageClient = (): RedoCoverageClient => {
218
219
  return redoContext.cartInfoToEnable?.selectedVariant;
219
220
  },
220
221
  get cartAttribute() {
221
- return redoContext.cartInfoToEnable?.cartAttribute
222
+ return redoContext.cartInfoToEnable?.cartAttribute;
222
223
  },
223
224
  get storeId() {
224
225
  return redoContext.storeId;
225
226
  },
226
227
  get errors() {
227
228
  return redoContext.errors;
228
- }
229
- }
229
+ },
230
+ };
230
231
  };
231
232
 
232
- export {
233
- RedoProvider,
234
- useRedoCoverageClient
235
- }
233
+ export { RedoProvider, useRedoCoverageClient };
package/src/svg.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  declare module "*.svg" {
2
2
  import { JSX } from "react";
3
- function component(props: any): JSX.Element;
3
+ function component(props: React.SVGProps<SVGSVGElement>): JSX.Element;
4
4
  export default component;
5
- }
5
+ }
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<ProductVariant,
6
- "components" | "metafields" | "quantityPriceBreaks" | "quantityRule" | "requiresComponents" | "requiresShipping" | "storeAvailability" | "taxable" | "weightUnit"
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: any
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
+ };