@rebuy/rebuy-hydrogen 2.3.1 → 3.0.0-beta.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.
Files changed (64) hide show
  1. package/package.json +52 -26
  2. package/src/components/AddToCartBtn/AddToCartBtn.tsx +45 -0
  3. package/src/components/AddToCartBtn/HydrogenAddToCartBtn.tsx +43 -0
  4. package/src/components/AddToCartBtn/HydrogenReactAddToCartBtn.tsx +35 -0
  5. package/src/components/AddToCartBtn/index.ts +1 -0
  6. package/src/components/AddToCartBtn/types.ts +27 -0
  7. package/src/components/ProductCard/ProductCard.tsx +70 -0
  8. package/src/components/ProductCard/index.ts +1 -0
  9. package/src/components/ProductCard/types.ts +10 -0
  10. package/src/components/ProductPrice/ProductPrice.tsx +49 -0
  11. package/src/components/ProductPrice/index.ts +1 -0
  12. package/src/components/Title/Title.tsx +20 -0
  13. package/src/components/Title/index.ts +1 -0
  14. package/src/components/Title/types.ts +7 -0
  15. package/src/components/VariantSelect/VariantSelect.tsx +45 -0
  16. package/src/components/VariantSelect/index.ts +1 -0
  17. package/src/components/VariantSelect/types.ts +6 -0
  18. package/src/context/RebuyContext.tsx +9 -0
  19. package/src/hooks/titleLevel.tsx +42 -0
  20. package/src/index.ts +7 -0
  21. package/src/providers/RebuyHydrogenContextProvider.tsx +112 -0
  22. package/src/providers/RebuyHydrogenReactContextProvider.tsx +192 -0
  23. package/src/providers/types.ts +58 -0
  24. package/src/queries/cart.queries.ts +467 -0
  25. package/src/types/common.ts +8 -0
  26. package/src/types/css.d.ts +11 -0
  27. package/src/types/env.d.ts +12 -0
  28. package/src/types/rebuy.d.ts +31 -0
  29. package/src/types/rebuyCustom.ts +263 -0
  30. package/src/types/rebuySmartCart.ts +188 -0
  31. package/src/types/shopify.ts +142 -0
  32. package/src/types/widgets.ts +29 -0
  33. package/src/utils/convertToRebuyProduct.tsx +319 -0
  34. package/src/utils/createContextParameters.ts +142 -0
  35. package/src/utils/getEncodedAttributes.ts +11 -0
  36. package/src/utils/getRebuyConfig.ts +31 -0
  37. package/src/widgetContainer/RebuyWidgetContainer.tsx +183 -0
  38. package/src/widgets/RebuyCompleteTheLook/RebuyCompleteTheLook.tsx +50 -0
  39. package/src/widgets/RebuyCompleteTheLook/index.ts +1 -0
  40. package/src/widgets/RebuyCompleteTheLook/types.ts +5 -0
  41. package/src/widgets/RebuyDynamicBundleProducts/BundleImages.tsx +62 -0
  42. package/src/widgets/RebuyDynamicBundleProducts/BundlePrice.tsx +93 -0
  43. package/src/widgets/RebuyDynamicBundleProducts/BundleSelection.tsx +65 -0
  44. package/src/widgets/RebuyDynamicBundleProducts/RebuyDynamicBundleProducts.tsx +118 -0
  45. package/src/widgets/RebuyDynamicBundleProducts/Select.tsx +41 -0
  46. package/src/widgets/RebuyDynamicBundleProducts/index.ts +1 -0
  47. package/src/widgets/RebuyDynamicBundleProducts/types.ts +23 -0
  48. package/src/widgets/RebuyProductAddOns/RebuyProductAddOnCard.tsx +66 -0
  49. package/src/widgets/RebuyProductAddOns/RebuyProductAddOns.tsx +218 -0
  50. package/src/widgets/RebuyProductAddOns/index.ts +1 -0
  51. package/src/widgets/RebuyProductAddOns/types.ts +24 -0
  52. package/src/widgets/RebuyProductRecommendations/RebuyProductRecommendations.tsx +50 -0
  53. package/src/widgets/RebuyProductRecommendations/index.ts +1 -0
  54. package/src/widgets/RebuyProductRecommendations/types.ts +5 -0
  55. package/RebuyCompleteTheLook.client.jsx +0 -188
  56. package/RebuyContextProvider.client.jsx +0 -222
  57. package/RebuyContexts.client.jsx +0 -3
  58. package/RebuyDynamicBundleProducts.client.jsx +0 -415
  59. package/RebuyProductAddOnCard.client.jsx +0 -89
  60. package/RebuyProductAddOns.client.jsx +0 -227
  61. package/RebuyProductRecommendations.client.jsx +0 -68
  62. package/RebuyProductViewed.client.jsx +0 -62
  63. package/RebuyRecentlyViewedProducts.client.jsx +0 -68
  64. package/RebuyWidgetContainer.client.jsx +0 -136
@@ -0,0 +1,218 @@
1
+ import { Money } from '@shopify/hydrogen-react';
2
+ import { useCallback, useEffect, useState } from 'react';
3
+
4
+ import { RebuyProductAddOnCard } from './RebuyProductAddOnCard';
5
+
6
+ import styles from './RebuyProductAddOns.module.css';
7
+
8
+ import type {
9
+ CurrencyCode,
10
+ MoneyV2,
11
+ } from '@shopify/hydrogen-react/storefront-api-types';
12
+ import type { RebuyProduct } from '~/types/rebuyCustom';
13
+ import type { RebuyProductAddOnProps } from '~/widgets/RebuyProductAddOns/types';
14
+
15
+ import { AddToCartBtn } from '~/components/AddToCartBtn';
16
+ import { Title } from '~/components/Title';
17
+ import { getTitleLevel } from '~/hooks/titleLevel';
18
+ import { convertToRebuyProduct } from '~/utils/convertToRebuyProduct';
19
+
20
+ export const RebuyProductAddOns = (props: RebuyProductAddOnProps) => {
21
+ const {
22
+ addToCartCallback,
23
+ customTitle = `These pair with ${props.product?.title}`,
24
+ customTitleLevel = 'h2',
25
+ customTitleStyle,
26
+ includeMainProduct = false,
27
+ isHydrogenReact,
28
+ learnMoreText = 'Learn more',
29
+ outOfStockText = 'Out of stock',
30
+ product,
31
+ products = [],
32
+ addToCartBtnText = 'Add to cart',
33
+ subtotalText = 'Add-ons Subtotal: ',
34
+ withProductText = `With ${product?.title}: `,
35
+ } = props;
36
+
37
+ const [addedItems, setAddedItems] = useState<RebuyProduct[]>(products);
38
+
39
+ const [subtotalWithProduct, setSubtotalWithProduct] = useState<MoneyV2>();
40
+ const [subtotalWithOutProduct, setSubtotalWithOutProduct] =
41
+ useState<MoneyV2>();
42
+
43
+ useEffect(() => {
44
+ let initialTotal = 0;
45
+ let currencyCode = 'USD' as CurrencyCode;
46
+
47
+ products.map((product) => {
48
+ product.selectedVariant = product.variants.nodes[0];
49
+ product.selected = true;
50
+
51
+ if (product.selectedVariant?.priceV2) {
52
+ initialTotal += Number(product.selectedVariant.priceV2.amount);
53
+ currencyCode = (product.selectedVariant.priceV2.currencyCode ||
54
+ 'USD') as CurrencyCode;
55
+ }
56
+ });
57
+
58
+ setSubtotalWithProduct({
59
+ amount: String(initialTotal),
60
+ currencyCode,
61
+ });
62
+
63
+ setSubtotalWithOutProduct({
64
+ amount: String(
65
+ initialTotal -
66
+ Number(
67
+ isHydrogenReact
68
+ ? product?.selectedVariant?.price.amount
69
+ : product?.selectedOrFirstAvailableVariant?.price
70
+ .amount
71
+ )
72
+ ),
73
+ currencyCode,
74
+ });
75
+ setAddedItems(products);
76
+ if (includeMainProduct) {
77
+ setAddedItems([
78
+ convertToRebuyProduct(isHydrogenReact || false, product),
79
+ ...products,
80
+ ]);
81
+ } else {
82
+ setAddedItems(products);
83
+ }
84
+ }, [products, product, isHydrogenReact, includeMainProduct]);
85
+
86
+ const handleChange = useCallback(
87
+ (event: React.ChangeEvent<HTMLInputElement>, product: RebuyProduct) => {
88
+ const newProducts = [...products];
89
+ const productIndex = newProducts.findIndex(
90
+ (p) => p.id === product.id
91
+ );
92
+
93
+ if (productIndex !== -1) {
94
+ newProducts[productIndex] = {
95
+ ...newProducts[productIndex],
96
+ selected: event.target.checked,
97
+ };
98
+
99
+ product.selected = event.target.checked;
100
+
101
+ if (event.target.checked) {
102
+ setAddedItems((prev) => [...prev, product]);
103
+ } else {
104
+ setAddedItems((prev) =>
105
+ prev.filter((item) => item.id !== product.id)
106
+ );
107
+ }
108
+ }
109
+ },
110
+ [products]
111
+ );
112
+
113
+ useEffect(() => {
114
+ let total = 0;
115
+ let currencyCode = 'USD' as CurrencyCode;
116
+
117
+ addedItems.forEach((item) => {
118
+ if (item.selected && item.selectedVariant?.priceV2) {
119
+ total += Number(item.selectedVariant.priceV2.amount);
120
+ currencyCode = (item.selectedVariant.priceV2.currencyCode ||
121
+ 'USD') as CurrencyCode;
122
+ }
123
+ });
124
+
125
+ setSubtotalWithProduct({
126
+ amount: String(total),
127
+ currencyCode,
128
+ });
129
+
130
+ setSubtotalWithOutProduct({
131
+ amount: String(
132
+ total -
133
+ Number(
134
+ isHydrogenReact
135
+ ? product?.selectedVariant?.price.amount
136
+ : product?.selectedOrFirstAvailableVariant?.price
137
+ .amount
138
+ )
139
+ ),
140
+ currencyCode,
141
+ });
142
+ }, [addedItems, product, isHydrogenReact]);
143
+
144
+ if (products.length === 0) {
145
+ console.log('RebuyProductAddOns: No products found');
146
+ return null;
147
+ }
148
+
149
+ return (
150
+ <div className={styles.container}>
151
+ <Title
152
+ level={getTitleLevel(customTitleLevel)}
153
+ style={customTitleStyle}
154
+ text={customTitle}
155
+ />
156
+ <ul className={styles.productAddOnsList}>
157
+ {products.map((product) => (
158
+ <li key={product.id}>
159
+ <RebuyProductAddOnCard
160
+ handleChange={handleChange}
161
+ learnMoreText={learnMoreText}
162
+ outOfStockText={outOfStockText}
163
+ product={product}
164
+ titleLevel={getTitleLevel(customTitleLevel, true)}
165
+ />
166
+ </li>
167
+ ))}
168
+ </ul>
169
+ <div className={styles.productAddOnsFooter}>
170
+ <div className={styles.moneyContainer}>
171
+ {subtotalText}
172
+ {subtotalWithOutProduct && (
173
+ <Money
174
+ data={subtotalWithOutProduct}
175
+ withoutTrailingZeros
176
+ />
177
+ )}
178
+ </div>
179
+ {includeMainProduct && (
180
+ <div className={styles.moneyContainer}>
181
+ {withProductText}
182
+ {subtotalWithProduct && (
183
+ <Money
184
+ data={subtotalWithProduct}
185
+ withoutTrailingZeros
186
+ />
187
+ )}
188
+ </div>
189
+ )}
190
+ <div className={styles.addCartBtnContainer}>
191
+ {includeMainProduct ? (
192
+ <AddToCartBtn
193
+ addToCartBtnText={addToCartBtnText}
194
+ addToCartCallback={addToCartCallback}
195
+ disabled={addedItems.length === 0}
196
+ isHydrogenReact={isHydrogenReact}
197
+ moneyData={subtotalWithProduct}
198
+ selectedVariants={addedItems.map(
199
+ (item) => item.selectedVariant
200
+ )}
201
+ />
202
+ ) : (
203
+ <AddToCartBtn
204
+ addToCartBtnText={addToCartBtnText}
205
+ addToCartCallback={addToCartCallback}
206
+ disabled={addedItems.length === 0}
207
+ isHydrogenReact={isHydrogenReact}
208
+ moneyData={subtotalWithOutProduct}
209
+ selectedVariants={addedItems.map(
210
+ (item) => item.selectedVariant
211
+ )}
212
+ />
213
+ )}
214
+ </div>
215
+ </div>
216
+ </div>
217
+ );
218
+ };
@@ -0,0 +1 @@
1
+ export * from './RebuyProductAddOns';
@@ -0,0 +1,24 @@
1
+ import type { ProductCardTitleLevel } from '~/types/common';
2
+ import type { RebuyProduct } from '~/types/rebuyCustom';
3
+ import type { WidgetChildProps } from '~/types/widgets';
4
+
5
+ export type RebuyProductAddOnProps = {
6
+ addToCartBtnText?: string;
7
+ includeMainProduct?: boolean;
8
+ learnMoreText?: string;
9
+ outOfStockText?: string;
10
+ subtotalText?: string;
11
+ withProductText?: string;
12
+ } & WidgetChildProps;
13
+
14
+ export type RebuyProductAddOnCardProps = {
15
+ handleChange: (
16
+ event: React.ChangeEvent<HTMLInputElement>,
17
+ product: RebuyProduct
18
+ ) => void;
19
+
20
+ learnMoreText?: string;
21
+ outOfStockText?: string;
22
+ product: RebuyProduct;
23
+ titleLevel: ProductCardTitleLevel;
24
+ };
@@ -0,0 +1,50 @@
1
+ import styles from './RebuyProductRecommendations.module.css';
2
+
3
+ import type { RebuyProductRecommendationsProps } from './types';
4
+
5
+ import { ProductCard } from '~/components/ProductCard';
6
+ import { Title } from '~/components/Title';
7
+ import { getTitleLevel } from '~/hooks/titleLevel';
8
+
9
+ export const RebuyProductRecommendations = (
10
+ props: RebuyProductRecommendationsProps
11
+ ) => {
12
+ const {
13
+ addToCartBtnText = 'Add to cart',
14
+ addToCartCallback,
15
+ customTitle = `These pair with ${props.product?.title}`,
16
+ customTitleLevel = 'h2',
17
+ customTitleStyle,
18
+ products = [],
19
+ } = props;
20
+
21
+ if (products.length === 0) {
22
+ console.log('RebuyProductRecommendations: No products found');
23
+ return null;
24
+ }
25
+ return (
26
+ <section className={styles.container}>
27
+ <Title
28
+ level={getTitleLevel(customTitleLevel)}
29
+ style={customTitleStyle}
30
+ text={customTitle}
31
+ />
32
+ <ul className={styles.productGrid}>
33
+ {products.map((product) => (
34
+ <li className={styles.productItem} key={product.id}>
35
+ <ProductCard
36
+ addToCartBtnText={addToCartBtnText}
37
+ addToCartCallback={addToCartCallback}
38
+ isHydrogenReact={props.isHydrogenReact}
39
+ product={product}
40
+ productCardTitleLevel={getTitleLevel(
41
+ customTitleLevel,
42
+ true
43
+ )}
44
+ />
45
+ </li>
46
+ ))}
47
+ </ul>
48
+ </section>
49
+ );
50
+ };
@@ -0,0 +1 @@
1
+ export { RebuyProductRecommendations } from './RebuyProductRecommendations';
@@ -0,0 +1,5 @@
1
+ import type { WidgetChildProps } from '~/types/widgets';
2
+
3
+ export type RebuyProductRecommendationsProps = {
4
+ addToCartBtnText?: string;
5
+ } & WidgetChildProps;
@@ -1,188 +0,0 @@
1
- import {
2
- AddToCartButton,
3
- Image,
4
- Link,
5
- Money,
6
- ProductOptionsProvider,
7
- } from '@shopify/hydrogen';
8
- import clsx from 'clsx';
9
- import { useState } from 'react';
10
- import { Section, Text } from '~/components';
11
- import { Button } from '~/components/elements';
12
-
13
- const isDiscounted = (price, compareAtPrice) =>
14
- Number(compareAtPrice?.amount) > Number(price?.amount);
15
-
16
- const CompareAtPrice = ({ data: compareAtPrice, className }) => {
17
- const styles = clsx('strike', className);
18
-
19
- return (
20
- <Money
21
- withoutTrailingZeros
22
- data={compareAtPrice}
23
- as="span"
24
- className={styles}
25
- />
26
- );
27
- };
28
-
29
- const AddToCartMarkup = ({
30
- product,
31
- selectedVariant = product.variants.nodes[0],
32
- }) => {
33
- const isOutOfStock = !selectedVariant.availableForSale;
34
-
35
- return (
36
- <AddToCartButton
37
- disabled={isOutOfStock}
38
- variantId={selectedVariant?.id}
39
- quantity={1}
40
- accessibleAddingToCartLabel="Adding item to your cart"
41
- type="button"
42
- attributes={[
43
- { key: '_source', value: 'Rebuy' },
44
- { key: '_attribution', value: 'Rebuy Product Recommendations' },
45
- ]}
46
- >
47
- <Button
48
- width="full"
49
- variant={isOutOfStock ? 'secondary' : 'primary'}
50
- as="span"
51
- className="px-0"
52
- >
53
- {isOutOfStock ? 'Out of stock' : 'Add'}
54
- </Button>
55
- </AddToCartButton>
56
- );
57
- };
58
-
59
- const RebuyProductPrice = ({ selectedVariant = {} }) => {
60
- const { priceV2: price, compareAtPriceV2: compareAtPrice } =
61
- selectedVariant;
62
-
63
- return (
64
- price && (
65
- <div className="gap-4">
66
- <Text className="flex gap-2">
67
- <Money withoutTrailingZeros data={price} />
68
- {isDiscounted(price, compareAtPrice) && (
69
- <CompareAtPrice
70
- className={'opacity-50'}
71
- data={compareAtPrice}
72
- />
73
- )}
74
- </Text>
75
- </div>
76
- )
77
- );
78
- };
79
-
80
- const VariantSelector = ({ product, handleSelectedVariant }) => {
81
- return (
82
- product?.variants.nodes.length > 1 && (
83
- <div className="">
84
- <select
85
- className="w-full py-1 rounded"
86
- name=""
87
- onChange={(e) =>
88
- handleSelectedVariant(product, e.target.value)
89
- }
90
- >
91
- <optgroup label={getOptionsLabel(product)}>
92
- {product.variants.nodes.map(({ id, title }) => (
93
- <option key={id + '-variant'} value={id}>
94
- {title}
95
- </option>
96
- ))}
97
- </optgroup>
98
- </select>
99
- </div>
100
- )
101
- );
102
- };
103
-
104
- const getOptionsLabel = (product) => {
105
- const options = product.variants.nodes[0].selectedOptions;
106
- const optionsFromKeys = Object.keys(options[0]);
107
- const optionsFromValues = options.map((option) => option.name);
108
- const useValues = optionsFromKeys.every((key) =>
109
- ['name', 'value'].includes(key)
110
- );
111
-
112
- // Return delimited label for available option(s) e.g. Color / Size, Scent, etc
113
- return (useValues ? optionsFromValues : optionsFromKeys).join(' / ');
114
- };
115
-
116
- const RebuyProductCard = ({ product }) => {
117
- const [selectedVariant, setSelectedVariant] = useState(
118
- product.variants.nodes[0]
119
- );
120
- const { image } = selectedVariant;
121
- const handleSelectedVariant = (product, variant_id) => {
122
- const updatedVariant = product.variants.nodes.find(
123
- (variant) => variant.id === variant_id
124
- );
125
-
126
- setSelectedVariant(updatedVariant);
127
- };
128
-
129
- return (
130
- <div className="grid grid-cols-2 grid-rows-1 gap-6">
131
- <Link to={`/products/${product.handle}`}>
132
- {image && (
133
- <Image
134
- className="w-full object-cover fadeIn"
135
- data={image}
136
- alt={image.altText || `Picture of ${product.title}`}
137
- />
138
- )}
139
- </Link>
140
- <div className="grid gap-1 items-start">
141
- <Link to={`/products/${product.handle}`}>
142
- <Text>{product.title}</Text>
143
- </Link>
144
- <RebuyProductPrice selectedVariant={selectedVariant} />
145
- <VariantSelector
146
- product={product}
147
- handleSelectedVariant={handleSelectedVariant}
148
- />
149
- <AddToCartMarkup
150
- product={product}
151
- selectedVariant={selectedVariant}
152
- />
153
- </div>
154
- </div>
155
- );
156
- };
157
-
158
- export const RebuyCompleteTheLook = ({
159
- product = {},
160
- products = [],
161
- // eslint-disable-next-line no-unused-vars
162
- metadata = {},
163
- title = `These pair with ${product.title}`,
164
- className = '',
165
- }) => {
166
- const styles = clsx('', className);
167
-
168
- return (
169
- products.length > 0 && (
170
- <Section heading={title} padding="n" className={styles}>
171
- <ul className="grid gap-8">
172
- {products.map((product) => (
173
- <li key={product.id}>
174
- <ProductOptionsProvider
175
- data={product}
176
- initialVariantId={product.variants.nodes[0].id}
177
- >
178
- <RebuyProductCard product={product} />
179
- </ProductOptionsProvider>
180
- </li>
181
- ))}
182
- </ul>
183
- </Section>
184
- )
185
- );
186
- };
187
-
188
- export default RebuyCompleteTheLook;