@okendo/shopify-hydrogen 2.1.1 → 2.1.3

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,556 +1,635 @@
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).
2
-
3
- # Okendo Hydrogen 2 (Remix) React Components
4
-
5
- This package brings [Okendo's review widgets](https://www.okendo.io/blog/widget-plus/) to a Shopify Hydrogen store.
6
-
7
- ## Requirements
8
-
9
- - A Shopify store with the [**Okendo: Product Reviews & UCG**](https://apps.shopify.com/okendo-reviews) app installed and configured.
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
- - A current Okendo subscription.
12
- - A [Storefront access token](https://github.com/okendo/okendo-shopify-hydrogen-demo/wiki/Configure-Shopify-Storefront-API).
13
- - A [Shopify Hydrogen](https://hydrogen.shopify.dev/) app.
14
-
15
- ## Demo Store
16
-
17
- 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
-
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.
20
-
21
- ## How it works
22
-
23
- This package provides:
24
-
25
- - one function: `getOkendoProviderData`
26
- - three React components: `OkendoProvider`, `OkendoStarRating`, and `OkendoReviews`
27
-
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`.
29
-
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.
31
-
32
- ## Expose Shopify Metafields <a id="expose-shopify-metafields" name="expose-shopify-metafields"></a>
33
-
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.
35
-
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).
37
-
38
- <details>
39
- <summary>For technical users, follow this method to expose metafields via the storefront API</summary>
40
-
41
- ### Exposing Metafields via GraphQL
42
-
43
- #### Using Curl
44
-
45
- You can also expose the required Okendo Shopify metafields by using GraphQL with curl.
46
-
47
- 1. Open a new terminal or PowerShell window.
48
- 2. Run the following command to expose the `WidgetPreRenderStyleTags` shop metafield:
49
-
50
- ```bash
51
- curl -X POST \
52
- https://{shop}.myshopify.com/admin/api/2022-04/graphql.json \
53
- -H 'Content-Type: application/graphql' \
54
- -H 'X-Shopify-Access-Token: {access_token}' \
55
- -d '
56
- mutation {
57
- metafieldStorefrontVisibilityCreate(
58
- input: {
59
- namespace: "okendo"
60
- key: "WidgetPreRenderStyleTags"
61
- ownerType: SHOP
62
- }
63
- ) {
64
- metafieldStorefrontVisibility {
65
- id
66
- }
67
- userErrors {
68
- field
69
- message
70
- }
71
- }
72
- }
73
- '
74
- ```
75
-
76
- 3. Run the following command to expose the `WidgetPreRenderBodyStyleTags` shop metafield:
77
-
78
- ```bash
79
- curl -X POST \
80
- https://{shop}.myshopify.com/admin/api/2022-04/graphql.json \
81
- -H 'Content-Type: application/graphql' \
82
- -H 'X-Shopify-Access-Token: {access_token}' \
83
- -d '
84
- mutation {
85
- metafieldStorefrontVisibilityCreate(
86
- input: {
87
- namespace: "okendo"
88
- key: "WidgetPreRenderBodyStyleTags"
89
- ownerType: SHOP
90
- }
91
- ) {
92
- metafieldStorefrontVisibility {
93
- id
94
- }
95
- userErrors {
96
- field
97
- message
98
- }
99
- }
100
- }
101
- '
102
- ```
103
-
104
- 4. Run the following command to expose the `ReviewsWidgetSnippet` product metafield:
105
-
106
- ```bash
107
- curl -X POST \
108
- https://{shop}.myshopify.com/admin/api/2022-04/graphql.json \
109
- -H 'Content-Type: application/graphql' \
110
- -H 'X-Shopify-Access-Token: {access_token}' \
111
- -d '
112
- mutation {
113
- metafieldStorefrontVisibilityCreate(
114
- input: {
115
- namespace: "okendo"
116
- key: "ReviewsWidgetSnippet"
117
- ownerType: PRODUCT
118
- }
119
- ) {
120
- metafieldStorefrontVisibility {
121
- id
122
- }
123
- userErrors {
124
- field
125
- message
126
- }
127
- }
128
- }
129
- '
130
- ```
131
-
132
- 5. Run the following command to expose the `StarRatingSnippet` the product metafield:
133
-
134
- ```bash
135
- curl -X POST \
136
- https://{shop}.myshopify.com/admin/api/2022-04/graphql.json \
137
- -H 'Content-Type: application/graphql' \
138
- -H 'X-Shopify-Access-Token: {access_token}' \
139
- -d '
140
- mutation {
141
- metafieldStorefrontVisibilityCreate(
142
- input: {
143
- namespace: "okendo"
144
- key: "StarRatingSnippet"
145
- ownerType: PRODUCT
146
- }
147
- ) {
148
- metafieldStorefrontVisibility {
149
- id
150
- }
151
- userErrors {
152
- field
153
- message
154
- }
155
- }
156
- }
157
- '
158
- ```
159
-
160
- ### Using GraphQL IDE
161
-
162
- 1. Open your GraphQL IDE (such as Postman) and make a `POST` request with the following details:
163
-
164
- - **URL:** https://{shop}.myshopify.com/admin/api/2022-04/graphql.json
165
- - **Headers:** - X-Shopify-Access-Token: {access_token} - Content-Type: application/json
166
-
167
- 2. Execute the following request to expose the `WidgetPreRenderStyleTags` shop metafield:
168
-
169
- ```graphql
170
- mutation {
171
- metafieldStorefrontVisibilityCreate(
172
- input: {
173
- namespace: "okendo"
174
- key: "WidgetPreRenderStyleTags"
175
- ownerType: SHOP
176
- }
177
- ) {
178
- metafieldStorefrontVisibility {
179
- id
180
- }
181
- userErrors {
182
- field
183
- message
184
- }
185
- }
186
- }
187
- ```
188
-
189
- 3. Execute the following request to expose the `WidgetPreRenderBodyStyleTags` shop metafield:
190
-
191
- ```graphql
192
- mutation {
193
- metafieldStorefrontVisibilityCreate(
194
- input: {
195
- namespace: "okendo"
196
- key: "WidgetPreRenderBodyStyleTags"
197
- ownerType: SHOP
198
- }
199
- ) {
200
- metafieldStorefrontVisibility {
201
- id
202
- }
203
- userErrors {
204
- field
205
- message
206
- }
207
- }
208
- }
209
- ```
210
-
211
- 4. Execute the following request to expose the `ReviewsWidgetSnippet` product metafield:
212
-
213
- ```graphql
214
- mutation {
215
- metafieldStorefrontVisibilityCreate(
216
- input: {
217
- namespace: "okendo"
218
- key: "ReviewsWidgetSnippet"
219
- ownerType: PRODUCT
220
- }
221
- ) {
222
- metafieldStorefrontVisibility {
223
- id
224
- }
225
- userErrors {
226
- field
227
- message
228
- }
229
- }
230
- }
231
- ```
232
-
233
- 5. Execute the following request to expose the `StarRatingSnippet` the product metafield:
234
-
235
- ```graphql
236
- mutation {
237
- metafieldStorefrontVisibilityCreate(
238
- input: { namespace: "okendo", key: "StarRatingSnippet", ownerType: PRODUCT }
239
- ) {
240
- metafieldStorefrontVisibility {
241
- id
242
- }
243
- userErrors {
244
- field
245
- message
246
- }
247
- }
248
- }
249
- ```
250
-
251
- **References**
252
-
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
- </details>
256
-
257
- ## Installation
258
-
259
- > The code examples provided in this section are based on the [demo store provided by Shopify](https://github.com/Shopify/hydrogen/tree/main/templates/demo-store). You will find the following steps already done in [our demo store](https://github.com/okendo/okendo-shopify-hydrogen-demo).
260
-
261
- Run:
262
-
263
- ```bash
264
- npm i @okendo/shopify-hydrogen
265
- ```
266
-
267
- ### `app/root.tsx`
268
-
269
- Open `app/root.tsx` and add the following import:
270
-
271
- ```ts
272
- import {
273
- OkendoProvider,
274
- getOkendoProviderData,
275
- } from "@okendo/shopify-hydrogen";
276
- ```
277
-
278
- Locate the `loader` function, append the `okendoProviderData` property to the returned data, and set `subscriberId` to your Okendo subscriber ID:
279
-
280
- ```ts
281
- return defer({
282
- ...
283
- okendoProviderData: await getOkendoProviderData({
284
- context,
285
- subscriberId: "<your-okendo-subscriber-id>",
286
- }),
287
- });
288
- ```
289
-
290
- Locate the `App` function, add the `meta` tag `oke:subscriber_id` to `head`, and place your Okendo subscriber ID in its content:
291
-
292
- ```ts
293
- <head>
294
- <meta charSet="utf-8" />
295
- <meta name="viewport" content="width=device-width,initial-scale=1" />
296
- <meta name="oke:subscriber_id" content="<your-okendo-subscriber-id>" />
297
- ...
298
- ```
299
-
300
- 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):
301
-
302
- ```tsx
303
- ...
304
- <body>
305
- <OkendoProvider
306
- nonce={nonce}
307
- okendoProviderData={data.okendoProviderData}
308
- />
309
- ...
310
- </body>
311
- ...
312
- ```
313
-
314
- ### `app/entry.server.tsx`
315
-
316
- > This is only necessary if Content Security Policy is active in your project.
317
-
318
- Locate the call to `createContentSecurityPolicy`, and add:
319
-
320
- - `https://d3hw6dc1ow8pp2.cloudfront.net`, `https://d3g5hqndtiniji.cloudfront.net`, and `data:` to `defaultSrc`
321
- - `https://d3hw6dc1ow8pp2.cloudfront.net` to `styleSrc`
322
- - `https://api.okendo.io` to `connectSrc`
323
-
324
- Note that it's necessary to to add the default values (`'self'`, etc.) when [extending the CSP](https://shopify.dev/docs/custom-storefronts/hydrogen/content-security-policy). The call to `createContentSecurityPolicy` should now look like the following:
325
-
326
- ```ts
327
- const { nonce, header, NonceProvider } = createContentSecurityPolicy({
328
- defaultSrc: [
329
- "'self'",
330
- "localhost:*",
331
- "https://cdn.shopify.com",
332
- "https://d3hw6dc1ow8pp2.cloudfront.net",
333
- "https://d3g5hqndtiniji.cloudfront.net",
334
- "data:",
335
- ],
336
- styleSrc: [
337
- "'self'",
338
- "'unsafe-inline'",
339
- "https://cdn.shopify.com",
340
- "localhost:*",
341
- "https://d3hw6dc1ow8pp2.cloudfront.net",
342
- ],
343
- connectSrc: [
344
- "'self'",
345
- "https://monorail-edge.shopifysvc.com",
346
- "localhost:*",
347
- "ws://localhost:*",
348
- "ws://127.0.0.1:*",
349
- "https://api.okendo.io",
350
- ],
351
- });
352
- ```
353
-
354
- ### `app/routes/_index.tsx`
355
-
356
- Add the following imports:
357
-
358
- ```ts
359
- import {
360
- OKENDO_PRODUCT_STAR_RATING_FRAGMENT,
361
- OkendoStarRating,
362
- WithOkendoStarRatingSnippet,
363
- } from "@okendo/shopify-hydrogen";
364
- ```
365
-
366
- Locate the `RECOMMENDED_PRODUCTS_QUERY` GraphQL query, and append `${OKENDO_PRODUCT_STAR_RATING_FRAGMENT}` `...OkendoStarRatingSnippet` to it:
367
-
368
- ```ts
369
- const RECOMMENDED_PRODUCTS_QUERY = `#graphql
370
- ${OKENDO_PRODUCT_STAR_RATING_FRAGMENT}
371
- fragment RecommendedProduct on Product {
372
- id
373
- title
374
- handle
375
- priceRange {
376
- minVariantPrice {
377
- amount
378
- currencyCode
379
- }
380
- }
381
- images(first: 1) {
382
- nodes {
383
- id
384
- url
385
- altText
386
- width
387
- height
388
- }
389
- }
390
- ...OkendoStarRatingSnippet
391
- }
392
- query RecommendedProducts ($country: CountryCode, $language: LanguageCode)
393
- @inContext(country: $country, language: $language) {
394
- products(first: 4, sortKey: UPDATED_AT, reverse: true) {
395
- nodes {
396
- ...RecommendedProduct
397
- }
398
- }
399
- }
400
- ` as const;
401
- ```
402
-
403
- Tweak the type of the `products` prop of `RecommendedProducts`:
404
-
405
- ```ts
406
- products: Promise<{
407
- products: {
408
- nodes: (RecommendedProductsQuery['products']['nodes'][0] &
409
- WithOkendoStarRatingSnippet)[];
410
- };
411
- };
412
- ```
413
-
414
- Add `OkendoStarRating` to `RecommendedProducts`:
415
-
416
- ```tsx
417
- <OkendoStarRating
418
- productId={product.id}
419
- okendoStarRatingSnippet={product.okendoStarRatingSnippet}
420
- />
421
- ```
422
-
423
- For instance, we can add it below the product title, like this:
424
-
425
- ```tsx
426
- <Image
427
- data={product.images.nodes[0]}
428
- aspectRatio="1/1"
429
- sizes="(min-width: 45em) 20vw, 50vw"
430
- />
431
- <h4>{product.title}</h4>
432
- <OkendoStarRating
433
- productId={product.id}
434
- okendoStarRatingSnippet={product.okendoStarRatingSnippet}
435
- />
436
- <small>
437
- <Money data={product.priceRange.minVariantPrice} />
438
- </small>
439
- ```
440
-
441
- We now have the Okendo Star Rating widget visible on our page:
442
-
443
- ![Okendo's Star Rating widget](./okendo-star-rating-widget.png)
444
-
445
- ### `app/routes/products.$handle.tsx`
446
-
447
- Add the following imports:
448
-
449
- ```ts
450
- import {
451
- OKENDO_PRODUCT_REVIEWS_FRAGMENT,
452
- OKENDO_PRODUCT_STAR_RATING_FRAGMENT,
453
- OkendoReviews,
454
- OkendoStarRating,
455
- WithOkendoReviewsSnippet,
456
- WithOkendoStarRatingSnippet,
457
- } from "@okendo/shopify-hydrogen";
458
- ```
459
-
460
- Locate the `PRODUCT_FRAGMENT` GraphQL query, and append `${OKENDO_PRODUCT_STAR_RATING_FRAGMENT}`, `${OKENDO_PRODUCT_REVIEWS_FRAGMENT}`, `...OkendoStarRatingSnippet`, and `...OkendoReviewsSnippet` to it:
461
-
462
- ```ts
463
- const PRODUCT_FRAGMENT = `#graphql
464
- ${OKENDO_PRODUCT_STAR_RATING_FRAGMENT}
465
- ${OKENDO_PRODUCT_REVIEWS_FRAGMENT}
466
- fragment Product on Product {
467
- id
468
- title
469
- vendor
470
- handle
471
- descriptionHtml
472
- description
473
- options {
474
- name
475
- values
476
- }
477
- selectedVariant: variantBySelectedOptions(selectedOptions: $selectedOptions) {
478
- ...ProductVariant
479
- }
480
- variants(first: 1) {
481
- nodes {
482
- ...ProductVariant
483
- }
484
- }
485
- seo {
486
- description
487
- title
488
- }
489
- ...OkendoStarRatingSnippet
490
- ...OkendoReviewsSnippet
491
- }
492
- ${PRODUCT_VARIANT_FRAGMENT}
493
- ` as const;
494
- ```
495
-
496
- Add `OkendoReviews` to `Product`:
497
-
498
- ```tsx
499
- <OkendoReviews
500
- productId={product.id}
501
- okendoReviewsSnippet={product.okendoReviewsSnippet}
502
- />
503
- ```
504
-
505
- For instance, we can add it below the product section, like this:
506
-
507
- ```tsx
508
- <>
509
- <div className="product">
510
- <ProductImage image={selectedVariant?.image} />
511
- <ProductMain
512
- selectedVariant={selectedVariant}
513
- product={product}
514
- variants={variants}
515
- />
516
- </div>
517
-
518
- <OkendoReviews
519
- productId={product.id}
520
- okendoReviewsSnippet={product.okendoReviewsSnippet}
521
- />
522
- </>
523
- ```
524
-
525
- Tweak the type of the `product` prop of `ProductMain`:
526
-
527
- ```ts
528
- product: ProductFragment &
529
- WithOkendoStarRatingSnippet &
530
- WithOkendoReviewsSnippet;
531
- ```
532
-
533
- Add `OkendoStarRating` to `ProductMain`:
534
-
535
- ```tsx
536
- <OkendoStarRating
537
- productId={product.id}
538
- okendoStarRatingSnippet={product.okendoStarRatingSnippet}
539
- />
540
- ```
541
-
542
- For instance, we can add it below the product title, like this:
543
-
544
- ```tsx
545
- <div className="product-main">
546
- <h1>{title}</h1>
547
- <OkendoStarRating
548
- productId={product.id}
549
- okendoStarRatingSnippet={product.okendoStarRatingSnippet}
550
- />
551
- <ProductPrice selectedVariant={selectedVariant} />
552
- ```
553
-
554
- We now have the Okendo Star Rating and Reviews widgets visible on our product page:
555
-
556
- ![Okendo's Star Rating and Reviews widgets](./okendo-star-rating-and-reviews-widgets.png)
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).
2
+
3
+ # Okendo Hydrogen 2 (Remix) React Components
4
+
5
+ This package brings [Okendo's review widgets](https://www.okendo.io/blog/widget-plus/) to a Shopify Hydrogen store.
6
+
7
+ ## Requirements
8
+
9
+ - A Shopify store with the [**Okendo: Product Reviews & UCG**](https://apps.shopify.com/okendo-reviews) app installed and configured.
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
+ - A current Okendo subscription.
12
+ - A [Storefront access token](https://github.com/okendo/okendo-shopify-hydrogen-demo/wiki/Configure-Shopify-Storefront-API).
13
+ - A [Shopify Hydrogen](https://hydrogen.shopify.dev/) app.
14
+
15
+ ## Demo Store
16
+
17
+ 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
+
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.
20
+
21
+ ## How it works
22
+
23
+ This package provides:
24
+
25
+ - one function: `getOkendoProviderData`
26
+ - three React components: `OkendoProvider`, `OkendoStarRating`, and `OkendoReviews`
27
+
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`.
29
+
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.
31
+
32
+ ## Expose Shopify Metafields <a id="expose-shopify-metafields" name="expose-shopify-metafields"></a>
33
+
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.
35
+
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).
37
+
38
+ <details>
39
+ <summary>For technical users, follow this method to expose metafields via the storefront API</summary>
40
+
41
+ ### Exposing Metafields via GraphQL
42
+
43
+ #### Using Curl
44
+
45
+ You can also expose the required Okendo Shopify metafields by using GraphQL with curl.
46
+
47
+ 1. Open a new terminal or PowerShell window.
48
+ 2. Run the following command to expose the `WidgetPreRenderStyleTags` shop metafield:
49
+
50
+ ```bash
51
+ curl -X POST \
52
+ https://{shop}.myshopify.com/admin/api/2022-04/graphql.json \
53
+ -H 'Content-Type: application/graphql' \
54
+ -H 'X-Shopify-Access-Token: {access_token}' \
55
+ -d '
56
+ mutation {
57
+ metafieldStorefrontVisibilityCreate(
58
+ input: {
59
+ namespace: "okendo"
60
+ key: "WidgetPreRenderStyleTags"
61
+ ownerType: SHOP
62
+ }
63
+ ) {
64
+ metafieldStorefrontVisibility {
65
+ id
66
+ }
67
+ userErrors {
68
+ field
69
+ message
70
+ }
71
+ }
72
+ }
73
+ '
74
+ ```
75
+
76
+ 3. Run the following command to expose the `WidgetPreRenderBodyStyleTags` shop metafield:
77
+
78
+ ```bash
79
+ curl -X POST \
80
+ https://{shop}.myshopify.com/admin/api/2022-04/graphql.json \
81
+ -H 'Content-Type: application/graphql' \
82
+ -H 'X-Shopify-Access-Token: {access_token}' \
83
+ -d '
84
+ mutation {
85
+ metafieldStorefrontVisibilityCreate(
86
+ input: {
87
+ namespace: "okendo"
88
+ key: "WidgetPreRenderBodyStyleTags"
89
+ ownerType: SHOP
90
+ }
91
+ ) {
92
+ metafieldStorefrontVisibility {
93
+ id
94
+ }
95
+ userErrors {
96
+ field
97
+ message
98
+ }
99
+ }
100
+ }
101
+ '
102
+ ```
103
+
104
+ 4. Run the following command to expose the `ReviewsWidgetSnippet` product metafield:
105
+
106
+ ```bash
107
+ curl -X POST \
108
+ https://{shop}.myshopify.com/admin/api/2022-04/graphql.json \
109
+ -H 'Content-Type: application/graphql' \
110
+ -H 'X-Shopify-Access-Token: {access_token}' \
111
+ -d '
112
+ mutation {
113
+ metafieldStorefrontVisibilityCreate(
114
+ input: {
115
+ namespace: "okendo"
116
+ key: "ReviewsWidgetSnippet"
117
+ ownerType: PRODUCT
118
+ }
119
+ ) {
120
+ metafieldStorefrontVisibility {
121
+ id
122
+ }
123
+ userErrors {
124
+ field
125
+ message
126
+ }
127
+ }
128
+ }
129
+ '
130
+ ```
131
+
132
+ 5. Run the following command to expose the `StarRatingSnippet` the product metafield:
133
+
134
+ ```bash
135
+ curl -X POST \
136
+ https://{shop}.myshopify.com/admin/api/2022-04/graphql.json \
137
+ -H 'Content-Type: application/graphql' \
138
+ -H 'X-Shopify-Access-Token: {access_token}' \
139
+ -d '
140
+ mutation {
141
+ metafieldStorefrontVisibilityCreate(
142
+ input: {
143
+ namespace: "okendo"
144
+ key: "StarRatingSnippet"
145
+ ownerType: PRODUCT
146
+ }
147
+ ) {
148
+ metafieldStorefrontVisibility {
149
+ id
150
+ }
151
+ userErrors {
152
+ field
153
+ message
154
+ }
155
+ }
156
+ }
157
+ '
158
+ ```
159
+
160
+ ### Using GraphQL IDE
161
+
162
+ 1. Open your GraphQL IDE (such as Postman) and make a `POST` request with the following details:
163
+
164
+ - **URL:** https://{shop}.myshopify.com/admin/api/2022-04/graphql.json
165
+ - **Headers:** - X-Shopify-Access-Token: {access_token} - Content-Type: application/json
166
+
167
+ 2. Execute the following request to expose the `WidgetPreRenderStyleTags` shop metafield:
168
+
169
+ ```graphql
170
+ mutation {
171
+ metafieldStorefrontVisibilityCreate(
172
+ input: {
173
+ namespace: "okendo"
174
+ key: "WidgetPreRenderStyleTags"
175
+ ownerType: SHOP
176
+ }
177
+ ) {
178
+ metafieldStorefrontVisibility {
179
+ id
180
+ }
181
+ userErrors {
182
+ field
183
+ message
184
+ }
185
+ }
186
+ }
187
+ ```
188
+
189
+ 3. Execute the following request to expose the `WidgetPreRenderBodyStyleTags` shop metafield:
190
+
191
+ ```graphql
192
+ mutation {
193
+ metafieldStorefrontVisibilityCreate(
194
+ input: {
195
+ namespace: "okendo"
196
+ key: "WidgetPreRenderBodyStyleTags"
197
+ ownerType: SHOP
198
+ }
199
+ ) {
200
+ metafieldStorefrontVisibility {
201
+ id
202
+ }
203
+ userErrors {
204
+ field
205
+ message
206
+ }
207
+ }
208
+ }
209
+ ```
210
+
211
+ 4. Execute the following request to expose the `ReviewsWidgetSnippet` product metafield:
212
+
213
+ ```graphql
214
+ mutation {
215
+ metafieldStorefrontVisibilityCreate(
216
+ input: {
217
+ namespace: "okendo"
218
+ key: "ReviewsWidgetSnippet"
219
+ ownerType: PRODUCT
220
+ }
221
+ ) {
222
+ metafieldStorefrontVisibility {
223
+ id
224
+ }
225
+ userErrors {
226
+ field
227
+ message
228
+ }
229
+ }
230
+ }
231
+ ```
232
+
233
+ 5. Execute the following request to expose the `StarRatingSnippet` the product metafield:
234
+
235
+ ```graphql
236
+ mutation {
237
+ metafieldStorefrontVisibilityCreate(
238
+ input: { namespace: "okendo", key: "StarRatingSnippet", ownerType: PRODUCT }
239
+ ) {
240
+ metafieldStorefrontVisibility {
241
+ id
242
+ }
243
+ userErrors {
244
+ field
245
+ message
246
+ }
247
+ }
248
+ }
249
+ ```
250
+
251
+ **References**
252
+
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
+ </details>
256
+
257
+ ## Installation
258
+
259
+ > 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
+
261
+ Run:
262
+
263
+ ```bash
264
+ npm i @okendo/shopify-hydrogen
265
+ ```
266
+
267
+ ### `app/root.tsx`
268
+
269
+ Open `app/root.tsx` and add the following import:
270
+
271
+ ```ts
272
+ import {
273
+ OkendoProvider,
274
+ getOkendoProviderData,
275
+ } from "@okendo/shopify-hydrogen";
276
+ ```
277
+
278
+ Locate the `loader` function, append `okendoProviderData` to the returned data as shown below, and set `subscriberId` to your Okendo subscriber ID:
279
+
280
+ ```ts
281
+ return defer(
282
+ {
283
+ ...
284
+ okendoProviderData: await getOkendoProviderData({
285
+ context,
286
+ subscriberId: "<your-okendo-subscriber-id>",
287
+ }),
288
+ }
289
+ );
290
+ ```
291
+
292
+ Locate the `App` function, add the `meta` tag `oke:subscriber_id` to `head`, and place your Okendo subscriber ID in its content:
293
+
294
+ ```ts
295
+ <head>
296
+ <meta charSet="utf-8" />
297
+ <meta name="viewport" content="width=device-width,initial-scale=1" />
298
+ <meta name="oke:subscriber_id" content="<your-okendo-subscriber-id>" />
299
+ ...
300
+ ```
301
+
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):
303
+
304
+ ```tsx
305
+ ...
306
+ <body>
307
+ <OkendoProvider
308
+ nonce={nonce}
309
+ okendoProviderData={data.okendoProviderData}
310
+ />
311
+ ...
312
+ </body>
313
+ ...
314
+ ```
315
+
316
+ ### `app/entry.server.tsx`
317
+
318
+ > This is only necessary if Content Security Policy is active in your project.
319
+
320
+ Locate the call to `createContentSecurityPolicy`, and add:
321
+
322
+ - `https://d3hw6dc1ow8pp2.cloudfront.net`, `https://d3g5hqndtiniji.cloudfront.net`, and `data:` to `defaultSrc`
323
+ - `https://d3hw6dc1ow8pp2.cloudfront.net` to `styleSrc`
324
+ - `https://api.okendo.io` to `connectSrc`
325
+
326
+ Note that it's necessary to to add the default values (`'self'`, etc.) when [extending the CSP](https://shopify.dev/docs/custom-storefronts/hydrogen/content-security-policy). The call to `createContentSecurityPolicy` should now look like the following:
327
+
328
+ ```ts
329
+ const { nonce, header, NonceProvider } = createContentSecurityPolicy({
330
+ defaultSrc: [
331
+ "'self'",
332
+ "localhost:*",
333
+ "https://cdn.shopify.com",
334
+ "https://d3hw6dc1ow8pp2.cloudfront.net",
335
+ "https://d3g5hqndtiniji.cloudfront.net",
336
+ "https://cdn-static.okendo.io",
337
+ "https://surveys.okendo.io",
338
+ "data:",
339
+ ],
340
+ imgSrc: [
341
+ "'self'",
342
+ "https://cdn.shopify.com",
343
+ "data:",
344
+ "https://d3hw6dc1ow8pp2.cloudfront.net",
345
+ "https://d3g5hqndtiniji.cloudfront.net",
346
+ "https://cdn-static.okendo.io",
347
+ "https://surveys.okendo.io",
348
+ ],
349
+ mediaSrc: [
350
+ "'self'",
351
+ "https://d3hw6dc1ow8pp2.cloudfront.net",
352
+ "https://d3g5hqndtiniji.cloudfront.net",
353
+ "https://cdn-static.okendo.io"
354
+ ],
355
+ styleSrcElem: [
356
+ "'self'",
357
+ "'unsafe-inline'",
358
+ "https://cdn.shopify.com",
359
+ "https://fonts.googleapis.com",
360
+ "https://fonts.gstatic.com",
361
+ "https://d3hw6dc1ow8pp2.cloudfront.net",
362
+ "https://cdn-static.okendo.io",
363
+ "https://surveys.okendo.io",
364
+ ],
365
+ scriptSrc: [
366
+ "'self'",
367
+ "https://cdn.shopify.com",
368
+ "https://d3hw6dc1ow8pp2.cloudfront.net",
369
+ "https://cdn-static.okendo.io",
370
+ "https://surveys.okendo.io",
371
+ ],
372
+ fontSrc: [
373
+ "'self'",
374
+ "https://fonts.gstatic.com',
375
+ "https://d3hw6dc1ow8pp2.cloudfront.net",
376
+ "https://cdn.shopify.com",
377
+ "https://cdn-static.okendo.io",
378
+ "https://surveys.okendo.io",
379
+ ],
380
+ connectSrc: [
381
+ "'self'",
382
+ "https://monorail-edge.shopifysvc.com",
383
+ "localhost:*",
384
+ "ws://localhost:*",
385
+ "ws://127.0.0.1:*",
386
+ "https://api.okendo.io",
387
+ "https://cdn-static.okendo.io",
388
+ "https://surveys.okendo.io",
389
+ ],
390
+ });
391
+ ```
392
+
393
+ ### `app/routes/_index.tsx`
394
+
395
+ Add the following imports:
396
+
397
+ ```ts
398
+ import {
399
+ OkendoStarRating,
400
+ type WithOkendoStarRatingSnippet,
401
+ } from "@okendo/shopify-hydrogen";
402
+ ```
403
+
404
+ Add the following block just before the `RECOMMENDED_PRODUCTS_QUERY` GraphQL query:
405
+
406
+ ```ts
407
+ const OKENDO_PRODUCT_STAR_RATING_FRAGMENT = `#graphql
408
+ fragment OkendoStarRatingSnippet on Product {
409
+ okendoStarRatingSnippet: metafield(
410
+ namespace: "okendo"
411
+ key: "StarRatingSnippet"
412
+ ) {
413
+ value
414
+ }
415
+ }
416
+ ` as const;
417
+ ```
418
+
419
+ Then append `${OKENDO_PRODUCT_STAR_RATING_FRAGMENT}` and `...OkendoStarRatingSnippet` to `RECOMMENDED_PRODUCTS_QUERY`:
420
+
421
+ ```ts
422
+ const RECOMMENDED_PRODUCTS_QUERY = `#graphql
423
+ ${OKENDO_PRODUCT_STAR_RATING_FRAGMENT}
424
+ fragment RecommendedProduct on Product {
425
+ id
426
+ title
427
+ handle
428
+ priceRange {
429
+ minVariantPrice {
430
+ amount
431
+ currencyCode
432
+ }
433
+ }
434
+ images(first: 1) {
435
+ nodes {
436
+ id
437
+ url
438
+ altText
439
+ width
440
+ height
441
+ }
442
+ }
443
+ ...OkendoStarRatingSnippet
444
+ }
445
+ query RecommendedProducts ($country: CountryCode, $language: LanguageCode)
446
+ @inContext(country: $country, language: $language) {
447
+ products(first: 4, sortKey: UPDATED_AT, reverse: true) {
448
+ nodes {
449
+ ...RecommendedProduct
450
+ }
451
+ }
452
+ }
453
+ ` as const;
454
+ ```
455
+
456
+ Tweak the type of the `products` prop of `RecommendedProducts`:
457
+
458
+ ```ts
459
+ products: Promise<{
460
+ products: {
461
+ nodes: (RecommendedProductsQuery["products"]["nodes"][0] &
462
+ WithOkendoStarRatingSnippet)[];
463
+ };
464
+ }>;
465
+ ```
466
+
467
+ Add `OkendoStarRating` to `RecommendedProducts`:
468
+
469
+ ```tsx
470
+ <OkendoStarRating
471
+ productId={product.id}
472
+ okendoStarRatingSnippet={product.okendoStarRatingSnippet}
473
+ />
474
+ ```
475
+
476
+ For instance, we can add it below the product title, like this:
477
+
478
+ ```tsx
479
+ <Image
480
+ data={product.images.nodes[0]}
481
+ aspectRatio="1/1"
482
+ sizes="(min-width: 45em) 20vw, 50vw"
483
+ />
484
+ <h4>{product.title}</h4>
485
+ <OkendoStarRating
486
+ productId={product.id}
487
+ okendoStarRatingSnippet={product.okendoStarRatingSnippet}
488
+ />
489
+ <small>
490
+ <Money data={product.priceRange.minVariantPrice} />
491
+ </small>
492
+ ```
493
+
494
+ We now have the Okendo Star Rating widget visible on our page:
495
+
496
+ ![Okendo's Star Rating widget](./okendo-star-rating-widget.png)
497
+
498
+ ### `app/routes/products.$handle.tsx`
499
+
500
+ Add the following imports:
501
+
502
+ ```ts
503
+ import {
504
+ OKENDO_PRODUCT_REVIEWS_FRAGMENT,
505
+ OKENDO_PRODUCT_STAR_RATING_FRAGMENT,
506
+ OkendoReviews,
507
+ OkendoStarRating,
508
+ type WithOkendoReviewsSnippet,
509
+ type WithOkendoStarRatingSnippet,
510
+ } from "@okendo/shopify-hydrogen";
511
+ ```
512
+
513
+ Add the following block just before the `RECOMMENDED_PRODUCTS_QUERY` GraphQL query:
514
+
515
+ ```ts
516
+ const OKENDO_PRODUCT_STAR_RATING_FRAGMENT = `#graphql
517
+ fragment OkendoStarRatingSnippet on Product {
518
+ okendoStarRatingSnippet: metafield(
519
+ namespace: "okendo"
520
+ key: "StarRatingSnippet"
521
+ ) {
522
+ value
523
+ }
524
+ }
525
+ ` as const;
526
+
527
+ const OKENDO_PRODUCT_REVIEWS_FRAGMENT = `#graphql
528
+ fragment OkendoReviewsSnippet on Product {
529
+ okendoReviewsSnippet: metafield(
530
+ namespace: "okendo"
531
+ key: "ReviewsWidgetSnippet"
532
+ ) {
533
+ value
534
+ }
535
+ }
536
+ ` as const;
537
+ ```
538
+
539
+ Then append `${OKENDO_PRODUCT_STAR_RATING_FRAGMENT}`, `${OKENDO_PRODUCT_REVIEWS_FRAGMENT}`, `...OkendoStarRatingSnippet`, and `...OkendoReviewsSnippet` to `PRODUCT_FRAGMENT`:
540
+
541
+ ```ts
542
+ const PRODUCT_FRAGMENT = `#graphql
543
+ ${OKENDO_PRODUCT_STAR_RATING_FRAGMENT}
544
+ ${OKENDO_PRODUCT_REVIEWS_FRAGMENT}
545
+ fragment Product on Product {
546
+ id
547
+ title
548
+ vendor
549
+ handle
550
+ descriptionHtml
551
+ description
552
+ options {
553
+ name
554
+ values
555
+ }
556
+ selectedVariant: variantBySelectedOptions(selectedOptions: $selectedOptions) {
557
+ ...ProductVariant
558
+ }
559
+ variants(first: 1) {
560
+ nodes {
561
+ ...ProductVariant
562
+ }
563
+ }
564
+ seo {
565
+ description
566
+ title
567
+ }
568
+ ...OkendoStarRatingSnippet
569
+ ...OkendoReviewsSnippet
570
+ }
571
+ ${PRODUCT_VARIANT_FRAGMENT}
572
+ ` as const;
573
+ ```
574
+
575
+ Add `OkendoReviews` to `Product`:
576
+
577
+ ```tsx
578
+ <OkendoReviews
579
+ productId={product.id}
580
+ okendoReviewsSnippet={product.okendoReviewsSnippet}
581
+ />
582
+ ```
583
+
584
+ For instance, we can add it below the product section, like this:
585
+
586
+ ```tsx
587
+ <>
588
+ <div className="product">
589
+ <ProductImage image={selectedVariant?.image} />
590
+ <ProductMain
591
+ selectedVariant={selectedVariant}
592
+ product={product}
593
+ variants={variants}
594
+ />
595
+ </div>
596
+
597
+ <OkendoReviews
598
+ productId={product.id}
599
+ okendoReviewsSnippet={product.okendoReviewsSnippet}
600
+ />
601
+ </>
602
+ ```
603
+
604
+ Tweak the type of the `product` prop of `ProductMain`:
605
+
606
+ ```ts
607
+ product: ProductFragment &
608
+ WithOkendoStarRatingSnippet &
609
+ WithOkendoReviewsSnippet;
610
+ ```
611
+
612
+ Add `OkendoStarRating` to `ProductMain`:
613
+
614
+ ```tsx
615
+ <OkendoStarRating
616
+ productId={product.id}
617
+ okendoStarRatingSnippet={product.okendoStarRatingSnippet}
618
+ />
619
+ ```
620
+
621
+ For instance, we can add it below the product title, like this:
622
+
623
+ ```tsx
624
+ <div className="product-main">
625
+ <h1>{title}</h1>
626
+ <OkendoStarRating
627
+ productId={product.id}
628
+ okendoStarRatingSnippet={product.okendoStarRatingSnippet}
629
+ />
630
+ <ProductPrice selectedVariant={selectedVariant} />
631
+ ```
632
+
633
+ We now have the Okendo Star Rating and Reviews widgets visible on our product page:
634
+
635
+ ![Okendo's Star Rating and Reviews widgets](./okendo-star-rating-and-reviews-widgets.png)