@okendo/shopify-hydrogen 2.1.5 → 2.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/README.md CHANGED
@@ -1,4 +1,4 @@
1
- > Note: this package is to be used on stores built with Hydrogen v2, based on Remix. If your store is built with the now deprecated Hydrogen v1, please use [version 1 of this package](https://www.npmjs.com/package/@okendo/shopify-hydrogen/v/1.3.0).
1
+ > Note: this package is to be used on stores built with Hydrogen v2, based on Remix. If your store is built with the deprecated Hydrogen v1, please use the [version 1](https://www.npmjs.com/package/@okendo/shopify-hydrogen/v/1.3.0) of this package.
2
2
 
3
3
  # Okendo Hydrogen 2 (Remix) React Components
4
4
 
@@ -9,47 +9,284 @@ This package brings [Okendo's review widgets](https://www.okendo.io/blog/widget-
9
9
  - A Shopify store with the [**Okendo: Product Reviews & UCG**](https://apps.shopify.com/okendo-reviews) app installed and configured.
10
10
  - For existing merchants, your store must be upgraded to Okendo's Widget Plus widgets. It is free to upgrade. For more information please [contact Okendo Support](mailto:support@okendo.io).
11
11
  - A current Okendo subscription.
12
- - A [Storefront access token](https://github.com/okendo/okendo-shopify-hydrogen-demo/wiki/Configure-Shopify-Storefront-API).
13
12
  - A [Shopify Hydrogen](https://hydrogen.shopify.dev/) app.
14
13
 
15
14
  ## Demo Store
16
15
 
17
16
  Our demo store, which is based on the demo store provided by Shopify, can be found [here](https://github.com/okendo/okendo-shopify-hydrogen-demo).
18
17
 
19
- > Note: there have been multiple versions of Shopify's Hydrogen demo store. If your project is based on an old version of it, consult our version history to find out how to add Okendo to it.
18
+ > Note: there have been multiple versions of Shopify's Hydrogen demo store. If your project is based on an old version of it, consult the history of our dome store's repository.
20
19
 
21
- ## How it works
20
+ ## Exposition of Shopify Metafields <a id="expose-shopify-metafields" name="expose-shopify-metafields"></a>
22
21
 
23
- This package provides:
22
+ Okendo Reviews use Product and Shop [metafields](https://help.shopify.com/en/manual/custom-data/metafields). You will need to expose these metafields so that they can be retrieved by your Hydrogen app.
24
23
 
25
- - one function: `getOkendoProviderData`
26
- - three React components: `OkendoProvider`, `OkendoStarRating`, and `OkendoReviews`
24
+ At the moment, Shopify does not have a way of exposing Shop Metafields through their admin UI, so the preferred method is to [contact Okendo's Support](mailto:support@okendo.io).
27
25
 
28
- The function `getOkendoProviderData` needs to be called in the `loader` function of the `root.tsx` file in the Hydrogen 2 store. The data is then retrieved in `App` through `useLoaderData` and provided to `OkendoProvider` which is added in the `body` of the HTML returned by `App`.
26
+ <details>
29
27
 
30
- Then, the components `OkendoStarRating` and `OkendoReviews` can be added on the store pages. There are a few more bits of configuration to do, please see below.
28
+ <summary>If you're a technical user however, you can click here and follow the method to expose the metafields via the storefront API.</summary>
29
+
30
+ ### Exposing Metafields via GraphQL
31
+
32
+ You will need a **Storefront access token** with the following API access scopes:
33
+
34
+ ```
35
+ unauthenticated_read_content
36
+ unauthenticated_read_customers
37
+ unauthenticated_read_product_listings
38
+ unauthenticated_read_product_inventory
39
+ unauthenticated_read_product_pickup_locations
40
+ unauthenticated_read_product_tags
41
+ ```
42
+
43
+ Follow the instructions on [this page](https://help.shopify.com/en/manual/apps/app-types/custom-apps#create-and-install-a-custom-app) to create it.
44
+
45
+ #### Note
46
+
47
+ Shopify is in the process of deprecating `metafieldStorefrontVisibilityCreate` in favour of `metafieldDefinitionCreate`. The following method uses `metafieldDefinitionCreate`. If you're having trouble with it, you will find below the deprecated method using `metafieldStorefrontVisibilityCreate`.
48
+
49
+ #### Using Curl
50
+
51
+ Open a new terminal or PowerShell window, then:
52
+
53
+ 1. Run the following command to expose the `WidgetPreRenderStyleTags` shop metafield:
54
+
55
+ ```bash
56
+ curl -X POST \
57
+ https://{shop}.myshopify.com/admin/api/2023-07/graphql.json \
58
+ -H 'Content-Type: application/graphql' \
59
+ -H 'X-Shopify-Access-Token: {access_token}' \
60
+ -d '
61
+ mutation {
62
+ metafieldDefinitionCreate(
63
+ definition: {
64
+ name: "WidgetPreRenderStyleTags"
65
+ namespace: "okendo"
66
+ key: "WidgetPreRenderStyleTags"
67
+ type: "multi_line_text_field"
68
+ ownerType: SHOP
69
+ visibleToStorefrontApi: true
70
+ }
71
+ ) {
72
+ createdDefinition { id name }
73
+ userErrors { field message code }
74
+ }
75
+ }
76
+ '
77
+ ```
78
+
79
+ 2. Run the following command to expose the `WidgetPreRenderBodyStyleTags` shop metafield:
80
+
81
+ ```bash
82
+ curl -X POST \
83
+ https://{shop}.myshopify.com/admin/api/2023-07/graphql.json \
84
+ -H 'Content-Type: application/graphql' \
85
+ -H 'X-Shopify-Access-Token: {access_token}' \
86
+ -d '
87
+ mutation {
88
+ metafieldDefinitionCreate(
89
+ definition: {
90
+ name: "WidgetPreRenderBodyStyleTags"
91
+ namespace: "okendo"
92
+ key: "WidgetPreRenderBodyStyleTags"
93
+ type: "multi_line_text_field"
94
+ ownerType: SHOP
95
+ visibleToStorefrontApi: true
96
+ }
97
+ ) {
98
+ createdDefinition { id name }
99
+ userErrors { field message code }
100
+ }
101
+ }
102
+ '
103
+ ```
104
+
105
+ 3. Run the following command to expose the `ReviewsWidgetSnippet` product metafield:
106
+
107
+ ```bash
108
+ curl -X POST \
109
+ https://{shop}.myshopify.com/admin/api/2023-07/graphql.json \
110
+ -H 'Content-Type: application/graphql' \
111
+ -H 'X-Shopify-Access-Token: {access_token}' \
112
+ -d '
113
+ mutation {
114
+ metafieldDefinitionCreate(
115
+ definition: {
116
+ name: "ReviewsWidgetSnippet"
117
+ namespace: "okendo"
118
+ key: "ReviewsWidgetSnippet"
119
+ type: "multi_line_text_field"
120
+ ownerType: PRODUCT
121
+ visibleToStorefrontApi: true
122
+ }
123
+ ) {
124
+ createdDefinition { id name }
125
+ userErrors { field message code }
126
+ }
127
+ }
128
+ '
129
+ ```
130
+
131
+ 4. Run the following command to expose the `StarRatingSnippet` the product metafield:
132
+
133
+ ```bash
134
+ curl -X POST \
135
+ https://{shop}.myshopify.com/admin/api/2023-07/graphql.json \
136
+ -H 'Content-Type: application/graphql' \
137
+ -H 'X-Shopify-Access-Token: {access_token}' \
138
+ -d '
139
+ mutation {
140
+ metafieldDefinitionCreate(
141
+ definition: {
142
+ name: "StarRatingSnippet"
143
+ namespace: "okendo"
144
+ key: "StarRatingSnippet"
145
+ type: "multi_line_text_field"
146
+ ownerType: PRODUCT
147
+ visibleToStorefrontApi: true
148
+ }
149
+ ) {
150
+ createdDefinition { id name }
151
+ userErrors { field message code }
152
+ }
153
+ }
154
+ '
155
+ ```
156
+
157
+ ### Using GraphQL IDE
31
158
 
32
- ## Expose Shopify Metafields <a id="expose-shopify-metafields" name="expose-shopify-metafields"></a>
159
+ Open your GraphQL IDE (such as Postman) and make `POST` requests with the following details:
33
160
 
34
- Okendo Reviews use Product and Shop [metafields](https://shopify.dev/api/examples/metafields). You will need to expose these metafields so that they can be retrieved by your Hydrogen app.
161
+ - **URL:** https://{shop}.myshopify.com/admin/api/2023-07/graphql.json
162
+ - **Headers:** - X-Shopify-Access-Token: {access_token} - Content-Type: application/json
35
163
 
36
- At the moment, Shopify does not have a way of exposing Shop Metafields through their admin UI, so the preferred method is to [contact Okendo Support](mailto:support@okendo.io).
164
+ 1. Execute the following request to expose the `WidgetPreRenderStyleTags` shop metafield:
165
+
166
+ ```graphql
167
+ mutation {
168
+ metafieldDefinitionCreate(
169
+ definition: {
170
+ name: "WidgetPreRenderStyleTags"
171
+ namespace: "okendo"
172
+ key: "WidgetPreRenderStyleTags"
173
+ type: "multi_line_text_field"
174
+ ownerType: SHOP
175
+ visibleToStorefrontApi: true
176
+ }
177
+ ) {
178
+ createdDefinition {
179
+ id
180
+ name
181
+ }
182
+ userErrors {
183
+ field
184
+ message
185
+ code
186
+ }
187
+ }
188
+ }
189
+ ```
190
+
191
+ 2. Execute the following request to expose the `WidgetPreRenderBodyStyleTags` shop metafield:
192
+
193
+ ```graphql
194
+ mutation {
195
+ metafieldDefinitionCreate(
196
+ definition: {
197
+ name: "WidgetPreRenderBodyStyleTags"
198
+ namespace: "okendo"
199
+ key: "WidgetPreRenderBodyStyleTags"
200
+ type: "multi_line_text_field"
201
+ ownerType: SHOP
202
+ visibleToStorefrontApi: true
203
+ }
204
+ ) {
205
+ createdDefinition {
206
+ id
207
+ name
208
+ }
209
+ userErrors {
210
+ field
211
+ message
212
+ code
213
+ }
214
+ }
215
+ }
216
+ ```
217
+
218
+ 3. Execute the following request to expose the `ReviewsWidgetSnippet` product metafield:
219
+
220
+ ```graphql
221
+ mutation {
222
+ metafieldDefinitionCreate(
223
+ definition: {
224
+ name: "ReviewsWidgetSnippet"
225
+ namespace: "okendo"
226
+ key: "ReviewsWidgetSnippet"
227
+ type: "multi_line_text_field"
228
+ ownerType: PRODUCT
229
+ visibleToStorefrontApi: true
230
+ }
231
+ ) {
232
+ createdDefinition {
233
+ id
234
+ name
235
+ }
236
+ userErrors {
237
+ field
238
+ message
239
+ code
240
+ }
241
+ }
242
+ }
243
+ ```
244
+
245
+ 4. Execute the following request to expose the `StarRatingSnippet` the product metafield:
246
+
247
+ ```graphql
248
+ mutation {
249
+ metafieldDefinitionCreate(
250
+ definition: {
251
+ name: "StarRatingSnippet"
252
+ namespace: "okendo"
253
+ key: "StarRatingSnippet"
254
+ type: "multi_line_text_field"
255
+ ownerType: PRODUCT
256
+ visibleToStorefrontApi: true
257
+ }
258
+ ) {
259
+ createdDefinition {
260
+ id
261
+ name
262
+ }
263
+ userErrors {
264
+ field
265
+ message
266
+ code
267
+ }
268
+ }
269
+ }
270
+ ```
271
+
272
+ **References**
273
+
274
+ - [https://shopify.dev/api/examples/metafields#step-1-expose-metafields](https://shopify.dev/api/examples/metafields#step-1-expose-metafields)
275
+ - [https://shopify.dev/api/admin-graphql/2023-07/mutations/metafieldDefinitionCreate](https://shopify.dev/api/admin-graphql/2023-07/mutations/metafieldDefinitionCreate)
37
276
 
38
277
  <details>
39
- <summary>For technical users, follow this method to expose metafields via the storefront API</summary>
40
278
 
41
- ### Exposing Metafields via GraphQL
279
+ <summary>If you're having trouble with `metafieldDefinitionCreate`, click here to see the deprecated method, using `metafieldStorefrontVisibilityCreate`.</summary>
42
280
 
43
281
  #### Using Curl
44
282
 
45
- You can also expose the required Okendo Shopify metafields by using GraphQL with curl.
283
+ Open a new terminal or PowerShell window, then:
46
284
 
47
- 1. Open a new terminal or PowerShell window.
48
- 2. Run the following command to expose the `WidgetPreRenderStyleTags` shop metafield:
285
+ 1. Run the following command to expose the `WidgetPreRenderStyleTags` shop metafield:
49
286
 
50
287
  ```bash
51
288
  curl -X POST \
52
- https://{shop}.myshopify.com/admin/api/2022-04/graphql.json \
289
+ https://{shop}.myshopify.com/admin/api/2023-07/graphql.json \
53
290
  -H 'Content-Type: application/graphql' \
54
291
  -H 'X-Shopify-Access-Token: {access_token}' \
55
292
  -d '
@@ -73,11 +310,11 @@ mutation {
73
310
  '
74
311
  ```
75
312
 
76
- 3. Run the following command to expose the `WidgetPreRenderBodyStyleTags` shop metafield:
313
+ 2. Run the following command to expose the `WidgetPreRenderBodyStyleTags` shop metafield:
77
314
 
78
315
  ```bash
79
316
  curl -X POST \
80
- https://{shop}.myshopify.com/admin/api/2022-04/graphql.json \
317
+ https://{shop}.myshopify.com/admin/api/2023-07/graphql.json \
81
318
  -H 'Content-Type: application/graphql' \
82
319
  -H 'X-Shopify-Access-Token: {access_token}' \
83
320
  -d '
@@ -101,11 +338,11 @@ mutation {
101
338
  '
102
339
  ```
103
340
 
104
- 4. Run the following command to expose the `ReviewsWidgetSnippet` product metafield:
341
+ 3. Run the following command to expose the `ReviewsWidgetSnippet` product metafield:
105
342
 
106
343
  ```bash
107
344
  curl -X POST \
108
- https://{shop}.myshopify.com/admin/api/2022-04/graphql.json \
345
+ https://{shop}.myshopify.com/admin/api/2023-07/graphql.json \
109
346
  -H 'Content-Type: application/graphql' \
110
347
  -H 'X-Shopify-Access-Token: {access_token}' \
111
348
  -d '
@@ -129,11 +366,11 @@ mutation {
129
366
  '
130
367
  ```
131
368
 
132
- 5. Run the following command to expose the `StarRatingSnippet` the product metafield:
369
+ 4. Run the following command to expose the `StarRatingSnippet` the product metafield:
133
370
 
134
371
  ```bash
135
372
  curl -X POST \
136
- https://{shop}.myshopify.com/admin/api/2022-04/graphql.json \
373
+ https://{shop}.myshopify.com/admin/api/2023-07/graphql.json \
137
374
  -H 'Content-Type: application/graphql' \
138
375
  -H 'X-Shopify-Access-Token: {access_token}' \
139
376
  -d '
@@ -159,12 +396,12 @@ mutation {
159
396
 
160
397
  ### Using GraphQL IDE
161
398
 
162
- 1. Open your GraphQL IDE (such as Postman) and make a `POST` request with the following details:
399
+ Open your GraphQL IDE (such as Postman) and make `POST` requests with the following details:
163
400
 
164
- - **URL:** https://{shop}.myshopify.com/admin/api/2022-04/graphql.json
401
+ - **URL:** https://{shop}.myshopify.com/admin/api/2023-07/graphql.json
165
402
  - **Headers:** - X-Shopify-Access-Token: {access_token} - Content-Type: application/json
166
403
 
167
- 2. Execute the following request to expose the `WidgetPreRenderStyleTags` shop metafield:
404
+ 1. Execute the following request to expose the `WidgetPreRenderStyleTags` shop metafield:
168
405
 
169
406
  ```graphql
170
407
  mutation {
@@ -186,7 +423,7 @@ mutation {
186
423
  }
187
424
  ```
188
425
 
189
- 3. Execute the following request to expose the `WidgetPreRenderBodyStyleTags` shop metafield:
426
+ 2. Execute the following request to expose the `WidgetPreRenderBodyStyleTags` shop metafield:
190
427
 
191
428
  ```graphql
192
429
  mutation {
@@ -208,7 +445,7 @@ mutation {
208
445
  }
209
446
  ```
210
447
 
211
- 4. Execute the following request to expose the `ReviewsWidgetSnippet` product metafield:
448
+ 3. Execute the following request to expose the `ReviewsWidgetSnippet` product metafield:
212
449
 
213
450
  ```graphql
214
451
  mutation {
@@ -230,7 +467,7 @@ mutation {
230
467
  }
231
468
  ```
232
469
 
233
- 5. Execute the following request to expose the `StarRatingSnippet` the product metafield:
470
+ 4. Execute the following request to expose the `StarRatingSnippet` the product metafield:
234
471
 
235
472
  ```graphql
236
473
  mutation {
@@ -248,14 +485,24 @@ mutation {
248
485
  }
249
486
  ```
250
487
 
251
- **References**
488
+ </details>
252
489
 
253
- - [https://shopify.dev/api/examples/metafields#step-1-expose-metafields](https://shopify.dev/api/examples/metafields#step-1-expose-metafields)
254
- - [https://shopify.dev/api/admin-graphql/2022-04/mutations/metafieldstorefrontvisibilitycreate](https://shopify.dev/api/admin-graphql/2022-04/mutations/metafieldstorefrontvisibilitycreate)
255
490
  </details>
256
491
 
257
492
  ## Installation
258
493
 
494
+ This package provides:
495
+
496
+ - one function: `getOkendoProviderData`,
497
+ - one provider: `OkendoProvider`,
498
+ - two React components: `OkendoStarRating` and `OkendoReviews`.
499
+
500
+ The function `getOkendoProviderData` needs to be called in the `loader` function of `root.tsx` in the Hydrogen 2 store. The data is then passed to `OkendoProvider`, which is added to your website's `body` and wraps everything in it.
501
+
502
+ > Important: `OkendoProvider` supports two ways of loading the data returned by `getOkendoProviderData`: either as a promise, or as the data itself. Its behaviour is different between the two ways — this is explained below.
503
+
504
+ Then, the components `OkendoStarRating` and `OkendoReviews` can be added on the store pages. There are a few more bits of configuration to do, please see below.
505
+
259
506
  > The code examples provided in this section are based on the Shopify template store created by running `npm create @shopify/hydrogen@latest` (see [Shopify's documentation](https://shopify.dev/docs/custom-storefronts/hydrogen/getting-started)). You will find the following steps already done in [our demo store](https://github.com/okendo/okendo-shopify-hydrogen-demo).
260
507
 
261
508
  Run:
@@ -266,6 +513,26 @@ npm i @okendo/shopify-hydrogen
266
513
 
267
514
  ### `app/root.tsx`
268
515
 
516
+ `OkendoProvider` supports two ways of loading the data returned by `getOkendoProviderData`:
517
+
518
+ - **as a promise**: in this case, the query getting the data is deferred, which allows your page to load as quickly as it does without Okendo's widgets. When the data is ready, it is sent to the browser, and Okendo's widgets are rendered. Blank placeholders are shown until the widgets are rendered. You can customise these placeholders to show loading spinners or skeletons that fit well with your store's theme.
519
+ - **as the data**: in this case, the query getting the data needs to complete before your page loads, which can add a couple hundreds milliseconds of loading time. Widgets are then rendered server-side, and so appear as soon as your page loads.
520
+
521
+ To summarise the differences between the two behaviours:
522
+
523
+ - Pass the promise to `OkendoProvider` — so don't use `await` with `getOkendoProviderData`:
524
+
525
+ - The page loading time won't be increased at all.
526
+ - The widgets will be rendered client-side.
527
+ - Placeholders (which are customisable) are shown until the widgets are rendered.
528
+
529
+ - Pass the data to `OkendoProvider` — so use `await` with `getOkendoProviderData`:
530
+ - The page loading time can be increased by a couple hundreds milliseconds.
531
+ - The widgets will be rendered server-side.
532
+ - The widgets are shown as soon as the page loads — no placeholders needed.
533
+
534
+ You can easily experiment with the two ways, and decide which is the one you'd like to keep for your store.
535
+
269
536
  Open `app/root.tsx` and add the following import:
270
537
 
271
538
  ```ts
@@ -275,17 +542,19 @@ import {
275
542
  } from "@okendo/shopify-hydrogen";
276
543
  ```
277
544
 
278
- Locate the `loader` function, append `okendoProviderData` to the returned data as shown below, and set `subscriberId` to your Okendo subscriber ID:
545
+ Locate the `loader` function, append `okendoProviderData` to the returned data as shown below, and set `subscriberId` to your Okendo subscriber ID.
546
+
547
+ As explained above, set `okendoProviderData` to either `getOkendoProviderData(...)`, or `await getOkendoProviderData(...)`:
279
548
 
280
549
  ```ts
281
550
  return defer(
282
551
  {
283
552
  ...
284
- okendoProviderData: await getOkendoProviderData({
553
+ okendoProviderData: /* place `await` here if you want server-rendered widgets */ getOkendoProviderData({
285
554
  context,
286
555
  subscriberId: "<your-okendo-subscriber-id>",
287
556
  }),
288
- }
557
+ },
289
558
  );
290
559
  ```
291
560
 
@@ -299,7 +568,7 @@ Locate the `App` function, add the `meta` tag `oke:subscriber_id` to `head`, and
299
568
  ...
300
569
  ```
301
570
 
302
- Append `OkendoProvider` to `body`, and pass it the data returned by `getOkendoProviderData`. If Content Security Policy is active in your project, you also need to provide the `nonce` (available with `const nonce = useNonce()` in Shopify's Hydrogen demo store):
571
+ Append `OkendoProvider` to `body`, and pass it the promise — or the data returned by `getOkendoProviderData`. If Content Security Policy is active in your project, you also need to provide the `nonce` (available with `const nonce = useNonce()` in Shopify's Hydrogen demo store):
303
572
 
304
573
  ```tsx
305
574
  ...
@@ -307,8 +576,9 @@ Append `OkendoProvider` to `body`, and pass it the data returned by `getOkendoPr
307
576
  <OkendoProvider
308
577
  nonce={nonce}
309
578
  okendoProviderData={data.okendoProviderData}
310
- />
311
- ...
579
+ >
580
+ ...
581
+ </OkendoProvider>
312
582
  </body>
313
583
  ...
314
584
  ```
@@ -350,7 +620,7 @@ const { nonce, header, NonceProvider } = createContentSecurityPolicy({
350
620
  "'self'",
351
621
  "https://d3hw6dc1ow8pp2.cloudfront.net",
352
622
  "https://d3g5hqndtiniji.cloudfront.net",
353
- "https://cdn-static.okendo.io"
623
+ "https://cdn-static.okendo.io",
354
624
  ],
355
625
  styleSrcElem: [
356
626
  "'self'",
@@ -491,6 +761,8 @@ For instance, we can add it below the product title, like this:
491
761
  </small>
492
762
  ```
493
763
 
764
+ > Note: if the widgets are rendered client-side (if you don't use `await` when calling `getOkendoProviderData`), you can provide your own placeholder by using the `placeholder` property of `OkendoStarRating`.
765
+
494
766
  We now have the Okendo Star Rating widget visible on our page:
495
767
 
496
768
  ![Okendo's Star Rating widget](./okendo-star-rating-widget.png)
@@ -601,6 +873,8 @@ For instance, we can add it below the product section, like this:
601
873
  </>
602
874
  ```
603
875
 
876
+ > Note: if the widgets are rendered client-side (if you don't use `await` when calling `getOkendoProviderData`), you can provide your own placeholder by using the `placeholder` property of `OkendoReviews`.
877
+
604
878
  Tweak the type of the `product` prop of `ProductMain`:
605
879
 
606
880
  ```ts
package/dist/cjs/index.js CHANGED
@@ -1 +1 @@
1
- "use strict";var t=require("react");const e=({dataAttributes:e,metafieldContent:n=""})=>{const r=t.useRef(null),o=function(e){const n=t.useRef();return t.useEffect((()=>{n.current=e})),n.current}(e),i=()=>{r.current&&window.okeWidgetApi.initWidget(r.current)};return t.useEffect((()=>{if(!o||e["data-oke-widget"]!==o["data-oke-widget"]||e["data-oke-star-rating"]!==o["data-oke-star-rating"]||e["data-oke-reviews-product-id"]!==o["data-oke-reviews-product-id"])return window.okeWidgetApi&&r.current?i():document.addEventListener("oke-script-loaded",i),()=>{document.removeEventListener("oke-script-loaded",i)}}),[e,o]),t.createElement("div",{ref:r,key:JSON.stringify(e),...e,dangerouslySetInnerHTML:n?{__html:n}:void 0})},n=/^[0-9]*$/;function r(t){if(t)return`shopify-${n.test(t)?t:t.split("/").slice(-1)[0]}`}exports.OKENDO_PRODUCT_REVIEWS_FRAGMENT='#graphql\n\tfragment OkendoReviewsSnippet on Product {\n\t\tokendoReviewsSnippet: metafield(\n\t\t\tnamespace: "okendo"\n\t\t\tkey: "ReviewsWidgetSnippet"\n\t\t) {\n\t\t\tvalue\n\t\t}\n\t}\n',exports.OKENDO_PRODUCT_STAR_RATING_FRAGMENT='#graphql\n\tfragment OkendoStarRatingSnippet on Product {\n\t\tokendoStarRatingSnippet: metafield(\n\t\t\tnamespace: "okendo"\n\t\t\tkey: "StarRatingSnippet"\n\t\t) {\n\t\t\tvalue\n\t\t}\n\t}\n',exports.OkendoProvider=({nonce:e="",okendoProviderData:n,productUrlFormatter:r})=>{if(!n)return null;const{reviewsHeaderConfig:o,cssVariables:i,customCss:s,initScriptContents:a,preRenderStyleTags:d,starSymbols:c}=n,l="function"==typeof r?r.toString():"string"==typeof r?r:"(product) =>\n\t\t\t\t\tproduct && product.productHandle\n\t\t\t\t\t\t? \"/products/\" + product.productHandle + \"/\" + (product.variantId ? '?variantId=' + product.variantId : '')\n\t\t\t\t\t\t: undefined",u=(i??"").replace('<style id="oke-css-vars">',"").replace("</style>",""),p=s?s.replace('<style id="oke-reviews-custom-css">',"").replace("</style>",""):"";return t.createElement(t.Fragment,null,t.createElement("script",{nonce:e,id:"oke-reviews-settings",type:"application/json",dangerouslySetInnerHTML:{__html:JSON.stringify(o)}}),t.createElement("style",{nonce:e,id:"oke-css-vars",dangerouslySetInnerHTML:{__html:u}}),p&&t.createElement("style",{nonce:e,id:"oke-reviews-custom-css",dangerouslySetInnerHTML:{__html:p}}),a&&t.createElement("script",{nonce:e,dangerouslySetInnerHTML:{__html:a}}),t.createElement("script",{nonce:e,type:"text/javascript",dangerouslySetInnerHTML:{__html:`window.okeProductUrlFormatter = ${l.toString()}`}}),d&&t.createElement("div",{dangerouslySetInnerHTML:{__html:d}}),c&&t.createElement("div",{dangerouslySetInnerHTML:{__html:c}}))},exports.OkendoReviews=({productId:n,okendoReviewsSnippet:o})=>{const i={"data-oke-widget":"","data-oke-reviews-product-id":r(n)};return t.createElement(e,{dataAttributes:i,metafieldContent:o?.value})},exports.OkendoStarRating=({productId:n,okendoStarRatingSnippet:o})=>{const i={"data-oke-star-rating":"","data-oke-reviews-product-id":r(n)};return t.createElement(e,{dataAttributes:i,metafieldContent:o?.value})},exports.getOkendoProviderData=async({context:t,subscriberId:e,apiDomain:n,cdnDomain:r})=>{const o=`https://${n||"api.okendo.io/v1"}/stores/${e}/widget_plus_settings`,i=await fetch(o);if(!i.ok)return console.error(`Failed to retrieve subscriber settings for subscriber ID '${e}'.`),null;const{reviewsHeaderConfig:s,cssVariables:a,customCss:d,starSymbols:c}=await i.json(),l=await fetch(`https://${r||"cdn-static.okendo.io"}/reviews-widget-plus/js/okendo-reviews.js`);if(!l.ok)return console.error("Failed to retrieve widget initialisation script."),null;const u=await l.text(),{shop:{widgetPreRenderStyleTags:p}}=await t.storefront.query('#graphql\n\t\tquery metafields {\n\t\t\tshop {\n\t\t\t\twidgetPreRenderStyleTags: metafield(\n\t\t\t\t\tnamespace: "okendo"\n\t\t\t\t\tkey: "WidgetPreRenderStyleTags"\n\t\t\t\t) {\n\t\t\t\t\tvalue\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t'),g=p?.value??"";return g||console.warn("Failed to retrieve pre-rendered widget style settings."),{reviewsHeaderConfig:s,cssVariables:a,customCss:d,initScriptContents:u,preRenderStyleTags:g,starSymbols:c}};
1
+ "use strict";var e=require("react"),t=require("@remix-run/react");const n=e.createContext(void 0),r=({children:t})=>{const[r,o]=e.useState(!1);return e.createElement(n.Provider,{value:{okendoDataLoaded:r,setOkendoDataLoaded:o}},t)},o=()=>{const t=e.useContext(n);if(void 0===t)throw new Error("useOkendoContext must be within OkendoContext");return t},a="cdn-static.okendo.io",i=({nonce:t="",okendoProviderData:n,productUrlFormatter:r,awaited:i,cdnDomain:s})=>{const{setOkendoDataLoaded:d}=o();if(e.useEffect((()=>{if(n&&d(!0),n&&i){const e=document.createElement("script");e.src=`https://${s||a}/reviews-widget-plus/js/okendo-reviews.js`,document.head.appendChild(e)}}),[n,d,i,s]),!n)return null;const{reviewsHeaderConfig:c,cssVariables:l,customCss:u,initScriptContents:p,preRenderStyleTags:m,starSymbols:g}=n,v=(l??"").replace('<style id="oke-css-vars">',"").replace("</style>",""),k=u?u.replace('<style id="oke-reviews-custom-css">',"").replace("</style>",""):"";return e.createElement(e.Fragment,null,e.createElement("script",{nonce:t,id:"oke-reviews-settings",type:"application/json",dangerouslySetInnerHTML:{__html:JSON.stringify(c)}}),e.createElement("style",{nonce:t,id:"oke-css-vars",dangerouslySetInnerHTML:{__html:v}}),k&&e.createElement("style",{nonce:t,id:"oke-reviews-custom-css",dangerouslySetInnerHTML:{__html:k}}),!i&&p&&e.createElement("script",{nonce:t,dangerouslySetInnerHTML:{__html:p}}),e.createElement("script",{nonce:t,type:"text/javascript",dangerouslySetInnerHTML:{__html:`window.okeProductUrlFormatter = ${"function"==typeof r?r.toString():"string"==typeof r?r:"(product) =>\n product && product.productHandle\n ? \"/products/\" + product.productHandle + \"/\" + (product.variantId ? '?variantId=' + product.variantId : '')\n : undefined"}`}}),m&&e.createElement("div",{dangerouslySetInnerHTML:{__html:m}}),g&&e.createElement("div",{dangerouslySetInnerHTML:{__html:g}}))};const s=({dataAttributes:t,metafieldContent:n=""})=>{const r=e.useRef(null),o=e.useRef(!1),a=function(t){const n=e.useRef();return e.useEffect((()=>{n.current=t})),n.current}(t),i=()=>{r.current&&(window.okeWidgetApi.initWidget(r.current),o.current=!0)};return e.useEffect((()=>{if(!a||t["data-oke-widget"]!==a["data-oke-widget"]||t["data-oke-star-rating"]!==a["data-oke-star-rating"]||t["data-oke-reviews-product-id"]!==a["data-oke-reviews-product-id"]||!o.current)return window.okeWidgetApi&&r.current?i():document.addEventListener("oke-script-loaded",i),()=>{document.removeEventListener("oke-script-loaded",i)}}),[t,a]),e.createElement("div",{ref:r,key:JSON.stringify(t),...t,dangerouslySetInnerHTML:n?{__html:n}:void 0})},d=/^[0-9]*$/;function c(e){if(e)return`shopify-${d.test(e)?e:e.split("/").slice(-1)[0]}`}exports.OKENDO_PRODUCT_REVIEWS_FRAGMENT='#graphql\n\tfragment OkendoReviewsSnippet on Product {\n\t\tokendoReviewsSnippet: metafield(\n\t\t\tnamespace: "okendo"\n\t\t\tkey: "ReviewsWidgetSnippet"\n\t\t) {\n\t\t\tvalue\n\t\t}\n\t}\n',exports.OKENDO_PRODUCT_STAR_RATING_FRAGMENT='#graphql\n\tfragment OkendoStarRatingSnippet on Product {\n\t\tokendoStarRatingSnippet: metafield(\n\t\t\tnamespace: "okendo"\n\t\t\tkey: "StarRatingSnippet"\n\t\t) {\n\t\t\tvalue\n\t\t}\n\t}\n',exports.OkendoProvider=({nonce:n="",okendoProviderData:o,productUrlFormatter:a,cdnDomain:s,children:d})=>e.createElement(r,null,o&&"then"in o?e.createElement(e.Suspense,null,e.createElement(t.Await,{resolve:o},(t=>e.createElement(i,{nonce:n,okendoProviderData:t,productUrlFormatter:a,cdnDomain:s,awaited:!0})))):e.createElement(i,{nonce:n,okendoProviderData:o,productUrlFormatter:a,cdnDomain:s}),d),exports.OkendoReviews=({productId:t,okendoReviewsSnippet:n,placeholder:r})=>{const{okendoDataLoaded:a}=o(),i={"data-oke-widget":"","data-oke-reviews-product-id":c(t)};return a?e.createElement(s,{dataAttributes:i,metafieldContent:n?.value}):r||e.createElement("div",{style:{height:"350px"}})},exports.OkendoStarRating=({productId:t,okendoStarRatingSnippet:n,placeholder:r})=>{const{okendoDataLoaded:a}=o(),i={"data-oke-star-rating":"","data-oke-reviews-product-id":c(t)};return a?e.createElement(s,{dataAttributes:i,metafieldContent:n?.value}):r||e.createElement("div",{style:{height:"20px"}})},exports.getOkendoProviderData=async({context:e,subscriberId:t,apiDomain:n,cdnDomain:r})=>{const o=`https://${n||"api.okendo.io/v1"}/stores/${t}/widget_plus_settings`,i=await fetch(o);if(!i.ok)return console.error(`Failed to retrieve subscriber settings for subscriber ID '${t}'.`),null;const{reviewsHeaderConfig:s,cssVariables:d,customCss:c,starSymbols:l}=await i.json(),u=await fetch(`https://${r||a}/reviews-widget-plus/js/okendo-reviews.js`);if(!u.ok)return console.error("Failed to retrieve widget initialisation script."),null;const p=await u.text(),{shop:{widgetPreRenderStyleTags:m}}=await e.storefront.query('#graphql\n\t\tquery metafields {\n\t\t\tshop {\n\t\t\t\twidgetPreRenderStyleTags: metafield(\n\t\t\t\t\tnamespace: "okendo"\n\t\t\t\t\tkey: "WidgetPreRenderStyleTags"\n\t\t\t\t) {\n\t\t\t\t\tvalue\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t'),g=m?.value??"";return g||console.warn("Failed to retrieve pre-rendered widget style settings."),{reviewsHeaderConfig:s,cssVariables:d,customCss:c,initScriptContents:p,preRenderStyleTags:g,starSymbols:l}};
@@ -1,7 +1,8 @@
1
- import type { Storefront } from "@shopify/hydrogen";
2
- import { type FC } from "react";
1
+ import type { WidgetPlus } from '@okendo/reviews-common';
2
+ import type { Storefront } from '@shopify/hydrogen';
3
+ import React, { type PropsWithChildren } from 'react';
3
4
  interface OkendoProviderData {
4
- reviewsHeaderConfig: Record<string, unknown>;
5
+ reviewsHeaderConfig: WidgetPlus.ReviewsHeaderConfig;
5
6
  cssVariables: string;
6
7
  customCss?: string;
7
8
  initScriptContents: string;
@@ -15,14 +16,7 @@ export declare const getOkendoProviderData: ({ context, subscriberId, apiDomain,
15
16
  subscriberId: string;
16
17
  apiDomain?: string;
17
18
  cdnDomain?: string;
18
- }) => Promise<{
19
- reviewsHeaderConfig: import("@okendo/reviews-common").WidgetPlus.ReviewsHeaderConfig;
20
- cssVariables: string;
21
- customCss: string | undefined;
22
- initScriptContents: string;
23
- preRenderStyleTags: string;
24
- starSymbols: string;
25
- } | null>;
19
+ }) => Promise<OkendoProviderData | null>;
26
20
  interface ReviewProduct {
27
21
  productHandle?: string;
28
22
  productId: string;
@@ -34,10 +28,17 @@ interface OkendoProviderProps {
34
28
  * this is required if Content Security Policy is set up on your store.
35
29
  */
36
30
  nonce?: string;
37
- /** The data returned by `getOkendoProviderData` */
38
- okendoProviderData: Partial<OkendoProviderData> | null;
31
+ /**
32
+ * Either:
33
+ * - the data returned by the promise returned by `getOkendoProviderData`, using this will allow the
34
+ * Okendo widgets to show instantly on page load, but it will slightly increase the page load time;
35
+ * - the promise returned by `getOkendoProviderData`, using this will make the Okendo widgets
36
+ * show slightly after page load, but it have the advantage of not increasing the page load time.
37
+ */
38
+ okendoProviderData: Partial<OkendoProviderData> | null | Promise<Partial<OkendoProviderData> | null>;
39
39
  /** An optional product URL formatter */
40
40
  productUrlFormatter?: (product: ReviewProduct) => string;
41
+ cdnDomain?: string;
41
42
  }
42
- export declare const OkendoProvider: FC<OkendoProviderProps>;
43
+ export declare const OkendoProvider: ({ nonce, okendoProviderData, productUrlFormatter, cdnDomain, children, }: OkendoProviderProps & PropsWithChildren) => React.JSX.Element;
43
44
  export {};
@@ -1,11 +1,16 @@
1
- import { type FC } from "react";
2
- import { type MetafieldValue } from "../../internal/types";
1
+ import React from 'react';
2
+ import { type MetafieldValue } from '../../internal/types';
3
3
  export interface WithOkendoReviewsSnippet {
4
4
  okendoReviewsSnippet?: MetafieldValue | null;
5
5
  }
6
6
  interface OkendoReviewsProps {
7
7
  productId: string;
8
8
  okendoReviewsSnippet?: MetafieldValue | null;
9
+ /**
10
+ * Only used if the promise returned by `getOkendoProviderData` is given to the OkendoProvider
11
+ * without being awaited; in this case, the placeholder here will be shown until the widget is ready
12
+ */
13
+ placeholder?: JSX.Element;
9
14
  }
10
- export declare const OkendoReviews: FC<OkendoReviewsProps>;
15
+ export declare const OkendoReviews: ({ productId, okendoReviewsSnippet, placeholder }: OkendoReviewsProps) => React.JSX.Element;
11
16
  export {};
@@ -1,11 +1,16 @@
1
- import { type FC } from "react";
2
- import { type MetafieldValue } from "../../internal/types";
1
+ import React, { type ReactNode } from 'react';
2
+ import { type MetafieldValue } from '../../internal/types';
3
3
  export interface WithOkendoStarRatingSnippet {
4
4
  okendoStarRatingSnippet?: MetafieldValue | null;
5
5
  }
6
6
  interface OkendoStarRatingProps {
7
7
  productId: string;
8
8
  okendoStarRatingSnippet?: MetafieldValue | null;
9
+ /**
10
+ * Only used if the promise returned by `getOkendoProviderData` is given to the OkendoProvider
11
+ * without being awaited; in this case, the placeholder here will be shown until the widget is ready
12
+ */
13
+ placeholder?: ReactNode;
9
14
  }
10
- export declare const OkendoStarRating: FC<OkendoStarRatingProps>;
15
+ export declare const OkendoStarRating: ({ productId, okendoStarRatingSnippet, placeholder }: OkendoStarRatingProps) => string | number | true | Iterable<React.ReactNode> | React.JSX.Element;
11
16
  export {};
@@ -0,0 +1,10 @@
1
+ import React, { type Dispatch, type PropsWithChildren, type SetStateAction } from 'react';
2
+ export declare const OkendoContext: React.Context<{
3
+ okendoDataLoaded: boolean;
4
+ setOkendoDataLoaded: Dispatch<SetStateAction<boolean>>;
5
+ } | undefined>;
6
+ export declare const OkendoContextProvider: ({ children }: PropsWithChildren) => React.JSX.Element;
7
+ export declare const useOkendoContext: () => {
8
+ okendoDataLoaded: boolean;
9
+ setOkendoDataLoaded: Dispatch<SetStateAction<boolean>>;
10
+ };
@@ -1,10 +1,10 @@
1
- import type { ReviewsWidgetPlus } from "@okendo/reviews-widget-plus/dist-utils/ReviewsWidgetPlus";
2
- import { type FC } from "react";
1
+ import type { ReviewsWidgetPlus } from '@okendo/reviews-widget-plus/dist-utils/ReviewsWidgetPlus';
2
+ import React from 'react';
3
3
  export interface OkendoWidgetProps {
4
4
  dataAttributes: {
5
- "data-oke-widget"?: string;
6
- "data-oke-star-rating"?: string;
7
- "data-oke-reviews-product-id": string | undefined;
5
+ 'data-oke-widget'?: string;
6
+ 'data-oke-star-rating'?: string;
7
+ 'data-oke-reviews-product-id': string | undefined;
8
8
  };
9
9
  metafieldContent?: string;
10
10
  }
@@ -13,4 +13,4 @@ declare global {
13
13
  okeWidgetApi: ReviewsWidgetPlus.WidgetWindowApi;
14
14
  }
15
15
  }
16
- export declare const OkendoWidget: FC<OkendoWidgetProps>;
16
+ export declare const OkendoWidget: ({ dataAttributes, metafieldContent }: OkendoWidgetProps) => React.JSX.Element;
package/dist/esm/index.js CHANGED
@@ -1 +1 @@
1
- import t,{useRef as e,useEffect as n}from"react";const r=async({context:t,subscriberId:e,apiDomain:n,cdnDomain:r})=>{const o=`https://${n||"api.okendo.io/v1"}/stores/${e}/widget_plus_settings`,i=await fetch(o);if(!i.ok)return console.error(`Failed to retrieve subscriber settings for subscriber ID '${e}'.`),null;const{reviewsHeaderConfig:a,cssVariables:s,customCss:d,starSymbols:c}=await i.json(),l=await fetch(`https://${r||"cdn-static.okendo.io"}/reviews-widget-plus/js/okendo-reviews.js`);if(!l.ok)return console.error("Failed to retrieve widget initialisation script."),null;const u=await l.text(),{shop:{widgetPreRenderStyleTags:p}}=await t.storefront.query('#graphql\n\t\tquery metafields {\n\t\t\tshop {\n\t\t\t\twidgetPreRenderStyleTags: metafield(\n\t\t\t\t\tnamespace: "okendo"\n\t\t\t\t\tkey: "WidgetPreRenderStyleTags"\n\t\t\t\t) {\n\t\t\t\t\tvalue\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t'),g=p?.value??"";return g||console.warn("Failed to retrieve pre-rendered widget style settings."),{reviewsHeaderConfig:a,cssVariables:s,customCss:d,initScriptContents:u,preRenderStyleTags:g,starSymbols:c}},o=({nonce:e="",okendoProviderData:n,productUrlFormatter:r})=>{if(!n)return null;const{reviewsHeaderConfig:o,cssVariables:i,customCss:a,initScriptContents:s,preRenderStyleTags:d,starSymbols:c}=n,l="function"==typeof r?r.toString():"string"==typeof r?r:"(product) =>\n\t\t\t\t\tproduct && product.productHandle\n\t\t\t\t\t\t? \"/products/\" + product.productHandle + \"/\" + (product.variantId ? '?variantId=' + product.variantId : '')\n\t\t\t\t\t\t: undefined",u=(i??"").replace('<style id="oke-css-vars">',"").replace("</style>",""),p=a?a.replace('<style id="oke-reviews-custom-css">',"").replace("</style>",""):"";return t.createElement(t.Fragment,null,t.createElement("script",{nonce:e,id:"oke-reviews-settings",type:"application/json",dangerouslySetInnerHTML:{__html:JSON.stringify(o)}}),t.createElement("style",{nonce:e,id:"oke-css-vars",dangerouslySetInnerHTML:{__html:u}}),p&&t.createElement("style",{nonce:e,id:"oke-reviews-custom-css",dangerouslySetInnerHTML:{__html:p}}),s&&t.createElement("script",{nonce:e,dangerouslySetInnerHTML:{__html:s}}),t.createElement("script",{nonce:e,type:"text/javascript",dangerouslySetInnerHTML:{__html:`window.okeProductUrlFormatter = ${l.toString()}`}}),d&&t.createElement("div",{dangerouslySetInnerHTML:{__html:d}}),c&&t.createElement("div",{dangerouslySetInnerHTML:{__html:c}}))};const i=({dataAttributes:r,metafieldContent:o=""})=>{const i=e(null),a=function(t){const r=e();return n((()=>{r.current=t})),r.current}(r),s=()=>{i.current&&window.okeWidgetApi.initWidget(i.current)};return n((()=>{if(!a||r["data-oke-widget"]!==a["data-oke-widget"]||r["data-oke-star-rating"]!==a["data-oke-star-rating"]||r["data-oke-reviews-product-id"]!==a["data-oke-reviews-product-id"])return window.okeWidgetApi&&i.current?s():document.addEventListener("oke-script-loaded",s),()=>{document.removeEventListener("oke-script-loaded",s)}}),[r,a]),t.createElement("div",{ref:i,key:JSON.stringify(r),...r,dangerouslySetInnerHTML:o?{__html:o}:void 0})},a=/^[0-9]*$/;function s(t){if(t)return`shopify-${a.test(t)?t:t.split("/").slice(-1)[0]}`}const d=({productId:e,okendoReviewsSnippet:n})=>{const r={"data-oke-widget":"","data-oke-reviews-product-id":s(e)};return t.createElement(i,{dataAttributes:r,metafieldContent:n?.value})},c=({productId:e,okendoStarRatingSnippet:n})=>{const r={"data-oke-star-rating":"","data-oke-reviews-product-id":s(e)};return t.createElement(i,{dataAttributes:r,metafieldContent:n?.value})},l='#graphql\n\tfragment OkendoStarRatingSnippet on Product {\n\t\tokendoStarRatingSnippet: metafield(\n\t\t\tnamespace: "okendo"\n\t\t\tkey: "StarRatingSnippet"\n\t\t) {\n\t\t\tvalue\n\t\t}\n\t}\n',u='#graphql\n\tfragment OkendoReviewsSnippet on Product {\n\t\tokendoReviewsSnippet: metafield(\n\t\t\tnamespace: "okendo"\n\t\t\tkey: "ReviewsWidgetSnippet"\n\t\t) {\n\t\t\tvalue\n\t\t}\n\t}\n';export{u as OKENDO_PRODUCT_REVIEWS_FRAGMENT,l as OKENDO_PRODUCT_STAR_RATING_FRAGMENT,o as OkendoProvider,d as OkendoReviews,c as OkendoStarRating,r as getOkendoProviderData};
1
+ import e,{createContext as t,useState as n,useContext as r,Suspense as o,useEffect as a,useRef as i}from"react";import{Await as d}from"@remix-run/react";const s=t(void 0),c=({children:t})=>{const[r,o]=n(!1);return e.createElement(s.Provider,{value:{okendoDataLoaded:r,setOkendoDataLoaded:o}},t)},l=()=>{const e=r(s);if(void 0===e)throw new Error("useOkendoContext must be within OkendoContext");return e},u="cdn-static.okendo.io",p=async({context:e,subscriberId:t,apiDomain:n,cdnDomain:r})=>{const o=`https://${n||"api.okendo.io/v1"}/stores/${t}/widget_plus_settings`,a=await fetch(o);if(!a.ok)return console.error(`Failed to retrieve subscriber settings for subscriber ID '${t}'.`),null;const{reviewsHeaderConfig:i,cssVariables:d,customCss:s,starSymbols:c}=await a.json(),l=await fetch(`https://${r||u}/reviews-widget-plus/js/okendo-reviews.js`);if(!l.ok)return console.error("Failed to retrieve widget initialisation script."),null;const p=await l.text(),{shop:{widgetPreRenderStyleTags:m}}=await e.storefront.query('#graphql\n\t\tquery metafields {\n\t\t\tshop {\n\t\t\t\twidgetPreRenderStyleTags: metafield(\n\t\t\t\t\tnamespace: "okendo"\n\t\t\t\t\tkey: "WidgetPreRenderStyleTags"\n\t\t\t\t) {\n\t\t\t\t\tvalue\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t'),g=m?.value??"";return g||console.warn("Failed to retrieve pre-rendered widget style settings."),{reviewsHeaderConfig:i,cssVariables:d,customCss:s,initScriptContents:p,preRenderStyleTags:g,starSymbols:c}},m=({nonce:t="",okendoProviderData:n,productUrlFormatter:r,cdnDomain:a,children:i})=>e.createElement(c,null,n&&"then"in n?e.createElement(o,null,e.createElement(d,{resolve:n},(n=>e.createElement(g,{nonce:t,okendoProviderData:n,productUrlFormatter:r,cdnDomain:a,awaited:!0})))):e.createElement(g,{nonce:t,okendoProviderData:n,productUrlFormatter:r,cdnDomain:a}),i),g=({nonce:t="",okendoProviderData:n,productUrlFormatter:r,awaited:o,cdnDomain:i})=>{const{setOkendoDataLoaded:d}=l();if(a((()=>{if(n&&d(!0),n&&o){const e=document.createElement("script");e.src=`https://${i||u}/reviews-widget-plus/js/okendo-reviews.js`,document.head.appendChild(e)}}),[n,d,o,i]),!n)return null;const{reviewsHeaderConfig:s,cssVariables:c,customCss:p,initScriptContents:m,preRenderStyleTags:g,starSymbols:v}=n,k=(c??"").replace('<style id="oke-css-vars">',"").replace("</style>",""),w=p?p.replace('<style id="oke-reviews-custom-css">',"").replace("</style>",""):"";return e.createElement(e.Fragment,null,e.createElement("script",{nonce:t,id:"oke-reviews-settings",type:"application/json",dangerouslySetInnerHTML:{__html:JSON.stringify(s)}}),e.createElement("style",{nonce:t,id:"oke-css-vars",dangerouslySetInnerHTML:{__html:k}}),w&&e.createElement("style",{nonce:t,id:"oke-reviews-custom-css",dangerouslySetInnerHTML:{__html:w}}),!o&&m&&e.createElement("script",{nonce:t,dangerouslySetInnerHTML:{__html:m}}),e.createElement("script",{nonce:t,type:"text/javascript",dangerouslySetInnerHTML:{__html:`window.okeProductUrlFormatter = ${"function"==typeof r?r.toString():"string"==typeof r?r:"(product) =>\n product && product.productHandle\n ? \"/products/\" + product.productHandle + \"/\" + (product.variantId ? '?variantId=' + product.variantId : '')\n : undefined"}`}}),g&&e.createElement("div",{dangerouslySetInnerHTML:{__html:g}}),v&&e.createElement("div",{dangerouslySetInnerHTML:{__html:v}}))};const v=({dataAttributes:t,metafieldContent:n=""})=>{const r=i(null),o=i(!1),d=function(e){const t=i();return a((()=>{t.current=e})),t.current}(t),s=()=>{r.current&&(window.okeWidgetApi.initWidget(r.current),o.current=!0)};return a((()=>{if(!d||t["data-oke-widget"]!==d["data-oke-widget"]||t["data-oke-star-rating"]!==d["data-oke-star-rating"]||t["data-oke-reviews-product-id"]!==d["data-oke-reviews-product-id"]||!o.current)return window.okeWidgetApi&&r.current?s():document.addEventListener("oke-script-loaded",s),()=>{document.removeEventListener("oke-script-loaded",s)}}),[t,d]),e.createElement("div",{ref:r,key:JSON.stringify(t),...t,dangerouslySetInnerHTML:n?{__html:n}:void 0})},k=/^[0-9]*$/;function w(e){if(e)return`shopify-${k.test(e)?e:e.split("/").slice(-1)[0]}`}const y=({productId:t,okendoReviewsSnippet:n,placeholder:r})=>{const{okendoDataLoaded:o}=l(),a={"data-oke-widget":"","data-oke-reviews-product-id":w(t)};return o?e.createElement(v,{dataAttributes:a,metafieldContent:n?.value}):r||e.createElement("div",{style:{height:"350px"}})},f=({productId:t,okendoStarRatingSnippet:n,placeholder:r})=>{const{okendoDataLoaded:o}=l(),a={"data-oke-star-rating":"","data-oke-reviews-product-id":w(t)};return o?e.createElement(v,{dataAttributes:a,metafieldContent:n?.value}):r||e.createElement("div",{style:{height:"20px"}})},S='#graphql\n\tfragment OkendoStarRatingSnippet on Product {\n\t\tokendoStarRatingSnippet: metafield(\n\t\t\tnamespace: "okendo"\n\t\t\tkey: "StarRatingSnippet"\n\t\t) {\n\t\t\tvalue\n\t\t}\n\t}\n',h='#graphql\n\tfragment OkendoReviewsSnippet on Product {\n\t\tokendoReviewsSnippet: metafield(\n\t\t\tnamespace: "okendo"\n\t\t\tkey: "ReviewsWidgetSnippet"\n\t\t) {\n\t\t\tvalue\n\t\t}\n\t}\n';export{h as OKENDO_PRODUCT_REVIEWS_FRAGMENT,S as OKENDO_PRODUCT_STAR_RATING_FRAGMENT,m as OkendoProvider,y as OkendoReviews,f as OkendoStarRating,p as getOkendoProviderData};
@@ -1,7 +1,8 @@
1
- import type { Storefront } from "@shopify/hydrogen";
2
- import { type FC } from "react";
1
+ import type { WidgetPlus } from '@okendo/reviews-common';
2
+ import type { Storefront } from '@shopify/hydrogen';
3
+ import React, { type PropsWithChildren } from 'react';
3
4
  interface OkendoProviderData {
4
- reviewsHeaderConfig: Record<string, unknown>;
5
+ reviewsHeaderConfig: WidgetPlus.ReviewsHeaderConfig;
5
6
  cssVariables: string;
6
7
  customCss?: string;
7
8
  initScriptContents: string;
@@ -15,14 +16,7 @@ export declare const getOkendoProviderData: ({ context, subscriberId, apiDomain,
15
16
  subscriberId: string;
16
17
  apiDomain?: string;
17
18
  cdnDomain?: string;
18
- }) => Promise<{
19
- reviewsHeaderConfig: import("@okendo/reviews-common").WidgetPlus.ReviewsHeaderConfig;
20
- cssVariables: string;
21
- customCss: string | undefined;
22
- initScriptContents: string;
23
- preRenderStyleTags: string;
24
- starSymbols: string;
25
- } | null>;
19
+ }) => Promise<OkendoProviderData | null>;
26
20
  interface ReviewProduct {
27
21
  productHandle?: string;
28
22
  productId: string;
@@ -34,10 +28,17 @@ interface OkendoProviderProps {
34
28
  * this is required if Content Security Policy is set up on your store.
35
29
  */
36
30
  nonce?: string;
37
- /** The data returned by `getOkendoProviderData` */
38
- okendoProviderData: Partial<OkendoProviderData> | null;
31
+ /**
32
+ * Either:
33
+ * - the data returned by the promise returned by `getOkendoProviderData`, using this will allow the
34
+ * Okendo widgets to show instantly on page load, but it will slightly increase the page load time;
35
+ * - the promise returned by `getOkendoProviderData`, using this will make the Okendo widgets
36
+ * show slightly after page load, but it have the advantage of not increasing the page load time.
37
+ */
38
+ okendoProviderData: Partial<OkendoProviderData> | null | Promise<Partial<OkendoProviderData> | null>;
39
39
  /** An optional product URL formatter */
40
40
  productUrlFormatter?: (product: ReviewProduct) => string;
41
+ cdnDomain?: string;
41
42
  }
42
- export declare const OkendoProvider: FC<OkendoProviderProps>;
43
+ export declare const OkendoProvider: ({ nonce, okendoProviderData, productUrlFormatter, cdnDomain, children, }: OkendoProviderProps & PropsWithChildren) => React.JSX.Element;
43
44
  export {};
@@ -1,11 +1,16 @@
1
- import { type FC } from "react";
2
- import { type MetafieldValue } from "../../internal/types";
1
+ import React from 'react';
2
+ import { type MetafieldValue } from '../../internal/types';
3
3
  export interface WithOkendoReviewsSnippet {
4
4
  okendoReviewsSnippet?: MetafieldValue | null;
5
5
  }
6
6
  interface OkendoReviewsProps {
7
7
  productId: string;
8
8
  okendoReviewsSnippet?: MetafieldValue | null;
9
+ /**
10
+ * Only used if the promise returned by `getOkendoProviderData` is given to the OkendoProvider
11
+ * without being awaited; in this case, the placeholder here will be shown until the widget is ready
12
+ */
13
+ placeholder?: JSX.Element;
9
14
  }
10
- export declare const OkendoReviews: FC<OkendoReviewsProps>;
15
+ export declare const OkendoReviews: ({ productId, okendoReviewsSnippet, placeholder }: OkendoReviewsProps) => React.JSX.Element;
11
16
  export {};
@@ -1,11 +1,16 @@
1
- import { type FC } from "react";
2
- import { type MetafieldValue } from "../../internal/types";
1
+ import React, { type ReactNode } from 'react';
2
+ import { type MetafieldValue } from '../../internal/types';
3
3
  export interface WithOkendoStarRatingSnippet {
4
4
  okendoStarRatingSnippet?: MetafieldValue | null;
5
5
  }
6
6
  interface OkendoStarRatingProps {
7
7
  productId: string;
8
8
  okendoStarRatingSnippet?: MetafieldValue | null;
9
+ /**
10
+ * Only used if the promise returned by `getOkendoProviderData` is given to the OkendoProvider
11
+ * without being awaited; in this case, the placeholder here will be shown until the widget is ready
12
+ */
13
+ placeholder?: ReactNode;
9
14
  }
10
- export declare const OkendoStarRating: FC<OkendoStarRatingProps>;
15
+ export declare const OkendoStarRating: ({ productId, okendoStarRatingSnippet, placeholder }: OkendoStarRatingProps) => string | number | true | Iterable<React.ReactNode> | React.JSX.Element;
11
16
  export {};
@@ -0,0 +1,10 @@
1
+ import React, { type Dispatch, type PropsWithChildren, type SetStateAction } from 'react';
2
+ export declare const OkendoContext: React.Context<{
3
+ okendoDataLoaded: boolean;
4
+ setOkendoDataLoaded: Dispatch<SetStateAction<boolean>>;
5
+ } | undefined>;
6
+ export declare const OkendoContextProvider: ({ children }: PropsWithChildren) => React.JSX.Element;
7
+ export declare const useOkendoContext: () => {
8
+ okendoDataLoaded: boolean;
9
+ setOkendoDataLoaded: Dispatch<SetStateAction<boolean>>;
10
+ };
@@ -1,10 +1,10 @@
1
- import type { ReviewsWidgetPlus } from "@okendo/reviews-widget-plus/dist-utils/ReviewsWidgetPlus";
2
- import { type FC } from "react";
1
+ import type { ReviewsWidgetPlus } from '@okendo/reviews-widget-plus/dist-utils/ReviewsWidgetPlus';
2
+ import React from 'react';
3
3
  export interface OkendoWidgetProps {
4
4
  dataAttributes: {
5
- "data-oke-widget"?: string;
6
- "data-oke-star-rating"?: string;
7
- "data-oke-reviews-product-id": string | undefined;
5
+ 'data-oke-widget'?: string;
6
+ 'data-oke-star-rating'?: string;
7
+ 'data-oke-reviews-product-id': string | undefined;
8
8
  };
9
9
  metafieldContent?: string;
10
10
  }
@@ -13,4 +13,4 @@ declare global {
13
13
  okeWidgetApi: ReviewsWidgetPlus.WidgetWindowApi;
14
14
  }
15
15
  }
16
- export declare const OkendoWidget: FC<OkendoWidgetProps>;
16
+ export declare const OkendoWidget: ({ dataAttributes, metafieldContent }: OkendoWidgetProps) => React.JSX.Element;
package/dist/index.d.ts CHANGED
@@ -1,9 +1,9 @@
1
- import * as _okendo_reviews_common from '@okendo/reviews-common';
1
+ import { WidgetPlus } from '@okendo/reviews-common';
2
2
  import { Storefront } from '@shopify/hydrogen';
3
- import { FC } from 'react';
3
+ import React, { PropsWithChildren, ReactNode } from 'react';
4
4
 
5
5
  interface OkendoProviderData {
6
- reviewsHeaderConfig: Record<string, unknown>;
6
+ reviewsHeaderConfig: WidgetPlus.ReviewsHeaderConfig;
7
7
  cssVariables: string;
8
8
  customCss?: string;
9
9
  initScriptContents: string;
@@ -17,14 +17,7 @@ declare const getOkendoProviderData: ({ context, subscriberId, apiDomain, cdnDom
17
17
  subscriberId: string;
18
18
  apiDomain?: string;
19
19
  cdnDomain?: string;
20
- }) => Promise<{
21
- reviewsHeaderConfig: _okendo_reviews_common.WidgetPlus.ReviewsHeaderConfig;
22
- cssVariables: string;
23
- customCss: string | undefined;
24
- initScriptContents: string;
25
- preRenderStyleTags: string;
26
- starSymbols: string;
27
- } | null>;
20
+ }) => Promise<OkendoProviderData | null>;
28
21
  interface ReviewProduct {
29
22
  productHandle?: string;
30
23
  productId: string;
@@ -36,12 +29,19 @@ interface OkendoProviderProps {
36
29
  * this is required if Content Security Policy is set up on your store.
37
30
  */
38
31
  nonce?: string;
39
- /** The data returned by `getOkendoProviderData` */
40
- okendoProviderData: Partial<OkendoProviderData> | null;
32
+ /**
33
+ * Either:
34
+ * - the data returned by the promise returned by `getOkendoProviderData`, using this will allow the
35
+ * Okendo widgets to show instantly on page load, but it will slightly increase the page load time;
36
+ * - the promise returned by `getOkendoProviderData`, using this will make the Okendo widgets
37
+ * show slightly after page load, but it have the advantage of not increasing the page load time.
38
+ */
39
+ okendoProviderData: Partial<OkendoProviderData> | null | Promise<Partial<OkendoProviderData> | null>;
41
40
  /** An optional product URL formatter */
42
41
  productUrlFormatter?: (product: ReviewProduct) => string;
42
+ cdnDomain?: string;
43
43
  }
44
- declare const OkendoProvider: FC<OkendoProviderProps>;
44
+ declare const OkendoProvider: ({ nonce, okendoProviderData, productUrlFormatter, cdnDomain, children, }: OkendoProviderProps & PropsWithChildren) => React.JSX.Element;
45
45
 
46
46
  interface MetafieldValue {
47
47
  value: string;
@@ -53,8 +53,13 @@ interface WithOkendoReviewsSnippet {
53
53
  interface OkendoReviewsProps {
54
54
  productId: string;
55
55
  okendoReviewsSnippet?: MetafieldValue | null;
56
+ /**
57
+ * Only used if the promise returned by `getOkendoProviderData` is given to the OkendoProvider
58
+ * without being awaited; in this case, the placeholder here will be shown until the widget is ready
59
+ */
60
+ placeholder?: JSX.Element;
56
61
  }
57
- declare const OkendoReviews: FC<OkendoReviewsProps>;
62
+ declare const OkendoReviews: ({ productId, okendoReviewsSnippet, placeholder }: OkendoReviewsProps) => React.JSX.Element;
58
63
 
59
64
  interface WithOkendoStarRatingSnippet {
60
65
  okendoStarRatingSnippet?: MetafieldValue | null;
@@ -62,8 +67,13 @@ interface WithOkendoStarRatingSnippet {
62
67
  interface OkendoStarRatingProps {
63
68
  productId: string;
64
69
  okendoStarRatingSnippet?: MetafieldValue | null;
70
+ /**
71
+ * Only used if the promise returned by `getOkendoProviderData` is given to the OkendoProvider
72
+ * without being awaited; in this case, the placeholder here will be shown until the widget is ready
73
+ */
74
+ placeholder?: ReactNode;
65
75
  }
66
- declare const OkendoStarRating: FC<OkendoStarRatingProps>;
76
+ declare const OkendoStarRating: ({ productId, okendoStarRatingSnippet, placeholder }: OkendoStarRatingProps) => string | number | true | Iterable<React.ReactNode> | React.JSX.Element;
67
77
 
68
78
  declare const OKENDO_PRODUCT_STAR_RATING_FRAGMENT: "#graphql\n\tfragment OkendoStarRatingSnippet on Product {\n\t\tokendoStarRatingSnippet: metafield(\n\t\t\tnamespace: \"okendo\"\n\t\t\tkey: \"StarRatingSnippet\"\n\t\t) {\n\t\t\tvalue\n\t\t}\n\t}\n";
69
79
  declare const OKENDO_PRODUCT_REVIEWS_FRAGMENT: "#graphql\n\tfragment OkendoReviewsSnippet on Product {\n\t\tokendoReviewsSnippet: metafield(\n\t\t\tnamespace: \"okendo\"\n\t\t\tkey: \"ReviewsWidgetSnippet\"\n\t\t) {\n\t\t\tvalue\n\t\t}\n\t}\n";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@okendo/shopify-hydrogen",
3
- "version": "2.1.5",
3
+ "version": "2.2.0",
4
4
  "description": "Okendo React components for Shopify Hydrogen 2 (Remix)",
5
5
  "author": "Okendo",
6
6
  "license": "SEE LICENSE IN LICENSE.txt",
@@ -40,8 +40,9 @@
40
40
  "typescript": "^5.3.3"
41
41
  },
42
42
  "peerDependencies": {
43
+ "@remix-run/react": "*",
43
44
  "@shopify/hydrogen": "*",
44
45
  "@shopify/remix-oxygen": "*",
45
- "react": "^18.2.0"
46
+ "react": "*"
46
47
  }
47
48
  }