@redotech/redo-hydrogen 1.0.2 → 1.1.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/dist/types.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { CartReturn, Storefront } from '@shopify/hydrogen';
1
+ import { CartReturn } from '@shopify/hydrogen';
2
2
  import { ReactNode, DependencyList } from 'react';
3
3
  import { ProductVariant } from '@shopify/hydrogen-react/storefront-api-types';
4
4
  import * as react_jsx_runtime from 'react/jsx-runtime';
@@ -10,9 +10,13 @@ interface RedoCoverageClient {
10
10
  disable(): Promise<boolean>;
11
11
  get loading(): boolean;
12
12
  get enabled(): boolean;
13
- get price(): number;
13
+ get eligible(): boolean;
14
+ get price(): number | undefined;
15
+ get storeId(): string | undefined;
16
+ get cart(): CartReturn | undefined;
14
17
  get cartProduct(): CartProductVariantFragment | undefined;
15
18
  get cartAttribute(): CartAttributeKey | undefined;
19
+ get errors(): RedoError[] | undefined;
16
20
  }
17
21
  type CartInfoToEnable = {
18
22
  productId: string;
@@ -26,6 +30,17 @@ type RedoContextValue = {
26
30
  storeId?: string;
27
31
  cartInfoToEnable?: CartInfoToEnable;
28
32
  cart?: CartReturn;
33
+ errors?: RedoError[];
34
+ };
35
+ declare enum RedoErrorType {
36
+ ApiBadRequest = "API_BAD_REQUEST",
37
+ ApiServerError = "API_SERVER_ERROR",
38
+ ApiUnknownError = "API_UNKNOWN_ERROR"
39
+ }
40
+ type RedoError = {
41
+ type: RedoErrorType;
42
+ message: string;
43
+ context: any;
29
44
  };
30
45
 
31
46
  declare const RedoProvider: ({ cart, storeId, children }: {
@@ -37,8 +52,6 @@ declare const useRedoCoverageClient: () => RedoCoverageClient;
37
52
 
38
53
  declare const RedoCheckoutButtons: (props: {
39
54
  cart: CartReturn;
40
- storefront: Storefront;
41
- storeId: string;
42
55
  children?: ReactNode;
43
56
  onClick?: (enabled: boolean) => void;
44
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.0.2",
3
+ "version": "1.1.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",
@@ -3,11 +3,11 @@ import {
3
3
  CartForm,
4
4
  CartActionInput,
5
5
  CartReturn,
6
- Storefront,
7
6
  } from "@shopify/hydrogen";
8
7
  import { useRedoCoverageClient } from "../providers/redo-coverage-client";
9
8
  import { CartInfoToEnable, RedoCoverageClient } from "../types";
10
9
  import { REDO_PUBLIC_API_HOSTNAME } from "../utils/security";
10
+ import { CurrencyCode } from "@shopify/hydrogen-react/storefront-api-types";
11
11
 
12
12
  type CheckoutButtonUIResponse = {
13
13
  html: string;
@@ -45,6 +45,10 @@ const getButtonsToShow = ({
45
45
  ui: json
46
46
  });
47
47
 
48
+ if(!ui) {
49
+ return reject(null);
50
+ }
51
+
48
52
  return resolve(ui);
49
53
  });
50
54
  });
@@ -59,10 +63,19 @@ const applyButtonVariables = ({
59
63
  cart: CartReturn,
60
64
  ui: CheckoutButtonUIResponse
61
65
  }): CheckoutButtonUIResponse | null => {
66
+ if(!redoCoverageClient.eligible || !redoCoverageClient.price) {
67
+ return null;
68
+ }
69
+
70
+ let currencyCode: CurrencyCode = cart.cost.totalAmount.currencyCode;
71
+ if(currencyCode === 'XXX') {
72
+ currencyCode = 'USD';
73
+ }
74
+
62
75
  const cartContainsRedo = !!(cart.lines.nodes.some((cartItem) => cartItem.merchandise?.product?.vendor === 're:do'));
63
76
  const combinedPrice = new Intl.NumberFormat('en-US', {
64
77
  style: 'currency',
65
- currency: cart.cost.totalAmount.currencyCode
78
+ currency: currencyCode
66
79
  }).format(Number(cart.cost.totalAmount.amount) + (cartContainsRedo ? 0 : redoCoverageClient.price));
67
80
 
68
81
  if(!combinedPrice || !combinedPrice.length || combinedPrice.includes('NaN')) {
@@ -89,14 +102,12 @@ const findAncestor = (
89
102
 
90
103
  const RedoCheckoutButtons = (props: {
91
104
  cart: CartReturn;
92
- storefront: Storefront;
93
- storeId: string;
94
105
  children?: ReactNode;
95
106
  onClick?: (enabled: boolean) => void;
96
107
  }) => {
97
108
  const redoCoverageClient = useRedoCoverageClient();
98
- let cart = props.cart;
99
- let checkoutUrl = cart.checkoutUrl;
109
+ let cart = redoCoverageClient.cart;
110
+ let checkoutUrl = redoCoverageClient.cart?.checkoutUrl || '/checkout';
100
111
  let [redoProductToAdd, setRedoProductToAdd] =
101
112
  useState<CartInfoToEnable | null>(null);
102
113
  let [checkoutButtonsUI, setCheckoutButtonsUI] = useState<CheckoutButtonUIResponse | null>(
@@ -105,12 +116,16 @@ const RedoCheckoutButtons = (props: {
105
116
 
106
117
  useEffect(() => {
107
118
  (async () => {
108
- const buttons = await getButtonsToShow({ redoCoverageClient, cart, storeId: props.storeId });
119
+ if(!redoCoverageClient.eligible || !cart || !redoCoverageClient.storeId) {
120
+ return;
121
+ }
122
+
123
+ const buttons = await getButtonsToShow({ redoCoverageClient, cart, storeId: redoCoverageClient.storeId });
109
124
  if(buttons) {
110
125
  setCheckoutButtonsUI(buttons);
111
126
  }
112
127
  })();
113
- }, [cart, redoCoverageClient.price]);
128
+ }, [cart, redoCoverageClient.eligible, redoCoverageClient.price, redoCoverageClient.storeId]);
114
129
 
115
130
  const wrapperClickHandler = async (e: MouseEvent) => {
116
131
  let clickedElement = e.target as HTMLElement;
@@ -1,7 +1,7 @@
1
1
  import { useFetcher } from "@remix-run/react";
2
2
  import { CartReturn } from "@shopify/hydrogen";
3
3
  import { createContext, ReactNode, useContext, useEffect, useState } from "react";
4
- import { CartProductVariantFragment, CartAttributeKey, CartInfoToEnable, RedoContextValue, RedoCoverageClient } from "../types";
4
+ import { CartProductVariantFragment, CartAttributeKey, CartInfoToEnable, RedoContextValue, RedoCoverageClient, RedoError, RedoErrorType } from "../types";
5
5
  import { REDO_PUBLIC_API_HOSTNAME } from "../utils/security";
6
6
  import { addProductToCartIfNeeded, removeProductFromCartIfNeeded, setCartRedoEnabledAttribute, useFetcherWithPromise } from "../utils/cart";
7
7
 
@@ -10,7 +10,7 @@ const DEFAULT_REDO_CONTEXT_VALUE: RedoContextValue = {
10
10
  loading: true,
11
11
  }
12
12
 
13
- const RedoContext = createContext(DEFAULT_REDO_CONTEXT_VALUE);
13
+ const RedoContext = createContext<RedoContextValue>(DEFAULT_REDO_CONTEXT_VALUE);
14
14
 
15
15
  const RedoProvider = ({
16
16
  cart,
@@ -25,8 +25,21 @@ const RedoProvider = ({
25
25
  const [cartAttribute, setCartAttribute] = useState<CartAttributeKey>();
26
26
  const [cartInfoToEnable, setCartInfoToEnable] = useState<CartInfoToEnable>();
27
27
  const [loading, setLoading] = useState<boolean>(true);
28
+ const [errors, setErrors] = useState<RedoError[]>([]);
29
+
30
+ const logUniqueError = (newError: RedoError) => {
31
+ if(errors.find((err) => err.type === newError.type)) {
32
+ } else {
33
+ setErrors([...errors, newError]);
34
+ }
35
+ return newError;
36
+ }
28
37
 
29
38
  useEffect(() => {
39
+ if(!cart || !storeId) {
40
+ return;
41
+ }
42
+
30
43
  fetch(`https://${REDO_PUBLIC_API_HOSTNAME}/v2.2/stores/${storeId}/coverage-products`, {
31
44
  method: 'POST',
32
45
  headers: {
@@ -64,13 +77,47 @@ const RedoProvider = ({
64
77
  })
65
78
  })
66
79
  .then(async (res) => {
80
+ if(res.status === 500) {
81
+ logUniqueError({
82
+ type: RedoErrorType.ApiServerError,
83
+ 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.",
84
+ context: {
85
+ json: await res.json()
86
+ }
87
+ });
88
+ return;
89
+ } else if(res.status === 400) {
90
+ logUniqueError({
91
+ type: RedoErrorType.ApiBadRequest,
92
+ 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.",
93
+ context: {
94
+ json: await res.json()
95
+ }
96
+ });
97
+ return;
98
+ } else if(res.status !== 200) {
99
+ logUniqueError({
100
+ type: RedoErrorType.ApiUnknownError,
101
+ message: "Unkown error occured while getting available coverage products from Redo API.",
102
+ context: {
103
+ status: res.status,
104
+ json: await res.json()
105
+ }
106
+ });
107
+ return;
108
+ }
109
+
67
110
  let json = await res.json();
68
111
 
69
112
  setLoading(false);
70
113
 
114
+ if(!json?.coverageProducts?.[0]?.cartInfoToEnable) {
115
+ return;
116
+ }
117
+
71
118
  setCartInfoToEnable(json.coverageProducts[0].cartInfoToEnable);
72
119
  })
73
- }, [cart]);
120
+ }, [cart, storeId]);
74
121
 
75
122
  const contextVal: RedoContextValue = {
76
123
  enabled: true,
@@ -78,6 +125,7 @@ const RedoProvider = ({
78
125
  storeId,
79
126
  cartInfoToEnable,
80
127
  cart,
128
+ errors: (errors?.length && errors.length > 0) ? errors : undefined
81
129
  };
82
130
 
83
131
  return (
@@ -138,17 +186,34 @@ const useRedoCoverageClient = (): RedoCoverageClient => {
138
186
  get loading() {
139
187
  return redoContext.loading;
140
188
  },
189
+ get eligible() {
190
+ return !this.loading && !!this.price && !!this.cartProduct;
191
+ },
141
192
  get enabled() {
142
193
  return redoContext.enabled;
143
194
  },
144
195
  get price() {
145
- return Number(redoContext.cartInfoToEnable?.selectedVariant.price.amount);
196
+ let priceToEnable = redoContext.cartInfoToEnable?.selectedVariant?.price?.amount;
197
+ if(!priceToEnable || Number(priceToEnable).toString() === 'NaN') {
198
+ return undefined;
199
+ }
200
+
201
+ return Number(priceToEnable);
202
+ },
203
+ get cart() {
204
+ return redoContext.cart;
146
205
  },
147
206
  get cartProduct() {
148
207
  return redoContext.cartInfoToEnable?.selectedVariant;
149
208
  },
150
209
  get cartAttribute() {
151
210
  return redoContext.cartInfoToEnable?.cartAttribute
211
+ },
212
+ get storeId() {
213
+ return redoContext.storeId;
214
+ },
215
+ get errors() {
216
+ return redoContext.errors;
152
217
  }
153
218
  }
154
219
  };
package/src/types.ts CHANGED
@@ -12,9 +12,13 @@ interface RedoCoverageClient {
12
12
  disable(): Promise<boolean>;
13
13
  get loading(): boolean;
14
14
  get enabled(): boolean;
15
- get price(): number;
16
- get cartProduct(): CartProductVariantFragment | undefined
17
- get cartAttribute(): CartAttributeKey | undefined
15
+ get eligible(): boolean;
16
+ get price(): number | undefined;
17
+ get storeId(): string | undefined;
18
+ get cart(): CartReturn | undefined;
19
+ get cartProduct(): CartProductVariantFragment | undefined;
20
+ get cartAttribute(): CartAttributeKey | undefined;
21
+ get errors(): RedoError[] | undefined;
18
22
  }
19
23
 
20
24
  type CartInfoToEnable = {
@@ -29,13 +33,31 @@ type RedoContextValue = {
29
33
  loading: boolean,
30
34
  storeId?: string,
31
35
  cartInfoToEnable?: CartInfoToEnable,
32
- cart?: CartReturn
36
+ cart?: CartReturn,
37
+ errors?: RedoError[],
33
38
  };
34
39
 
40
+ enum RedoErrorType {
41
+ ApiBadRequest = "API_BAD_REQUEST",
42
+ ApiServerError = "API_SERVER_ERROR",
43
+ ApiUnknownError = "API_UNKNOWN_ERROR"
44
+ };
45
+
46
+ type RedoError = {
47
+ type: RedoErrorType,
48
+ message: string,
49
+ context: any
50
+ };
51
+
52
+ export {
53
+ RedoErrorType,
54
+ }
55
+
35
56
  export type {
36
57
  CartAttributeKey,
37
58
  CartInfoToEnable,
38
59
  RedoContextValue,
39
60
  RedoCoverageClient,
40
- CartProductVariantFragment
61
+ CartProductVariantFragment,
62
+ RedoError
41
63
  }
package/src/utils/cart.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  import { FetcherWithComponents, useFetcher } from "@remix-run/react";
2
2
  import { CartInfoToEnable } from "../types";
3
3
  import { CartForm, CartReturn } from "@shopify/hydrogen";
4
- import { CartLine } from "@shopify/hydrogen-react/storefront-api-types";
5
4
  import type { AppData } from '@remix-run/react/dist/data';
6
5
  import React from 'react'
7
6