@rebuy/rebuy-hydrogen 1.0.2 → 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.
- package/README.md +11 -383
- package/RebuyContextProvider.client.jsx +184 -92
- package/RebuyContexts.client.jsx +3 -0
- package/RebuyProductRecommendations.client.jsx +63 -0
- package/RebuyProductViewed.client.jsx +48 -28
- package/RebuyRecentlyViewedProducts.client.jsx +65 -0
- package/RebuyWidgetContainer.client.jsx +121 -72
- package/package.json +26 -11
package/README.md
CHANGED
@@ -1,396 +1,24 @@
|
|
1
1
|
# Rebuy + Hydrogen Overview
|
2
2
|
|
3
|
-
Rebuy + Hydrogen package is a web development framework used for building Shopify custom storefronts.
|
3
|
+
Rebuy + Hydrogen package is a web development framework used for building Shopify custom storefronts. It includes providers, components, and tooling you need to get started so you can spend your time creating intelligent shopping experiences.
|
4
4
|
|
5
5
|
## How Rebuy + Hydrogen Works
|
6
6
|
|
7
|
-
Rebuy + Hydrogen is a lightweight framework for creating personalized shopping experiences that are lightening fast.
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
7
|
+
Rebuy + Hydrogen is a lightweight framework for creating personalized shopping experiences that are lightening fast. The framework is composed of:
|
8
|
+
|
9
|
+
1. **Providers** - these components build contextual objects that are used during network calls.
|
10
|
+
2. **Containers** - these components get personalized data from Rebuy and pass the resulting information to children components via props.
|
11
|
+
3. **Components** - these components can be used to render various merchandising UI.
|
12
|
+
4. **Events** - these components automatically track user behaviors.
|
12
13
|
|
13
14
|
### Data Sources
|
14
15
|
|
15
|
-
Rebuy + Hydrogen is powered by Rebuy's Data Sources, which allows for AI-powered product recommendations, custom defined rulesets, or mixture of human and computer derived output.
|
16
|
+
Rebuy + Hydrogen is powered by Rebuy's Data Sources, which allows for AI-powered product recommendations, custom defined rulesets, or mixture of human and computer derived output. Data Sources are created in Rebuy's App Admin, and the data source URL is used in your Hydrogen project. Separating the logic from the codebase allows non-technical team members to manage the merchandising rules without the need for costly code changes or redeployments.
|
16
17
|
|
17
18
|
### User Interfaces
|
18
19
|
|
19
|
-
Rebuy + Hydrogen uses a React container pattern to separate the data fetching logic and state
|
20
|
-
|
21
|
-
|
22
|
-
# Getting Started
|
23
|
-
|
24
|
-
In this tutorial, you'll extend your Hydrogen app with the Rebuy + Hydrogen framework. After completing this tutorial you'll have accomplished the following:
|
25
|
-
|
26
|
-
- Installed the Rebuy + Hydrogen framework
|
27
|
-
- Configured your environment
|
28
|
-
- Generated AI-powered product recommendations
|
29
|
-
|
30
|
-
## Requirements
|
31
|
-
|
32
|
-
In order to complete this tutorial, you will need the following:
|
33
|
-
- [Shopify](https://www.shopify.com/) account
|
34
|
-
- [Rebuy](https://www.rebuyengine.com/) installed in your Shopify store
|
35
|
-
- [Hydrogen App](https://shopify.dev/custom-storefronts/hydrogen/getting-started/create#step-1-create-a-new-hydrogen-storefront)
|
36
|
-
|
37
|
-
### Install Dependencies
|
38
|
-
Additionally, you will need the following dependencies:
|
39
|
-
- [npm](https://www.npmjs.com/)
|
40
|
-
- [Node.js](https://nodejs.org/en/) (version 16.5.0 or higher)
|
41
|
-
|
42
|
-
|
43
|
-
# Step 1: Create a new Hydrogen app
|
44
|
-
|
45
|
-
You can create a Hydrogen app locally using `yarn`, `npm`, or `npx`.
|
46
|
-
|
47
|
-
If you want to [integrate with an existing React framework](https://shopify.dev/custom-storefronts/hydrogen/framework/integrate-react-frameworks), like [Next.js](https://nextjs.org/) or [Gatsby](https://www.gatsbyjs.com/), then you can add the [@shopify/hydrogen](https://www.npmjs.com/package/@shopify/hydrogen) `npm` package to your project.
|
48
|
-
|
49
|
-
#### 1. Navigate to your working directoy
|
50
|
-
Navigate to the directory where you want to create your project and run the following command:
|
51
|
-
```
|
52
|
-
cd <directory>
|
53
|
-
```
|
54
|
-
|
55
|
-
#### 2. Create your project
|
56
|
-
Create a new Hydrogen project with a default template linked to a demo-store with the following command:
|
57
|
-
```
|
58
|
-
npm init @shopify/hydrogen -- --template demo-store
|
59
|
-
```
|
60
|
-
|
61
|
-
#### 3. Name your project
|
62
|
-
Follow the prompts to name your new Hydrogen application.
|
63
|
-
|
64
|
-
|
65
|
-
# Step 2: Link your Shopify Storefront
|
66
|
-
Connect your Hydrogen app to your Shopify storefront by updating the properties in the `hydrogen.config.js` file. You will need to [generate a Storefront API access token](https://help.shopify.com/en/manual/apps/custom-apps?shpxid=5ef5d325-B992-4F4F-3A6D-0FACECA2B482#install-the-app-and-get-the-api-access-tokens) for your Hydrogen app to communicate with your Shopify store.
|
67
|
-
|
68
|
-
#### 1. Open your `hydrogen.config.js` file
|
69
|
-
Open `hydrogen.config.js` file located in the root of your project directory.
|
70
|
-
|
71
|
-
#### 2. Update your config file
|
72
|
-
Edit your `hydrogen.config.js` file with the following changes and save the file:
|
73
|
-
- Update storeDomain to specify your store's domain name.
|
74
|
-
- Update storefrontToken to specify your Storefront API access token.
|
75
|
-
- Update storefrontApiVersion to specify the Storefront API version that you want to use.
|
76
|
-
```
|
77
|
-
export default defineConfig({
|
78
|
-
shopify: {
|
79
|
-
storeDomain: 'YOUR_STORE_NAME.myshopify.com',
|
80
|
-
storefrontToken: 'YOUR_STOREFRONT_ACCESS_TOKEN',
|
81
|
-
storefrontApiVersion: '2022-07',
|
82
|
-
},
|
83
|
-
});
|
84
|
-
```
|
85
|
-
|
86
|
-
|
87
|
-
# Step 3: Start a development server
|
88
|
-
Create a new local development server to start testing your changes.
|
89
|
-
|
90
|
-
#### 1. Navigate to your project directoy
|
91
|
-
Navigate to the root of your project directory:
|
92
|
-
```
|
93
|
-
cd <directory>
|
94
|
-
```
|
95
|
-
|
96
|
-
#### 2. Start the development server
|
97
|
-
Start up a new local development server on `localhost`:
|
98
|
-
```
|
99
|
-
npm run dev
|
100
|
-
```
|
101
|
-
|
102
|
-
The development environment will open automatically at [http://localhost:3000](http://localhost:3000).
|
103
|
-
|
104
|
-
|
105
|
-
# Step 4: Install Rebuy + Hydrogen Package
|
106
|
-
Once you have successfully created your new Hydrogen app and linked it to your Shopify store, it's now time to install the Rebuy + Hydrogen package. If you currently have your development server running, you can terminate the server with the following command `Ctrl` + `C`.
|
107
|
-
|
108
|
-
#### 1. Navigate to your project directoy
|
109
|
-
Navigate to the root of your project directory:
|
110
|
-
```
|
111
|
-
cd <directory>
|
112
|
-
```
|
113
|
-
|
114
|
-
#### 2. Install the Rebuy + Hydrogen package
|
115
|
-
Install the Rebuy + Hydrogen package from [npm](https://www.npmjs.com/) with the following command:
|
116
|
-
```
|
117
|
-
npm install @rebuy/rebuy-hydrogen
|
118
|
-
```
|
119
|
-
|
120
|
-
#### 3. Update your `.env` with your Rebuy public API key
|
121
|
-
You can find your [Rebuy public API key](https://rebuyengine.com/account/keys) in Rebuy admin > Account > API Keys. Once located, update your `.env` with the following:
|
122
|
-
```
|
123
|
-
PUBLIC_REBUY_API_KEY=yourRebuyAPIKey
|
124
|
-
```
|
125
|
-
*If your project does not have an `.env` file, you can create a new one and save it in the project root directory.*
|
126
|
-
|
127
|
-
|
128
|
-
# Step 5: Build Personalized Shopping Experiences
|
129
|
-
Once you have included Rebuy + Hydrogen in your project, it's time to start building dynamic, personalized shopping experiences! In this tutorial, we will accomplish the following:
|
130
|
-
|
131
|
-
- Customize the product details page
|
132
|
-
- Display a list of AI-powered product recommendations
|
133
|
-
- Display a list of recently viewed products
|
134
|
-
|
135
|
-
#### 1. Create a Recommended Products Component
|
136
|
-
Create a new file in `src/components` called `ProductRecommendations.jsx`, and add the following to the file:
|
137
|
-
```
|
138
|
-
import {
|
139
|
-
ProductOptionsProvider,
|
140
|
-
AddToCartButton,
|
141
|
-
} from '@shopify/hydrogen';
|
142
|
-
import ProductCard from './ProductCard';
|
143
|
-
import {
|
144
|
-
BUTTON_PRIMARY_CLASSES,
|
145
|
-
} from './Button.client';
|
146
|
-
|
147
|
-
function AddToCartMarkup({product}) {
|
148
|
-
const selectedVariant = product.variants.edges[0].node;
|
149
|
-
const isOutOfStock = !selectedVariant.availableForSale;
|
150
|
-
|
151
|
-
return (
|
152
|
-
<div className="space-y-2 mb-8">
|
153
|
-
<AddToCartButton
|
154
|
-
className={BUTTON_PRIMARY_CLASSES}
|
155
|
-
disabled={isOutOfStock}
|
156
|
-
product={product}
|
157
|
-
variantId={selectedVariant.id}
|
158
|
-
attributes={[
|
159
|
-
{key: '_source', value: 'Rebuy'},
|
160
|
-
{key: '_attribution', value: 'Product Recommendations'
|
161
|
-
}]}
|
162
|
-
>
|
163
|
-
{isOutOfStock ? 'Out of stock' : 'Add to bag'}
|
164
|
-
</AddToCartButton>
|
165
|
-
</div>
|
166
|
-
);
|
167
|
-
}
|
168
|
-
|
169
|
-
export default function ProductRecommendations(props) {
|
170
|
-
const {product, products, metadata} = props;
|
171
|
-
|
172
|
-
console.log('ProductRecommendations component properties:', props);
|
173
|
-
|
174
|
-
return(
|
175
|
-
<>
|
176
|
-
<h1 className="font-bold text-4xl md:text-5xl text-gray-900 mb-6 mt-6">If you like {product.title}, you'll also love these</h1>
|
177
|
-
<ul className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 mb-16">
|
178
|
-
{products.map((product) => (
|
179
|
-
<li key={product.id}>
|
180
|
-
<ProductOptionsProvider data={product} initialVariantId={product.variants.edges[0].node.id}>
|
181
|
-
<ProductCard product={product} />
|
182
|
-
<AddToCartMarkup product={product} />
|
183
|
-
</ProductOptionsProvider>
|
184
|
-
</li>
|
185
|
-
))}
|
186
|
-
</ul>
|
187
|
-
</>
|
188
|
-
)
|
189
|
-
}
|
190
|
-
```
|
191
|
-
|
192
|
-
#### 2. Create a Recently Viewed Products Component
|
193
|
-
Create a new file in `src/components` called `RecentlyViewedProducts.jsx`, and add the following to the file:
|
194
|
-
```
|
195
|
-
import {
|
196
|
-
ProductOptionsProvider,
|
197
|
-
AddToCartButton,
|
198
|
-
} from '@shopify/hydrogen';
|
199
|
-
import ProductCard from './ProductCard';
|
200
|
-
import {
|
201
|
-
BUTTON_PRIMARY_CLASSES,
|
202
|
-
} from './Button.client';
|
203
|
-
|
204
|
-
function AddToCartMarkup({product}) {
|
205
|
-
const selectedVariant = product.variants.edges[0].node;
|
206
|
-
const isOutOfStock = !selectedVariant.availableForSale;
|
207
|
-
|
208
|
-
return (
|
209
|
-
<div className="space-y-2 mb-8">
|
210
|
-
<AddToCartButton
|
211
|
-
className={BUTTON_PRIMARY_CLASSES}
|
212
|
-
disabled={isOutOfStock}
|
213
|
-
product={product}
|
214
|
-
variantId={selectedVariant.id}
|
215
|
-
attributes={[
|
216
|
-
{key: '_source', value: 'Rebuy'},
|
217
|
-
{key: '_attribution', value: 'Recently Viewed Product'
|
218
|
-
}]}
|
219
|
-
>
|
220
|
-
{isOutOfStock ? 'Out of stock' : 'Add to bag'}
|
221
|
-
</AddToCartButton>
|
222
|
-
</div>
|
223
|
-
);
|
224
|
-
}
|
225
|
-
|
226
|
-
export default function RecentlyViewedProducts(props) {
|
227
|
-
const {product, products, metadata} = props;
|
228
|
-
|
229
|
-
console.log('RecentlyViewedProducts component properties:', props);
|
230
|
-
|
231
|
-
return(
|
232
|
-
<>
|
233
|
-
<h1 className="font-bold text-4xl md:text-5xl text-gray-900 mb-6 mt-6">Recently Viewed</h1>
|
234
|
-
<ul className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 mb-16">
|
235
|
-
{products.map((product) => (
|
236
|
-
<li key={product.id}>
|
237
|
-
<ProductOptionsProvider data={product} initialVariantId={product.variants.edges[0].node.id}>
|
238
|
-
<ProductCard product={product} />
|
239
|
-
<AddToCartMarkup product={product} />
|
240
|
-
</ProductOptionsProvider>
|
241
|
-
</li>
|
242
|
-
))}
|
243
|
-
</ul>
|
244
|
-
</>
|
245
|
-
)
|
246
|
-
}
|
247
|
-
```
|
248
|
-
|
249
|
-
#### 3. Install the RebuyContextProvider
|
250
|
-
Open `/src/App.server.jsx` file and install the `RebuyContextProvider`, so all Rebuy API calls are enriched with contextual information. Open the file and make the following edits:
|
251
|
-
|
252
|
-
##### Import your the provider
|
253
|
-
```
|
254
|
-
import {RebuyContextProvider} from '@rebuy/rebuy-hydrogen/RebuyContextProvider.client';
|
255
|
-
```
|
256
|
-
|
257
|
-
##### Add RebuyContextProvider component
|
258
|
-
Wrap the `<Route>` component with the `RebuyContextProvider`, as follows:
|
259
|
-
```
|
260
|
-
<RebuyContextProvider>
|
261
|
-
<Route path="*" page={<NotFound />} />
|
262
|
-
</RebuyContextProvider>
|
263
|
-
```
|
264
|
-
|
265
|
-
#### 4. Customize the Product Details Page
|
266
|
-
Let's include our new components to the `ProductDetails` client component, located in `src/components/ProductDetails.client.jsx`. We will be wrapping our new components with a `RebuyWidgetContainer` components, which will give us an interface to request data from Rebuy.
|
267
|
-
|
268
|
-
##### Import your new UI components
|
269
|
-
Import the `ProductRecommendations` and `RecentlyViewedProducts` components in to the `ProductDetails.client.jsx` file.
|
270
|
-
```
|
271
|
-
import ProductRecommendations from './ProductRecommendations';
|
272
|
-
import RecentlyViewedProducts from './RecentlyViewedProducts';
|
273
|
-
```
|
274
|
-
|
275
|
-
##### Import Rebuy components
|
276
|
-
Import the `RebuyWidgetContainer` and `RebuyProductViewed` components in to the `ProductDetails.client.jsx` file.
|
277
|
-
```
|
278
|
-
import RebuyWidgetContainer from '@rebuy/rebuy-hydrogen/RebuyWidgetContainer.client';
|
279
|
-
import RebuyProductViewed from '@rebuy/rebuy-hydrogen/RebuyProductViewed.client';
|
280
|
-
```
|
281
|
-
|
282
|
-
##### Add imported components in to the `ProductDetails` functional component
|
283
|
-
Before the closing `</ProductOptionsProvider>` tag, insert our imported components:
|
284
|
-
```
|
285
|
-
<RebuyWidgetContainer product={product} dataSource="/api/v1/products/recommended" limit="6">
|
286
|
-
<ProductRecommendations />
|
287
|
-
</RebuyWidgetContainer>
|
288
|
-
|
289
|
-
<RebuyWidgetContainer product={product} dataSource="/api/v1/products/viewed" limit="6">
|
290
|
-
<RecentlyViewedProducts />
|
291
|
-
</RebuyWidgetContainer>
|
292
|
-
|
293
|
-
<RebuyProductViewed product={product} />
|
294
|
-
```
|
295
|
-
|
296
|
-
##### Review your changes
|
297
|
-
When you look at the product details page, you should now see two new sections:
|
298
|
-
1. AI-powered product recommendations
|
299
|
-
2. Recently viewed products
|
300
|
-
|
301
|
-
Additionally, we included the `<RebuyProductViewed product={product} />` component, which will automatically send a `user -> viewed -> product` event to Rebuy. This event is powering the recently viewed component in real-time! As you navigate the site, your browsing history will be tracked and display for easy navigation to previous product pages!
|
302
|
-
|
303
|
-
|
304
|
-
# Documentation
|
305
|
-
Below you will find documentation on each of the components included in the Rebuy + Hydrogen package.
|
306
|
-
|
307
|
-
## RebuyContextProvider
|
308
|
-
The `RebuyContextProvider` is a React provider component. It is responsible for collecting environment and state-based information as a `RebuyContext`. It will gather information about the current URL, session UTM parameters, as well as cart information such as the cart token, subtotal, line items, etc. As a provider component, no UI is rendered, it simply creates a React context value that can be requested by any child component.
|
309
|
-
|
310
|
-
The `RebuyContextProvider` is a client component, which means it renders in the browser. It has two dependencies, which are `useUrl` and `useCart`, so the component should be located high up in the component tree but **below** the `ShopifyProvider` and `CartProvider`.
|
311
|
-
|
312
|
-
The recommended location is in `src/App.server.jsx` and wrapping the `<Route>` component, which makes the `RebuyContext` automatically available on all pages across the site.
|
313
|
-
|
314
|
-
#### Import the RebuyContextProvider
|
315
|
-
```
|
316
|
-
import {RebuyContextProvider} from '@rebuy/rebuy-hydrogen/RebuyContextProvider.client';
|
317
|
-
```
|
318
|
-
|
319
|
-
#### Wrap the Route compontent with RebuyContextProvider
|
320
|
-
```
|
321
|
-
<RebuyContextProvider>
|
322
|
-
<Route path="*" page={<NotFound />} />
|
323
|
-
</RebuyContextProvider>
|
324
|
-
```
|
325
|
-
|
326
|
-
#### Access RebuyContext
|
327
|
-
Rebuy + Hydrogen components will automatically include and use `RebuyContext`, but if you would like to use this context information in your own components, you certainly can!
|
328
|
-
|
329
|
-
```
|
330
|
-
import React from 'react';
|
331
|
-
import {RebuyContext} from '@rebuy/rebuy-hydrogen/RebuyContextProvider.client';
|
332
|
-
|
333
|
-
// Access context parameters with React's useContext method
|
334
|
-
const contextParameters = React.useContext(RebuyContext);
|
335
|
-
```
|
336
|
-
|
337
|
-
## RebuyWidgetContainer
|
338
|
-
The `RebuyWidgetContainer` is a React container component. This component is responsible for fetching data and state management. It does not render any UI components, and will pass along its properties and data received from Rebuy to all of their children components. This abstraction allows you to use your existing application components or Rebuy's out of the box components.
|
339
|
-
|
340
|
-
#### Component properties
|
341
|
-
The component accepts argments via props to allow for different API responses. All props listed below are optional, if no `dataSource` prop is provided then `'/api/v1/products/recommended'` will be used.
|
342
|
-
|
343
|
-
| Property | Value |
|
344
|
-
| -------- | ----- |
|
345
|
-
| dataSource | `{String}` A relative URL path of your Rebuy data source (i.e. '/api/v1/products/recommended) |
|
346
|
-
| product | `{Object}` A product object with an `id` |
|
347
|
-
| productId | `{String}` A product ID string in graphQL URL format |
|
348
|
-
| variant | `{Object}` A variant object with an `id` |
|
349
|
-
| variantId | `{String}` A variant ID string in graphQL URL format |
|
350
|
-
| collection | `{Object}` A collection object with an `id` |
|
351
|
-
| collectionId | `{String}` A collection ID string in graphQL URL format |
|
352
|
-
| limit | `{Number}` A number of products to return from Rebuy's API |
|
353
|
-
| options | `{Object}` An object with key, value pairs of Rebuy REST API arguments (i.e. metafields: yes) |
|
354
|
-
|
355
|
-
#### Children components properties
|
356
|
-
All child components will inherit the container props, in addition to two container generated props `products` and `metadata`. So if you've provided a `product={product}` prop to the container, then each child compontent will have the following properties `product`, `products`, and `metadata`.
|
357
|
-
|
358
|
-
| Property | Value |
|
359
|
-
| -------- | ----- |
|
360
|
-
| products | `{Array}` An array of product objects returned from Rebuy's API |
|
361
|
-
| metadata | `{Object}` An object with metadata about the input data, rules matched, rules not matched, excluded products, and cache info |
|
362
|
-
|
363
|
-
#### Example implementation
|
364
|
-
In the following example, we are using an example compontent called `ProductRecommendations` which is a UI component used to render data provided to it via props by the `RebuyWidgetContainer` component.
|
365
|
-
|
366
|
-
When the components are rendered, it will display `6` products powered by Rebuy's AI algorithm based on the provided input `product`. So if the input product was ketchup, you would likely see recommended products like mustard, mayo, or other condiments.
|
367
|
-
|
368
|
-
```
|
369
|
-
import ProductRecommendations from './ProductRecommendations';
|
370
|
-
import RebuyWidgetContainer from '@rebuy/rebuy-hydrogen/RebuyWidgetContainer.client';
|
371
|
-
|
372
|
-
<RebuyWidgetContainer product={product} dataSource="/api/v1/products/recommended" limit="6">
|
373
|
-
<ProductRecommendations />
|
374
|
-
</RebuyWidgetContainer>
|
375
|
-
```
|
376
|
-
|
377
|
-
## RebuyProductViewed
|
378
|
-
The `RebuyProductViewed` is a client component, which helps automate customer browsing behavior. This behavior is used to train the AI as well as power recently viewed products. This component would typically live in your `ProductDetails` component, so as a customer is browsing your site we automatically record the product pages they are viewing.
|
379
|
-
|
380
|
-
#### Component properties
|
381
|
-
The component accepts argments via props to allow for registering different products that have been viewed. One of the following properties are required to fire the event.
|
382
|
-
|
383
|
-
| Property | Value |
|
384
|
-
| -------- | ----- |
|
385
|
-
| product | `{Object}` A product object with an `id` |
|
386
|
-
| productId | `{String}` A product ID string in graphQL URL format |
|
387
|
-
| productHandle | `{String}` A product handle string |
|
388
|
-
|
389
|
-
#### Example implementation
|
390
|
-
The following example would be included in the `ProductDetails.client.jsx` component to automatically register customer product view events. The current product displayed is passed to the `RebuyProductViewed` component via props as `product={product}`.
|
20
|
+
Rebuy + Hydrogen uses a React container pattern to separate the data fetching logic and state management from the presentational components. As such, container components do not render user interface elements. They simply pass their own properties, as well as data received from Rebuy, to their children components for consumption. This allows you to build your own user interface with your existing components, or use Rebuy's out of the box components to get up and running quickly.
|
391
21
|
|
392
|
-
|
393
|
-
import RebuyProductViewed from '@rebuy/rebuy-hydrogen/RebuyProductViewed.client';
|
22
|
+
### Getting Started
|
394
23
|
|
395
|
-
|
396
|
-
```
|
24
|
+
To get started, please visit our [Rebuy + Shopify Hydrogen documentation](https://developers.rebuyengine.com/reference/shopify-hydrogen-getting-started).
|
@@ -1,13 +1,33 @@
|
|
1
|
-
import {
|
2
|
-
|
3
|
-
import React, { createContext, useMemo } from 'react';
|
4
|
-
|
1
|
+
import { RebuyClient } from '@rebuy/rebuy';
|
2
|
+
import { RebuyContext } from '@rebuy/rebuy-hydrogen/RebuyContexts.client';
|
5
3
|
import * as Utilities from '@rebuy/rebuy/utilities';
|
4
|
+
import { useCart, useShop, useUrl } from '@shopify/hydrogen';
|
5
|
+
import { useEffect, useMemo, useState } from 'react';
|
6
|
+
|
7
|
+
const REBUY_API_KEY = import.meta.env.PUBLIC_REBUY_API_KEY;
|
8
|
+
const API = '/api/v1';
|
9
|
+
|
10
|
+
const getEncodedAttributes = (attributes) =>
|
11
|
+
encodeURIComponent(
|
12
|
+
JSON.stringify(
|
13
|
+
attributes.reduce(
|
14
|
+
(merged, { key, value }) => ({ ...merged, [key]: value }),
|
15
|
+
{}
|
16
|
+
)
|
17
|
+
)
|
18
|
+
);
|
6
19
|
|
7
|
-
export const
|
8
|
-
|
9
|
-
|
20
|
+
export const RebuyContextProvider = ({ children }) => {
|
21
|
+
// Shopify
|
22
|
+
const cart = useCart();
|
23
|
+
const shop = useShop();
|
10
24
|
const url = useUrl();
|
25
|
+
|
26
|
+
// Default state
|
27
|
+
const [initialized, setInitialized] = useState(false);
|
28
|
+
const [rebuyConfig, setRebuyConfig] = useState(null);
|
29
|
+
const [config, setConfig] = useState({ shop: null });
|
30
|
+
// const [Rebuy, setRebuy] = useState(null);
|
11
31
|
const queryObject = Utilities.queryStringToObject(url.search);
|
12
32
|
const utmObject = Utilities.utmObjectFromString(url);
|
13
33
|
|
@@ -17,114 +37,186 @@ export function RebuyContextProvider({ children }) {
|
|
17
37
|
}
|
18
38
|
}
|
19
39
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
40
|
+
// Initialization
|
41
|
+
useEffect(() => {
|
42
|
+
const getRebuyConfig = async () => {
|
43
|
+
try {
|
44
|
+
const request = {
|
45
|
+
url: `${API}/user/config`,
|
46
|
+
parameters: { shop: shop.storeDomain },
|
47
|
+
};
|
48
|
+
|
49
|
+
const { data: rebuy, ...response } = await new RebuyClient(
|
50
|
+
REBUY_API_KEY
|
51
|
+
).getDataFromCDN(request.url, request.parameters);
|
52
|
+
|
53
|
+
// Missing Rebuy shop data?
|
54
|
+
if (!rebuy?.shop) {
|
55
|
+
throw new Error(
|
56
|
+
'Rebuy configuration is not properly set up - missing shop',
|
57
|
+
{ cause: response }
|
58
|
+
);
|
59
|
+
}
|
26
60
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
61
|
+
setRebuyConfig(rebuy);
|
62
|
+
} catch (err) {
|
63
|
+
console.warn('Error fetching Rebuy shop config');
|
64
|
+
console.error(err, err.cause);
|
65
|
+
}
|
66
|
+
};
|
31
67
|
|
32
|
-
|
33
|
-
|
68
|
+
const onBeforeReady = async () => {
|
69
|
+
// Initializing...
|
70
|
+
if (!rebuyConfig?.shop) {
|
71
|
+
return await getRebuyConfig();
|
72
|
+
}
|
73
|
+
};
|
74
|
+
|
75
|
+
onBeforeReady();
|
76
|
+
}, [rebuyConfig, shop]);
|
77
|
+
|
78
|
+
useEffect(() => {
|
79
|
+
const applyConfig = async () => {
|
80
|
+
// Still fetching Rebuy config OR config already applied. Abort!
|
81
|
+
if (!rebuyConfig || config.shop) return;
|
82
|
+
|
83
|
+
// Bring it all together
|
84
|
+
const appConfig = { ...config, ...rebuyConfig };
|
85
|
+
|
86
|
+
setConfig(appConfig);
|
87
|
+
};
|
88
|
+
|
89
|
+
applyConfig();
|
90
|
+
}, [config, rebuyConfig]);
|
91
|
+
|
92
|
+
useEffect(() => {
|
93
|
+
const onReady = () => {
|
94
|
+
// Still initializing... Abort!
|
95
|
+
if (!config.shop) return;
|
96
|
+
|
97
|
+
// Initialized!
|
98
|
+
setInitialized(true);
|
99
|
+
// const { api_key, cache_key } = config.shop;
|
100
|
+
// setRebuy(new RebuyClient(api_key, { cache_key }));
|
101
|
+
};
|
102
|
+
|
103
|
+
onReady();
|
104
|
+
}, [config]);
|
105
|
+
|
106
|
+
const contextParameters = useMemo(() => {
|
107
|
+
// Still initializing... Abort!
|
108
|
+
if (!config.shop) return null;
|
109
|
+
|
110
|
+
const { cache_key } = config.shop;
|
111
|
+
const contextParameters = {
|
112
|
+
url: url.href,
|
113
|
+
cache_key,
|
114
|
+
};
|
115
|
+
// Cart object
|
116
|
+
const cartContext = {};
|
117
|
+
|
118
|
+
// Set time
|
119
|
+
if (Object.prototype.hasOwnProperty.call(queryObject, 'time')) {
|
120
|
+
contextParameters.time = queryObject.time;
|
121
|
+
}
|
34
122
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
123
|
+
// Set Cart: token
|
124
|
+
if (cart.id) {
|
125
|
+
cartContext.token = Utilities.getIdFromGraphUrl(cart.id, 'Cart');
|
126
|
+
contextParameters.cart_token = Utilities.getIdFromGraphUrl(
|
127
|
+
cart.id,
|
128
|
+
'Cart'
|
129
|
+
);
|
130
|
+
}
|
40
131
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
cart.estimatedCost.subtotalAmount &&
|
45
|
-
cart.estimatedCost.subtotalAmount &&
|
46
|
-
cart.estimatedCost.subtotalAmount.amount
|
47
|
-
) {
|
48
|
-
cartContext.subtotal = Utilities.amountToCents(cart.estimatedCost.subtotalAmount.amount);
|
49
|
-
contextParameters.cart_subtotal = Utilities.amountToCents(cart.estimatedCost.subtotalAmount.amount);
|
50
|
-
}
|
132
|
+
// Set Cart: subtotal
|
133
|
+
if (cart.cost?.subtotalAmount?.amount) {
|
134
|
+
const { amount } = cart.cost.subtotalAmount;
|
51
135
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
contextParameters.cart_count = cart.lines.length;
|
56
|
-
contextParameters.cart_line_count = cart.lines.length;
|
57
|
-
}
|
136
|
+
cartContext.subtotal = Utilities.amountToCents(amount);
|
137
|
+
contextParameters.cart_subtotal = Utilities.amountToCents(amount);
|
138
|
+
}
|
58
139
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
contextParameters.cart_item_count = cart.totalQuantity;
|
63
|
-
}
|
140
|
+
// Set Cart: line count
|
141
|
+
if (typeof cart.lines != 'undefined') {
|
142
|
+
const totalLines = cart.lines.length;
|
64
143
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
const cartItem = cart.lines[i];
|
144
|
+
cartContext.line_count = totalLines;
|
145
|
+
contextParameters.cart_count = totalLines;
|
146
|
+
contextParameters.cart_line_count = totalLines;
|
147
|
+
}
|
70
148
|
|
71
|
-
|
149
|
+
// Set Cart: item count
|
150
|
+
if (typeof cart.totalQuantity != 'undefined') {
|
151
|
+
const { totalQuantity } = cart;
|
72
152
|
|
73
|
-
|
74
|
-
|
75
|
-
|
153
|
+
cartContext.item_count = totalQuantity;
|
154
|
+
contextParameters.cart_item_count = totalQuantity;
|
155
|
+
}
|
76
156
|
|
77
|
-
|
78
|
-
|
79
|
-
|
157
|
+
// Set Cart: line items
|
158
|
+
if (typeof cart.lines != 'undefined') {
|
159
|
+
cartContext.items = [];
|
80
160
|
|
81
|
-
|
161
|
+
for (const cartItem of cart.lines) {
|
162
|
+
const item = {
|
163
|
+
quantity: cartItem.quantity,
|
164
|
+
};
|
82
165
|
|
83
|
-
|
84
|
-
|
166
|
+
if (cartItem.product?.id) {
|
167
|
+
item.product_id = Utilities.getIdFromGraphUrl(
|
168
|
+
cartItem.product.id,
|
169
|
+
'Product'
|
170
|
+
);
|
171
|
+
}
|
85
172
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
173
|
+
if (cartItem.merchandise?.id) {
|
174
|
+
item.variant_id = Utilities.getIdFromGraphUrl(
|
175
|
+
cartItem.merchandise.id,
|
176
|
+
'ProductVariant'
|
177
|
+
);
|
90
178
|
}
|
91
179
|
|
92
|
-
|
93
|
-
|
180
|
+
if (cartItem.attributes) {
|
181
|
+
item.properties = getEncodedAttributes(cartItem.attributes);
|
182
|
+
}
|
94
183
|
|
95
|
-
|
184
|
+
// TBD: item.selling_plan
|
96
185
|
|
97
|
-
|
186
|
+
cartContext.items.push(item);
|
187
|
+
}
|
98
188
|
}
|
99
|
-
}
|
100
189
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
for (let i = 0; i < cart.attributes.length; i++) {
|
106
|
-
const key = cart.attributes[i].key;
|
107
|
-
const value = cart.attributes[i].value;
|
108
|
-
attributes[key] = value;
|
190
|
+
// Set Cart: attributes
|
191
|
+
if (cart.attributes) {
|
192
|
+
cartContext.attributes = getEncodedAttributes(cart.attributes);
|
109
193
|
}
|
110
194
|
|
111
|
-
|
112
|
-
|
195
|
+
// Set Cart: notes
|
196
|
+
if (cart.note) {
|
197
|
+
cartContext.note = cart.note;
|
198
|
+
}
|
113
199
|
|
114
|
-
|
115
|
-
|
116
|
-
cartContext.note = cart.note;
|
117
|
-
}
|
200
|
+
// Set cart
|
201
|
+
contextParameters.cart = cartContext;
|
118
202
|
|
119
|
-
|
120
|
-
|
203
|
+
return contextParameters;
|
204
|
+
}, [cart, config, queryObject, url]);
|
121
205
|
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
206
|
+
// Static reference (JSON) + memoization
|
207
|
+
// ^ prevent re-rendering children when context params are unchanged
|
208
|
+
const contextParametersJSON = JSON.stringify(contextParameters);
|
209
|
+
const contextValue = useMemo(
|
210
|
+
() => ({ contextParameters: JSON.parse(contextParametersJSON) }),
|
211
|
+
[contextParametersJSON]
|
127
212
|
);
|
128
213
|
|
129
|
-
|
130
|
-
|
214
|
+
// Still initializing...
|
215
|
+
if (!initialized) return null;
|
216
|
+
|
217
|
+
return (
|
218
|
+
<RebuyContext.Provider value={contextValue}>
|
219
|
+
{children}
|
220
|
+
</RebuyContext.Provider>
|
221
|
+
);
|
222
|
+
};
|
@@ -0,0 +1,63 @@
|
|
1
|
+
import { AddToCartButton, ProductOptionsProvider } from '@shopify/hydrogen';
|
2
|
+
import { Section } from '~/components';
|
3
|
+
import { ProductCard } from '~/components/cards';
|
4
|
+
import { Button } from '~/components/elements';
|
5
|
+
|
6
|
+
const AddToCartMarkup = ({ product }) => {
|
7
|
+
const selectedVariant = product.variants.nodes[0];
|
8
|
+
const isOutOfStock = !selectedVariant.availableForSale;
|
9
|
+
|
10
|
+
return (
|
11
|
+
<div className="space-y-2 mb-8">
|
12
|
+
<AddToCartButton
|
13
|
+
disabled={isOutOfStock}
|
14
|
+
product={product}
|
15
|
+
variantId={selectedVariant?.id}
|
16
|
+
quantity={1}
|
17
|
+
accessibleAddingToCartLabel="Adding item to your cart"
|
18
|
+
type="button"
|
19
|
+
attributes={[
|
20
|
+
{ key: '_source', value: 'Rebuy' },
|
21
|
+
{ key: '_attribution', value: 'Product Recommendations' },
|
22
|
+
]}
|
23
|
+
>
|
24
|
+
<Button
|
25
|
+
width="full"
|
26
|
+
variant={isOutOfStock ? 'secondary' : 'primary'}
|
27
|
+
as="span"
|
28
|
+
>
|
29
|
+
{isOutOfStock ? 'Out of stock' : 'Add to bag'}
|
30
|
+
</Button>
|
31
|
+
</AddToCartButton>
|
32
|
+
</div>
|
33
|
+
);
|
34
|
+
};
|
35
|
+
|
36
|
+
export const RebuyProductRecommendations = ({ product, products }) => {
|
37
|
+
const title = `Like ${product.title}? You may also like:`;
|
38
|
+
|
39
|
+
return (
|
40
|
+
products.length > 0 && (
|
41
|
+
<Section heading={title} padding="y">
|
42
|
+
<ul className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 px-6 md:px-8 lg:px-12">
|
43
|
+
{products.map((product) => (
|
44
|
+
<li key={product.id}>
|
45
|
+
<ProductOptionsProvider
|
46
|
+
data={product}
|
47
|
+
initialVariantId={product.variants.nodes[0].id}
|
48
|
+
>
|
49
|
+
<ProductCard
|
50
|
+
product={product}
|
51
|
+
className={'w-100'}
|
52
|
+
/>
|
53
|
+
<AddToCartMarkup product={product} />
|
54
|
+
</ProductOptionsProvider>
|
55
|
+
</li>
|
56
|
+
))}
|
57
|
+
</ul>
|
58
|
+
</Section>
|
59
|
+
)
|
60
|
+
);
|
61
|
+
};
|
62
|
+
|
63
|
+
export default RebuyProductRecommendations;
|
@@ -1,42 +1,62 @@
|
|
1
|
-
import
|
2
|
-
|
1
|
+
import { RebuyClient } from '@rebuy/rebuy';
|
3
2
|
import * as Utilities from '@rebuy/rebuy/utilities';
|
4
|
-
import
|
3
|
+
import { useEffect, useMemo, useState } from 'react';
|
5
4
|
|
6
5
|
const REBUY_API_KEY = import.meta.env.PUBLIC_REBUY_API_KEY;
|
7
6
|
|
8
|
-
export
|
9
|
-
const { product, productId, productHandle
|
7
|
+
export const RebuyProductViewed = ({ children, ...props }) => {
|
8
|
+
const { product, productId, productHandle } = props;
|
10
9
|
|
10
|
+
// eslint-disable-next-line no-unused-vars
|
11
11
|
const [event, setEvent] = useState(null);
|
12
|
+
const [Rebuy, setRebuy] = useState(null);
|
13
|
+
const [initialized, setInitialized] = useState(false);
|
14
|
+
const shopifyProductId = product?.id ?? productId ?? null;
|
15
|
+
|
16
|
+
const request = useMemo(() => {
|
17
|
+
const request = {
|
18
|
+
parameters: {},
|
19
|
+
};
|
20
|
+
|
21
|
+
if (shopifyProductId) {
|
22
|
+
request.parameters.shopify_product_id = Utilities.getIdFromGraphUrl(
|
23
|
+
shopifyProductId,
|
24
|
+
'Product'
|
25
|
+
);
|
26
|
+
} else if (productHandle) {
|
27
|
+
request.parameters.shopify_product_handle = productHandle;
|
28
|
+
}
|
29
|
+
|
30
|
+
return request;
|
31
|
+
}, [productHandle, shopifyProductId]);
|
12
32
|
|
13
|
-
|
33
|
+
useEffect(() => {
|
34
|
+
const recordView = async () => {
|
35
|
+
const response = await Rebuy.trackProductViewed(request.parameters);
|
14
36
|
|
15
|
-
|
16
|
-
|
17
|
-
};
|
37
|
+
setEvent(response.data);
|
38
|
+
};
|
18
39
|
|
19
|
-
|
20
|
-
|
21
|
-
} else if (productId) {
|
22
|
-
request.parameters.shopify_product_id = Utilities.getIdFromGraphUrl(productId, 'Product');
|
23
|
-
} else if (productHandle) {
|
24
|
-
request.parameters.shopify_product_handle = productHandle;
|
25
|
-
}
|
40
|
+
if (!initialized) {
|
41
|
+
const client = new RebuyClient(REBUY_API_KEY);
|
26
42
|
|
27
|
-
|
28
|
-
|
29
|
-
return;
|
30
|
-
}
|
43
|
+
setRebuy(client);
|
44
|
+
setInitialized(true);
|
31
45
|
|
32
|
-
|
46
|
+
// Abort
|
47
|
+
return;
|
48
|
+
}
|
49
|
+
|
50
|
+
// Initialized
|
33
51
|
recordView();
|
34
|
-
}, []);
|
52
|
+
}, [Rebuy, request, initialized]);
|
53
|
+
|
54
|
+
if (Object.keys(request.parameters).length === 0) {
|
55
|
+
console.warn('No parameters!');
|
56
|
+
return;
|
57
|
+
}
|
35
58
|
|
36
|
-
|
37
|
-
|
38
|
-
setEvent(response.data);
|
39
|
-
};
|
59
|
+
return children;
|
60
|
+
};
|
40
61
|
|
41
|
-
|
42
|
-
}
|
62
|
+
export default RebuyProductViewed;
|
@@ -0,0 +1,65 @@
|
|
1
|
+
import { AddToCartButton, ProductOptionsProvider } from '@shopify/hydrogen';
|
2
|
+
import { Section } from '~/components';
|
3
|
+
import { ProductCard } from '~/components/cards';
|
4
|
+
import { Button } from '~/components/elements';
|
5
|
+
|
6
|
+
const AddToCartMarkup = ({ product }) => {
|
7
|
+
const selectedVariant = product.variants.nodes[0];
|
8
|
+
const isOutOfStock = !selectedVariant.availableForSale;
|
9
|
+
|
10
|
+
return (
|
11
|
+
<div className="space-y-2 mb-8">
|
12
|
+
<AddToCartButton
|
13
|
+
disabled={isOutOfStock}
|
14
|
+
product={product}
|
15
|
+
variantId={selectedVariant?.id}
|
16
|
+
quantity={1}
|
17
|
+
accessibleAddingToCartLabel="Adding item to your cart"
|
18
|
+
type="button"
|
19
|
+
attributes={[
|
20
|
+
{ key: '_source', value: 'Rebuy' },
|
21
|
+
{ key: '_attribution', value: 'Recently Viewed Product' },
|
22
|
+
]}
|
23
|
+
>
|
24
|
+
<Button
|
25
|
+
width="full"
|
26
|
+
variant={isOutOfStock ? 'secondary' : 'primary'}
|
27
|
+
as="span"
|
28
|
+
>
|
29
|
+
{isOutOfStock ? 'Out of stock' : 'Add to bag'}
|
30
|
+
</Button>
|
31
|
+
</AddToCartButton>
|
32
|
+
</div>
|
33
|
+
);
|
34
|
+
};
|
35
|
+
|
36
|
+
export const RebuyRecentlyViewedProducts = ({ product, products }) => {
|
37
|
+
const title = 'Recently Viewed';
|
38
|
+
// Filter current product from recently viewed
|
39
|
+
const filtered = products.filter((recent) => recent.id !== product.id);
|
40
|
+
|
41
|
+
return (
|
42
|
+
filtered.length > 0 && (
|
43
|
+
<Section heading={title} padding="y">
|
44
|
+
<ul className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 mb-16 px-6 md:px-8 lg:px-12">
|
45
|
+
{filtered.map((product) => (
|
46
|
+
<li key={product.id}>
|
47
|
+
<ProductOptionsProvider
|
48
|
+
data={product}
|
49
|
+
initialVariantId={product.variants.nodes[0].id}
|
50
|
+
>
|
51
|
+
<ProductCard
|
52
|
+
product={product}
|
53
|
+
className={'w-100'}
|
54
|
+
/>
|
55
|
+
<AddToCartMarkup product={product} />
|
56
|
+
</ProductOptionsProvider>
|
57
|
+
</li>
|
58
|
+
))}
|
59
|
+
</ul>
|
60
|
+
</Section>
|
61
|
+
)
|
62
|
+
);
|
63
|
+
};
|
64
|
+
|
65
|
+
export default RebuyRecentlyViewedProducts;
|
@@ -1,87 +1,136 @@
|
|
1
|
-
import
|
2
|
-
import { RebuyContext } from '
|
3
|
-
import { flattenConnection } from '@shopify/hydrogen';
|
4
|
-
|
1
|
+
import { RebuyClient } from '@rebuy/rebuy';
|
2
|
+
import { RebuyContext } from '@rebuy/rebuy-hydrogen/RebuyContexts.client';
|
5
3
|
import * as Utilities from '@rebuy/rebuy/utilities';
|
6
|
-
import
|
4
|
+
import { flattenConnection, useCart } from '@shopify/hydrogen';
|
5
|
+
import React, { useContext, useEffect, useMemo, useState } from 'react';
|
7
6
|
|
8
7
|
const REBUY_API_KEY = import.meta.env.PUBLIC_REBUY_API_KEY;
|
9
8
|
|
10
|
-
export
|
11
|
-
const {
|
12
|
-
|
13
|
-
|
14
|
-
|
9
|
+
export const RebuyWidgetContainer = ({ children, ...props }) => {
|
10
|
+
const {
|
11
|
+
collection,
|
12
|
+
collectionId,
|
13
|
+
dataSource,
|
14
|
+
limit,
|
15
|
+
options,
|
16
|
+
product,
|
17
|
+
productId,
|
18
|
+
variant,
|
19
|
+
variantId,
|
20
|
+
} = props;
|
21
|
+
|
22
|
+
const cart = useCart();
|
23
|
+
const { contextParameters } = useContext(RebuyContext);
|
15
24
|
const [products, setProducts] = useState([]);
|
16
25
|
const [metadata, setMetadata] = useState([]);
|
26
|
+
const [Rebuy, setRebuy] = useState(null);
|
27
|
+
const [initialized, setInitialized] = useState(false);
|
28
|
+
const isCartReady = ['uninitialized', 'idle'].includes(cart.status); // uninitialized, creating, fetching, updating, idle
|
29
|
+
const shopifyProductId = product?.id ?? productId ?? null;
|
30
|
+
const shopifyVariantId = variant?.id ?? variantId ?? null;
|
31
|
+
const shopifyCollectionId = collection?.id ?? collectionId ?? null;
|
32
|
+
|
33
|
+
// Initialize Rebuy API client
|
34
|
+
useEffect(() => {
|
35
|
+
if (!Rebuy) {
|
36
|
+
const client = new RebuyClient(REBUY_API_KEY);
|
37
|
+
client.setContextParameters(contextParameters);
|
17
38
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
const request = {
|
23
|
-
url: '/api/v1/products/recommended',
|
24
|
-
parameters: {},
|
25
|
-
};
|
26
|
-
|
27
|
-
if (dataSource) {
|
28
|
-
request.url = dataSource;
|
29
|
-
}
|
39
|
+
if (options) {
|
40
|
+
client.setDefaultParameters(options);
|
41
|
+
}
|
30
42
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
}
|
43
|
+
setRebuy(client);
|
44
|
+
setInitialized(true);
|
45
|
+
}
|
46
|
+
}, [Rebuy, contextParameters, options]);
|
36
47
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
48
|
+
// Update context params
|
49
|
+
useEffect(() => {
|
50
|
+
if (!Rebuy) return;
|
51
|
+
|
52
|
+
Rebuy.setContextParameters(contextParameters);
|
53
|
+
}, [Rebuy, contextParameters]);
|
54
|
+
|
55
|
+
// Memoize request object on prop changes (e.g. product)
|
56
|
+
const request = useMemo(() => {
|
57
|
+
const request = {
|
58
|
+
url: dataSource || '/api/v1/products/recommended',
|
59
|
+
parameters: {},
|
60
|
+
};
|
61
|
+
|
62
|
+
if (shopifyProductId) {
|
63
|
+
request.parameters.shopify_product_ids =
|
64
|
+
Utilities.getIdFromGraphUrl(shopifyProductId, 'Product');
|
65
|
+
}
|
66
|
+
|
67
|
+
if (shopifyVariantId) {
|
68
|
+
request.parameters.shopify_variant_ids =
|
69
|
+
Utilities.getIdFromGraphUrl(shopifyVariantId, 'ProductVariant');
|
70
|
+
}
|
71
|
+
|
72
|
+
if (shopifyCollectionId) {
|
73
|
+
request.parameters.shopify_collection_ids =
|
74
|
+
Utilities.getIdFromGraphUrl(shopifyCollectionId, 'Collection');
|
75
|
+
}
|
76
|
+
|
77
|
+
if (limit) {
|
78
|
+
request.parameters.limit = limit;
|
79
|
+
}
|
80
|
+
|
81
|
+
return request;
|
82
|
+
}, [
|
83
|
+
dataSource,
|
84
|
+
limit,
|
85
|
+
shopifyCollectionId,
|
86
|
+
shopifyProductId,
|
87
|
+
shopifyVariantId,
|
88
|
+
]);
|
89
|
+
|
90
|
+
// Update product recommendations on cart or request change
|
91
|
+
useEffect(() => {
|
92
|
+
let ignore = false;
|
93
|
+
|
94
|
+
// Initializing...
|
95
|
+
if (!initialized || !Rebuy || !isCartReady) return;
|
96
|
+
|
97
|
+
const fetchData = async () => {
|
98
|
+
const { data, metadata } = await Rebuy.getStorefrontData(
|
99
|
+
request.url,
|
100
|
+
request.parameters
|
101
|
+
);
|
102
|
+
|
103
|
+
data.forEach((product) => {
|
104
|
+
product.variants = {
|
105
|
+
nodes: flattenConnection(product.variants),
|
106
|
+
};
|
107
|
+
});
|
108
|
+
|
109
|
+
// Set state only if mounted
|
110
|
+
if (!ignore) {
|
111
|
+
setProducts(data);
|
112
|
+
setMetadata(metadata);
|
113
|
+
}
|
114
|
+
};
|
42
115
|
|
43
|
-
|
44
|
-
request.parameters.shopify_collection_ids = Utilities.getIdFromGraphUrl(collection.id, 'Collection');
|
45
|
-
} else if (collectionId) {
|
46
|
-
request.parameters.shopify_collection_ids = Utilities.getIdFromGraphUrl(collectionId, 'Collection');
|
47
|
-
}
|
116
|
+
fetchData();
|
48
117
|
|
49
|
-
|
50
|
-
|
51
|
-
|
118
|
+
// Unmounted?
|
119
|
+
return () => {
|
120
|
+
ignore = true;
|
121
|
+
};
|
122
|
+
}, [Rebuy, initialized, request, isCartReady]);
|
52
123
|
|
53
|
-
|
54
|
-
|
55
|
-
|
124
|
+
const childrenWithProps = (props) =>
|
125
|
+
React.Children.map(children, (child) =>
|
126
|
+
React.isValidElement(child)
|
127
|
+
? React.cloneElement(child, props)
|
128
|
+
: child
|
129
|
+
);
|
56
130
|
|
57
|
-
|
58
|
-
fetchData();
|
59
|
-
}, []);
|
60
|
-
|
61
|
-
const fetchData = async () => {
|
62
|
-
const response = await Rebuy.getStorefrontData(request.url, request.parameters);
|
63
|
-
|
64
|
-
const { data, metadata } = response;
|
65
|
-
data.forEach((product) => {
|
66
|
-
product.variants = {
|
67
|
-
nodes: flattenConnection(product.variants),
|
68
|
-
};
|
69
|
-
});
|
70
|
-
|
71
|
-
setProducts(data);
|
72
|
-
setMetadata(metadata);
|
73
|
-
};
|
74
|
-
|
75
|
-
const childrenWithProps = (data) => {
|
76
|
-
return React.Children.map(children, (child) => {
|
77
|
-
if (React.isValidElement(child)) {
|
78
|
-
return React.cloneElement(child, data);
|
79
|
-
}
|
80
|
-
return child;
|
81
|
-
});
|
82
|
-
};
|
131
|
+
const childProps = { ...props, products, metadata };
|
83
132
|
|
84
|
-
|
133
|
+
return childrenWithProps(childProps);
|
134
|
+
};
|
85
135
|
|
86
|
-
|
87
|
-
}
|
136
|
+
export default RebuyWidgetContainer;
|
package/package.json
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
{
|
2
2
|
"name": "@rebuy/rebuy-hydrogen",
|
3
3
|
"description": "This is the default library for Rebuy + Shopify Hydrogen",
|
4
|
-
"version": "
|
4
|
+
"version": "2.0.0",
|
5
5
|
"license": "MIT",
|
6
6
|
"author": "Rebuy, Inc.",
|
7
7
|
"type": "module",
|
@@ -10,10 +10,12 @@
|
|
10
10
|
},
|
11
11
|
"files": [
|
12
12
|
"README.md",
|
13
|
+
"**/*.js",
|
13
14
|
"**/*.jsx"
|
14
15
|
],
|
15
16
|
"scripts": {
|
16
|
-
"
|
17
|
+
"format": "npm run lint -- --fix",
|
18
|
+
"lint": "eslint . --ext .js,.jsx,.json",
|
17
19
|
"prepare": "husky install",
|
18
20
|
"test": "echo \"Error: no test specified\" && exit 1"
|
19
21
|
},
|
@@ -34,26 +36,39 @@
|
|
34
36
|
},
|
35
37
|
"homepage": "https://bitbucket.org/rebuyengine/npm-rebuy-hydrogen#readme",
|
36
38
|
"devDependencies": {
|
37
|
-
"@commitlint/cli": "^17.0
|
38
|
-
"@commitlint/config-conventional": "^17.0
|
39
|
+
"@commitlint/cli": "^17.2.0",
|
40
|
+
"@commitlint/config-conventional": "^17.2.0",
|
39
41
|
"@semantic-release/changelog": "^6.0.1",
|
40
42
|
"@semantic-release/commit-analyzer": "^9.0.2",
|
41
43
|
"@semantic-release/git": "^10.0.1",
|
42
44
|
"@semantic-release/npm": "^9.0.1",
|
43
45
|
"@semantic-release/release-notes-generator": "^10.0.3",
|
44
|
-
"@shopify/hydrogen": "^1.
|
46
|
+
"@shopify/hydrogen": "^1.6.1",
|
45
47
|
"conventional-changelog-eslint": "^3.0.9",
|
46
48
|
"cz-conventional-changelog": "^3.3.0",
|
47
|
-
"cz-customizable": "^6.
|
48
|
-
"eslint": "^8.
|
49
|
-
"eslint-
|
49
|
+
"cz-customizable": "^6.9.2",
|
50
|
+
"eslint": "^8.26.0",
|
51
|
+
"eslint-config-prettier": "^8.5.0",
|
52
|
+
"eslint-plugin-import": "^2.26.0",
|
53
|
+
"eslint-plugin-json": "^3.1.0",
|
54
|
+
"eslint-plugin-prettier": "^4.2.1",
|
55
|
+
"eslint-plugin-react": "^7.31.10",
|
56
|
+
"eslint-plugin-react-hooks": "^4.6.0",
|
50
57
|
"husky": "^8.0.1",
|
51
|
-
"
|
58
|
+
"lint-staged": "^13.0.3",
|
59
|
+
"prettier": "^2.7.1",
|
60
|
+
"semantic-release": "^19.0.5"
|
52
61
|
},
|
53
62
|
"dependencies": {
|
54
|
-
"@rebuy/rebuy": "^1.
|
63
|
+
"@rebuy/rebuy": "^1.3.9"
|
55
64
|
},
|
56
65
|
"peerDependencies": {
|
57
|
-
"@shopify/hydrogen": "^1.
|
66
|
+
"@shopify/hydrogen": "^1.6.1"
|
67
|
+
},
|
68
|
+
"lint-staged": {
|
69
|
+
"**/*.{js,jsx,json}": [
|
70
|
+
"npm run format",
|
71
|
+
"prettier --write"
|
72
|
+
]
|
58
73
|
}
|
59
74
|
}
|