@redotech/redo-hydrogen 1.1.2 → 1.2.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/CHANGELOG.md +9 -0
- package/dist/cjs/index.js +1 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/types.d.ts +5 -4
- package/package.json +1 -1
- package/src/components/redo-checkout-buttons.tsx +7 -5
- package/src/providers/redo-coverage-client.tsx +9 -4
- package/src/types.ts +3 -2
- package/src/utils/cart.ts +88 -38
package/dist/types.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { CartReturn } from '@shopify/hydrogen';
|
|
2
2
|
import { ReactNode, DependencyList } from 'react';
|
|
3
|
+
import { CartWithActionsDocs } from '@shopify/hydrogen-react/dist/types/cart-types';
|
|
3
4
|
import { ProductVariant } from '@shopify/hydrogen-react/storefront-api-types';
|
|
4
5
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
5
6
|
|
|
@@ -13,7 +14,7 @@ interface RedoCoverageClient {
|
|
|
13
14
|
get eligible(): boolean;
|
|
14
15
|
get price(): number | undefined;
|
|
15
16
|
get storeId(): string | undefined;
|
|
16
|
-
get cart(): CartReturn | undefined;
|
|
17
|
+
get cart(): CartReturn | CartWithActionsDocs | undefined;
|
|
17
18
|
get cartProduct(): CartProductVariantFragment | undefined;
|
|
18
19
|
get cartAttribute(): CartAttributeKey | undefined;
|
|
19
20
|
get errors(): RedoError[] | undefined;
|
|
@@ -29,7 +30,7 @@ type RedoContextValue = {
|
|
|
29
30
|
loading: boolean;
|
|
30
31
|
storeId?: string;
|
|
31
32
|
cartInfoToEnable?: CartInfoToEnable;
|
|
32
|
-
cart?: CartReturn;
|
|
33
|
+
cart?: CartReturn | CartWithActionsDocs;
|
|
33
34
|
errors?: RedoError[];
|
|
34
35
|
};
|
|
35
36
|
declare enum RedoErrorType {
|
|
@@ -44,14 +45,14 @@ type RedoError = {
|
|
|
44
45
|
};
|
|
45
46
|
|
|
46
47
|
declare const RedoProvider: ({ cart, storeId, children }: {
|
|
47
|
-
cart: CartReturn;
|
|
48
|
+
cart: CartReturn | CartWithActionsDocs;
|
|
48
49
|
storeId: string;
|
|
49
50
|
children: ReactNode;
|
|
50
51
|
}) => ReactNode;
|
|
51
52
|
declare const useRedoCoverageClient: () => RedoCoverageClient;
|
|
52
53
|
|
|
53
54
|
declare const RedoCheckoutButtons: (props: {
|
|
54
|
-
cart: CartReturn;
|
|
55
|
+
cart: CartReturn | CartWithActionsDocs;
|
|
55
56
|
children?: ReactNode;
|
|
56
57
|
onClick?: (enabled: boolean) => void;
|
|
57
58
|
}) => react_jsx_runtime.JSX.Element;
|
package/package.json
CHANGED
|
@@ -8,6 +8,8 @@ import { useRedoCoverageClient } from "../providers/redo-coverage-client";
|
|
|
8
8
|
import { CartInfoToEnable, RedoCoverageClient } from "../types";
|
|
9
9
|
import { REDO_PUBLIC_API_HOSTNAME } from "../utils/security";
|
|
10
10
|
import { CurrencyCode } from "@shopify/hydrogen-react/storefront-api-types";
|
|
11
|
+
import { CartWithActionsDocs } from "@shopify/hydrogen-react/dist/types/cart-types";
|
|
12
|
+
import { getCartLines, isCartWithActionsDocs } from "../utils/cart";
|
|
11
13
|
|
|
12
14
|
type CheckoutButtonUIResponse = {
|
|
13
15
|
html: string;
|
|
@@ -20,7 +22,7 @@ const getButtonsToShow = ({
|
|
|
20
22
|
storeId
|
|
21
23
|
}: {
|
|
22
24
|
redoCoverageClient: RedoCoverageClient,
|
|
23
|
-
cart: CartReturn,
|
|
25
|
+
cart: CartReturn | CartWithActionsDocs,
|
|
24
26
|
storeId: string;
|
|
25
27
|
}): Promise<CheckoutButtonUIResponse | null> => {
|
|
26
28
|
return new Promise<CheckoutButtonUIResponse | null>((resolve, reject) => {
|
|
@@ -60,10 +62,10 @@ const applyButtonVariables = ({
|
|
|
60
62
|
ui
|
|
61
63
|
}: {
|
|
62
64
|
redoCoverageClient: RedoCoverageClient,
|
|
63
|
-
cart: CartReturn,
|
|
65
|
+
cart: CartReturn | CartWithActionsDocs,
|
|
64
66
|
ui: CheckoutButtonUIResponse
|
|
65
67
|
}): CheckoutButtonUIResponse | null => {
|
|
66
|
-
if(!redoCoverageClient.eligible || !redoCoverageClient.price) {
|
|
68
|
+
if(!redoCoverageClient.eligible || !redoCoverageClient.price || !cart?.cost) {
|
|
67
69
|
return null;
|
|
68
70
|
}
|
|
69
71
|
|
|
@@ -72,7 +74,7 @@ const applyButtonVariables = ({
|
|
|
72
74
|
currencyCode = 'USD';
|
|
73
75
|
}
|
|
74
76
|
|
|
75
|
-
const cartContainsRedo = !!(cart.
|
|
77
|
+
const cartContainsRedo = !!(getCartLines(cart).some((cartItem) => cartItem.merchandise?.product?.vendor === 're:do'));
|
|
76
78
|
const combinedPrice = new Intl.NumberFormat('en-US', {
|
|
77
79
|
style: 'currency',
|
|
78
80
|
currency: currencyCode
|
|
@@ -101,7 +103,7 @@ const findAncestor = (
|
|
|
101
103
|
};
|
|
102
104
|
|
|
103
105
|
const RedoCheckoutButtons = (props: {
|
|
104
|
-
cart: CartReturn;
|
|
106
|
+
cart: CartReturn | CartWithActionsDocs;
|
|
105
107
|
children?: ReactNode;
|
|
106
108
|
onClick?: (enabled: boolean) => void;
|
|
107
109
|
}) => {
|
|
@@ -3,7 +3,8 @@ import { CartReturn } from "@shopify/hydrogen";
|
|
|
3
3
|
import { createContext, ReactNode, useContext, useEffect, 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 } from "../utils/cart";
|
|
6
|
+
import { addProductToCartIfNeeded, removeProductFromCartIfNeeded, setCartRedoEnabledAttribute, useFetcherWithPromise, isCartWithActionsDocs, getCartLines } from "../utils/cart";
|
|
7
|
+
import { CartWithActionsDocs } from "@shopify/hydrogen-react/dist/types/cart-types";
|
|
7
8
|
|
|
8
9
|
const DEFAULT_REDO_CONTEXT_VALUE: RedoContextValue = {
|
|
9
10
|
enabled: false,
|
|
@@ -17,7 +18,7 @@ const RedoProvider = ({
|
|
|
17
18
|
storeId,
|
|
18
19
|
children
|
|
19
20
|
}: {
|
|
20
|
-
cart: CartReturn,
|
|
21
|
+
cart: CartReturn | CartWithActionsDocs,
|
|
21
22
|
storeId: string,
|
|
22
23
|
children: ReactNode,
|
|
23
24
|
}): ReactNode => {
|
|
@@ -40,6 +41,8 @@ const RedoProvider = ({
|
|
|
40
41
|
return;
|
|
41
42
|
}
|
|
42
43
|
|
|
44
|
+
let cartLines = getCartLines(cart);
|
|
45
|
+
|
|
43
46
|
fetch(`https://${REDO_PUBLIC_API_HOSTNAME}/v2.2/stores/${storeId}/coverage-products`, {
|
|
44
47
|
method: 'POST',
|
|
45
48
|
headers: {
|
|
@@ -47,7 +50,7 @@ const RedoProvider = ({
|
|
|
47
50
|
},
|
|
48
51
|
body: JSON.stringify({
|
|
49
52
|
cart: {
|
|
50
|
-
lineItems:
|
|
53
|
+
lineItems: cartLines.map((cartLine) => ({
|
|
51
54
|
id: cartLine.id,
|
|
52
55
|
originalPrice: {
|
|
53
56
|
amount: cartLine.merchandise?.price?.amount,
|
|
@@ -161,6 +164,7 @@ const useRedoCoverageClient = (): RedoCoverageClient => {
|
|
|
161
164
|
cartInfoToEnable: redoContext.cartInfoToEnable,
|
|
162
165
|
});
|
|
163
166
|
await setCartRedoEnabledAttribute({
|
|
167
|
+
cart: redoContext.cart,
|
|
164
168
|
fetcher,
|
|
165
169
|
cartInfoToEnable: redoContext.cartInfoToEnable,
|
|
166
170
|
enabled: true
|
|
@@ -177,6 +181,7 @@ const useRedoCoverageClient = (): RedoCoverageClient => {
|
|
|
177
181
|
cartInfoToEnable: redoContext.cartInfoToEnable
|
|
178
182
|
});
|
|
179
183
|
await setCartRedoEnabledAttribute({
|
|
184
|
+
cart: redoContext.cart,
|
|
180
185
|
fetcher,
|
|
181
186
|
cartInfoToEnable: redoContext.cartInfoToEnable,
|
|
182
187
|
enabled: false
|
|
@@ -187,7 +192,7 @@ const useRedoCoverageClient = (): RedoCoverageClient => {
|
|
|
187
192
|
return redoContext.loading;
|
|
188
193
|
},
|
|
189
194
|
get eligible() {
|
|
190
|
-
return !this.loading && !!this.price && !!this.cartProduct;
|
|
195
|
+
return !this.loading && !!this.price && !!this.cartProduct && !!this.cart?.cost;
|
|
191
196
|
},
|
|
192
197
|
get enabled() {
|
|
193
198
|
return redoContext.enabled;
|
package/src/types.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { CartReturn } from "@shopify/hydrogen";
|
|
2
|
+
import { CartWithActionsDocs } from "@shopify/hydrogen-react/dist/types/cart-types";
|
|
2
3
|
import { ProductVariant } from "@shopify/hydrogen-react/storefront-api-types";
|
|
3
4
|
|
|
4
5
|
type CartProductVariantFragment = Omit<ProductVariant,
|
|
@@ -15,7 +16,7 @@ interface RedoCoverageClient {
|
|
|
15
16
|
get eligible(): boolean;
|
|
16
17
|
get price(): number | undefined;
|
|
17
18
|
get storeId(): string | undefined;
|
|
18
|
-
get cart(): CartReturn | undefined;
|
|
19
|
+
get cart(): CartReturn | CartWithActionsDocs | undefined;
|
|
19
20
|
get cartProduct(): CartProductVariantFragment | undefined;
|
|
20
21
|
get cartAttribute(): CartAttributeKey | undefined;
|
|
21
22
|
get errors(): RedoError[] | undefined;
|
|
@@ -33,7 +34,7 @@ type RedoContextValue = {
|
|
|
33
34
|
loading: boolean,
|
|
34
35
|
storeId?: string,
|
|
35
36
|
cartInfoToEnable?: CartInfoToEnable,
|
|
36
|
-
cart?: CartReturn,
|
|
37
|
+
cart?: CartReturn | CartWithActionsDocs,
|
|
37
38
|
errors?: RedoError[],
|
|
38
39
|
};
|
|
39
40
|
|
package/src/utils/cart.ts
CHANGED
|
@@ -3,49 +3,76 @@ import { CartInfoToEnable } from "../types";
|
|
|
3
3
|
import { CartForm, CartReturn } from "@shopify/hydrogen";
|
|
4
4
|
import type { AppData } from '@remix-run/react/dist/data';
|
|
5
5
|
import React from 'react'
|
|
6
|
+
import { CartWithActionsDocs } from "@shopify/hydrogen-react/dist/types/cart-types";
|
|
7
|
+
import { CartLine, ComponentizableCartLine } from "@shopify/hydrogen-react/storefront-api-types";
|
|
6
8
|
|
|
7
9
|
const DEFAULT_REDO_ENABLED_CART_ATTRIBUTE = 'redo_opted_in_from_cart';
|
|
8
10
|
|
|
11
|
+
const isCartWithActionsDocs = (cart: CartReturn | CartWithActionsDocs): cart is CartWithActionsDocs => {
|
|
12
|
+
return (Array.isArray(cart.lines) && 'linesAdd' in cart && typeof cart.linesAdd === 'function');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const getCartLines = (cart: CartReturn | CartWithActionsDocs): Array<CartLine | ComponentizableCartLine> => {
|
|
16
|
+
if(isCartWithActionsDocs(cart)) {
|
|
17
|
+
return cart.lines;
|
|
18
|
+
} else {
|
|
19
|
+
return cart.lines.nodes ?? cart.lines.edges.map((edge) => edge.node);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const waitUntilCartIdle = (cart: CartWithActionsDocs): Promise<void> => {
|
|
24
|
+
return new Promise((resolve, reject) => {
|
|
25
|
+
let interval = setInterval(() => {
|
|
26
|
+
if(cart.status === 'idle') {
|
|
27
|
+
clearInterval(interval);
|
|
28
|
+
return resolve();
|
|
29
|
+
}
|
|
30
|
+
}, 100);
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
9
34
|
const addProductToCartIfNeeded = async ({
|
|
10
35
|
cart,
|
|
11
36
|
fetcher,
|
|
12
37
|
cartInfoToEnable
|
|
13
38
|
}: {
|
|
14
|
-
cart: CartReturn | undefined,
|
|
39
|
+
cart: CartReturn | CartWithActionsDocs | undefined,
|
|
15
40
|
fetcher: FetcherWithComponents<unknown>,
|
|
16
41
|
cartInfoToEnable: CartInfoToEnable
|
|
17
42
|
}) => {
|
|
18
43
|
if(!cart) {
|
|
19
|
-
return await addProductToCart({ fetcher, cartInfoToEnable });
|
|
44
|
+
return await addProductToCart({ cart, fetcher, cartInfoToEnable });
|
|
20
45
|
}
|
|
21
46
|
|
|
22
|
-
const redoProductsInCart = cart.
|
|
47
|
+
const redoProductsInCart = getCartLines(cart).filter((cartLine) => {
|
|
23
48
|
return cartLine.merchandise.product.vendor === 're:do';
|
|
24
49
|
});
|
|
25
50
|
const correctRedoProductInCart = redoProductsInCart?.filter((cartLine) => {
|
|
26
51
|
return cartLine.merchandise.id === `gid://shopify/ProductVariant/${cartInfoToEnable.variantId}`;
|
|
27
52
|
});
|
|
28
53
|
if(redoProductsInCart.length === 0) {
|
|
29
|
-
return await addProductToCart({ fetcher, cartInfoToEnable });
|
|
54
|
+
return await addProductToCart({ cart, fetcher, cartInfoToEnable });
|
|
30
55
|
} else if (redoProductsInCart.length === 1 && correctRedoProductInCart.length === 1 && correctRedoProductInCart[0].quantity === 1) {
|
|
31
56
|
// No action needed
|
|
32
57
|
return;
|
|
33
58
|
} else {
|
|
34
59
|
let isSuccess = true;
|
|
35
60
|
|
|
36
|
-
await removeLinesFromCart({ fetcher, lineIds: redoProductsInCart.map((cartLine) => cartLine.id) });
|
|
37
|
-
await addProductToCart({ fetcher, cartInfoToEnable });
|
|
61
|
+
await removeLinesFromCart({ cart, fetcher, lineIds: redoProductsInCart.map((cartLine) => cartLine.id) });
|
|
62
|
+
await addProductToCart({ cart, fetcher, cartInfoToEnable });
|
|
38
63
|
|
|
39
64
|
return;
|
|
40
65
|
}
|
|
41
66
|
};
|
|
42
67
|
|
|
43
68
|
const removeLinesFromCart = async ({
|
|
69
|
+
cart,
|
|
44
70
|
fetcher,
|
|
45
71
|
lineIds
|
|
46
72
|
}: {
|
|
47
|
-
|
|
48
|
-
|
|
73
|
+
cart: CartReturn | CartWithActionsDocs | undefined;
|
|
74
|
+
fetcher: FetcherWithComponents<unknown>;
|
|
75
|
+
lineIds: string[];
|
|
49
76
|
}) => {
|
|
50
77
|
const formInput = {
|
|
51
78
|
action: CartForm.ACTIONS.LinesRemove,
|
|
@@ -54,12 +81,17 @@ const removeLinesFromCart = async ({
|
|
|
54
81
|
}
|
|
55
82
|
}
|
|
56
83
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
84
|
+
if(cart && isCartWithActionsDocs(cart)) {
|
|
85
|
+
cart.linesRemove(lineIds);
|
|
86
|
+
await waitUntilCartIdle(cart);
|
|
87
|
+
} else {
|
|
88
|
+
await fetcher.submit(
|
|
89
|
+
{
|
|
90
|
+
[CartForm.INPUT_NAME]: JSON.stringify(formInput),
|
|
91
|
+
},
|
|
92
|
+
{method: 'POST', action: '/cart'},
|
|
93
|
+
);
|
|
94
|
+
}
|
|
63
95
|
};
|
|
64
96
|
|
|
65
97
|
const removeProductFromCartIfNeeded = async ({
|
|
@@ -67,7 +99,7 @@ const removeProductFromCartIfNeeded = async ({
|
|
|
67
99
|
fetcher,
|
|
68
100
|
cartInfoToEnable
|
|
69
101
|
}: {
|
|
70
|
-
cart: CartReturn | undefined,
|
|
102
|
+
cart: CartReturn | CartWithActionsDocs | undefined,
|
|
71
103
|
fetcher: FetcherWithComponents<unknown>,
|
|
72
104
|
cartInfoToEnable: CartInfoToEnable
|
|
73
105
|
}) => {
|
|
@@ -76,20 +108,22 @@ const removeProductFromCartIfNeeded = async ({
|
|
|
76
108
|
return;
|
|
77
109
|
}
|
|
78
110
|
|
|
79
|
-
const redoProductsInCart = cart.
|
|
111
|
+
const redoProductsInCart = getCartLines(cart).filter((cartLine) => {
|
|
80
112
|
return cartLine.merchandise.product.vendor === 're:do';
|
|
81
113
|
});
|
|
82
114
|
|
|
83
115
|
if(redoProductsInCart.length !== 0) {
|
|
84
|
-
await removeLinesFromCart({ fetcher, lineIds: redoProductsInCart.map((cartLine) => cartLine.id) });
|
|
116
|
+
await removeLinesFromCart({ cart, fetcher, lineIds: redoProductsInCart.map((cartLine) => cartLine.id) });
|
|
85
117
|
} else {
|
|
86
118
|
}
|
|
87
119
|
};
|
|
88
120
|
|
|
89
121
|
const addProductToCart = async ({
|
|
122
|
+
cart,
|
|
90
123
|
fetcher,
|
|
91
|
-
cartInfoToEnable
|
|
124
|
+
cartInfoToEnable,
|
|
92
125
|
}: {
|
|
126
|
+
cart: CartReturn | CartWithActionsDocs | undefined,
|
|
93
127
|
fetcher: FetcherWithComponents<unknown>,
|
|
94
128
|
cartInfoToEnable: CartInfoToEnable
|
|
95
129
|
}) => {
|
|
@@ -108,41 +142,55 @@ const addProductToCart = async ({
|
|
|
108
142
|
}
|
|
109
143
|
}
|
|
110
144
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
145
|
+
if(cart && isCartWithActionsDocs(cart)) {
|
|
146
|
+
cart.linesAdd([redoProductLine]);
|
|
147
|
+
await waitUntilCartIdle(cart);
|
|
148
|
+
} else {
|
|
149
|
+
await fetcher.submit(
|
|
150
|
+
{
|
|
151
|
+
[CartForm.INPUT_NAME]: JSON.stringify(formInput),
|
|
152
|
+
},
|
|
153
|
+
{method: 'POST', action: '/cart'},
|
|
154
|
+
);
|
|
155
|
+
}
|
|
117
156
|
};
|
|
118
157
|
|
|
119
158
|
const setCartRedoEnabledAttribute = async ({
|
|
159
|
+
cart,
|
|
120
160
|
fetcher,
|
|
121
161
|
cartInfoToEnable,
|
|
122
162
|
enabled
|
|
123
163
|
}: {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
164
|
+
cart: CartReturn | CartWithActionsDocs | undefined;
|
|
165
|
+
fetcher: FetcherWithComponents<unknown>;
|
|
166
|
+
cartInfoToEnable: CartInfoToEnable | null;
|
|
167
|
+
enabled: boolean;
|
|
127
168
|
}) => {
|
|
169
|
+
const redoCartAttribute = {
|
|
170
|
+
key: cartInfoToEnable?.cartAttribute || DEFAULT_REDO_ENABLED_CART_ATTRIBUTE,
|
|
171
|
+
value: enabled.toString()
|
|
172
|
+
};
|
|
173
|
+
|
|
128
174
|
const formInput = {
|
|
129
175
|
action: CartForm.ACTIONS.AttributesUpdateInput,
|
|
130
176
|
inputs: {
|
|
131
177
|
attributes: [
|
|
132
|
-
|
|
133
|
-
key: cartInfoToEnable?.cartAttribute || DEFAULT_REDO_ENABLED_CART_ATTRIBUTE,
|
|
134
|
-
value: enabled.toString()
|
|
135
|
-
}
|
|
178
|
+
redoCartAttribute
|
|
136
179
|
]
|
|
137
180
|
}
|
|
138
181
|
}
|
|
139
182
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
183
|
+
if(cart && isCartWithActionsDocs(cart)) {
|
|
184
|
+
cart.cartAttributesUpdate([redoCartAttribute]);
|
|
185
|
+
await waitUntilCartIdle(cart);
|
|
186
|
+
} else {
|
|
187
|
+
await fetcher.submit(
|
|
188
|
+
{
|
|
189
|
+
[CartForm.INPUT_NAME]: JSON.stringify(formInput),
|
|
190
|
+
},
|
|
191
|
+
{method: 'POST', action: '/cart'},
|
|
192
|
+
);
|
|
193
|
+
}
|
|
146
194
|
};
|
|
147
195
|
|
|
148
196
|
type FetcherData<T> = NonNullable<T | unknown> // FIXME: used to use SerializeFrom which is deprecated. Can this be better typed?
|
|
@@ -190,5 +238,7 @@ export {
|
|
|
190
238
|
addProductToCartIfNeeded,
|
|
191
239
|
removeProductFromCartIfNeeded,
|
|
192
240
|
setCartRedoEnabledAttribute,
|
|
193
|
-
useFetcherWithPromise
|
|
241
|
+
useFetcherWithPromise,
|
|
242
|
+
isCartWithActionsDocs,
|
|
243
|
+
getCartLines
|
|
194
244
|
};
|