@okendo/shopify-hydrogen 2.4.0 → 2.5.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 +308 -624
- package/dist/cjs/index.js +1 -1
- package/dist/cjs/types/components/OkendoProvider/OkendoProvider.d.ts +3 -9
- package/dist/cjs/types/components/OkendoReviews/OkendoReviews.d.ts +4 -7
- package/dist/cjs/types/components/OkendoReviewsCarousel/OkendoReviewsCarousel.d.ts +4 -7
- package/dist/cjs/types/components/OkendoStarRating/OkendoStarRating.d.ts +4 -7
- package/dist/cjs/types/internal/OkendoWidget/OkendoWidget.d.ts +4 -2
- package/dist/esm/index.js +1 -1
- package/dist/esm/types/components/OkendoProvider/OkendoProvider.d.ts +3 -9
- package/dist/esm/types/components/OkendoReviews/OkendoReviews.d.ts +4 -7
- package/dist/esm/types/components/OkendoReviewsCarousel/OkendoReviewsCarousel.d.ts +4 -7
- package/dist/esm/types/components/OkendoStarRating/OkendoStarRating.d.ts +4 -7
- package/dist/esm/types/internal/OkendoWidget/OkendoWidget.d.ts +4 -2
- package/dist/index.d.ts +13 -28
- package/package.json +6 -6
- package/dist/cjs/types/internal/OkendoContext.d.ts +0 -10
- package/dist/esm/types/internal/OkendoContext.d.ts +0 -10
- package/readme-res/okendo-star-rating-and-reviews-widgets.png +0 -0
- package/readme-res/okendo-star-rating-widget.png +0 -0
package/README.md
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
> Note
|
|
1
|
+
> **Note**: this package is to be used on stores built with **Shopify Hydrogen v2**. If your store is built with the deprecated Shopify Hydrogen v1, please use the [version 1](https://www.npmjs.com/package/@okendo/shopify-hydrogen/v/1.6.6) of this package.
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> **Note**: the new version of Shopify Hydrogen v2 uses **React Router**. Previous versions used **Remix**. If your store is built with Remix, please use [version `2.4`](https://www.npmjs.com/package/@okendo/shopify-hydrogen/v/2.4.0) of this package.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
# Okendo Hydrogen (React Router) React Components
|
|
6
|
+
|
|
7
|
+
This package brings Okendo's [Reviews widgets](https://okendo.io/reviews) and [Loyalty widgets](https://okendo.io/loyalty) to a Shopify Hydrogen store.
|
|
6
8
|
|
|
7
9
|
## Requirements
|
|
8
10
|
|
|
9
|
-
- A Shopify store with
|
|
10
|
-
- For existing merchants, your store must be
|
|
11
|
-
- A current Okendo subscription.
|
|
12
|
-
- A [Shopify Hydrogen](https://hydrogen.shopify.dev/) app.
|
|
11
|
+
- A Shopify store with a [Hydrogen](https://hydrogen.shopify.dev/) storefront and [Okendo](https://apps.shopify.com/okendo-reviews) installed and configured.
|
|
12
|
+
- For existing merchants, your store must be using Okendo's Widget Plus widgets. [Contact us](mailto:support@okendo.io) if it's not the case, it's free to upgrade.
|
|
13
13
|
|
|
14
14
|
## Demo Store
|
|
15
15
|
|
|
@@ -17,418 +17,17 @@ Our demo store, which is based on the demo store provided by Shopify, can be fou
|
|
|
17
17
|
|
|
18
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 demo store's repository](https://github.com/okendo/okendo-shopify-hydrogen-demo/commits/master/).
|
|
19
19
|
|
|
20
|
-
## Exposition of Shopify Metafields <a id="expose-shopify-metafields" name="expose-shopify-metafields"></a>
|
|
21
|
-
|
|
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.
|
|
23
|
-
|
|
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).
|
|
25
|
-
|
|
26
|
-
<details>
|
|
27
|
-
|
|
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
|
-
#### Using Curl
|
|
46
|
-
|
|
47
|
-
> Note: The settings metafields exist within the Okendo App's Reserved Namespace. To access these metafields we need to include the Okendo App ID (`app--1576377`) as a prefix in the `namespace` field.
|
|
48
|
-
|
|
49
|
-
Open a new terminal or PowerShell window, then:
|
|
50
|
-
|
|
51
|
-
1. Run the following command to expose the `widget_pre_render_style_tags` shop metafield:
|
|
52
|
-
|
|
53
|
-
```bash
|
|
54
|
-
curl -X POST \
|
|
55
|
-
https://{shop}.myshopify.com/admin/api/2024-10/graphql.json \
|
|
56
|
-
-H 'Content-Type: application/graphql' \
|
|
57
|
-
-H 'X-Shopify-Access-Token: {access_token}' \
|
|
58
|
-
-d '
|
|
59
|
-
mutation {
|
|
60
|
-
metafieldDefinitionCreate(
|
|
61
|
-
definition: {
|
|
62
|
-
name: "WidgetPreRenderStyleTags"
|
|
63
|
-
namespace: "app--1576377--reviews"
|
|
64
|
-
key: "widget_pre_render_style_tags"
|
|
65
|
-
type: "multi_line_text_field"
|
|
66
|
-
ownerType: SHOP
|
|
67
|
-
access: {
|
|
68
|
-
admin: PUBLIC_READ
|
|
69
|
-
storefront: PUBLIC_READ
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
) {
|
|
73
|
-
createdDefinition { id name }
|
|
74
|
-
userErrors { field message code }
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
'
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
2. Run the following command to expose the `widget_pre_render_body_style_tags` shop metafield:
|
|
81
|
-
|
|
82
|
-
```bash
|
|
83
|
-
curl -X POST \
|
|
84
|
-
https://{shop}.myshopify.com/admin/api/2024-10/graphql.json \
|
|
85
|
-
-H 'Content-Type: application/graphql' \
|
|
86
|
-
-H 'X-Shopify-Access-Token: {access_token}' \
|
|
87
|
-
-d '
|
|
88
|
-
mutation {
|
|
89
|
-
metafieldDefinitionCreate(
|
|
90
|
-
definition: {
|
|
91
|
-
name: "WidgetPreRenderBodyStyleTags"
|
|
92
|
-
namespace: "app--1576377--reviews"
|
|
93
|
-
key: "widget_pre_render_body_style_tags"
|
|
94
|
-
type: "multi_line_text_field"
|
|
95
|
-
ownerType: SHOP
|
|
96
|
-
access: {
|
|
97
|
-
admin: PUBLIC_READ
|
|
98
|
-
storefront: PUBLIC_READ
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
) {
|
|
102
|
-
createdDefinition { id name }
|
|
103
|
-
userErrors { field message code }
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
'
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
3. Run the following command to expose the `reviews_widget_snippet` product metafield:
|
|
110
|
-
|
|
111
|
-
```bash
|
|
112
|
-
curl -X POST \
|
|
113
|
-
https://{shop}.myshopify.com/admin/api/2024-10/graphql.json \
|
|
114
|
-
-H 'Content-Type: application/graphql' \
|
|
115
|
-
-H 'X-Shopify-Access-Token: {access_token}' \
|
|
116
|
-
-d '
|
|
117
|
-
mutation {
|
|
118
|
-
metafieldDefinitionCreate(
|
|
119
|
-
definition: {
|
|
120
|
-
name: "ReviewsWidgetSnippet"
|
|
121
|
-
namespace: "app--1576377--reviews"
|
|
122
|
-
key: "reviews_widget_snippet"
|
|
123
|
-
type: "multi_line_text_field"
|
|
124
|
-
ownerType: PRODUCT
|
|
125
|
-
access: {
|
|
126
|
-
admin: PUBLIC_READ
|
|
127
|
-
storefront: PUBLIC_READ
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
) {
|
|
131
|
-
createdDefinition { id name }
|
|
132
|
-
userErrors { field message code }
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
'
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
4. Run the following command to expose the `star_rating_snippet` product metafield:
|
|
139
|
-
|
|
140
|
-
```bash
|
|
141
|
-
curl -X POST \
|
|
142
|
-
https://{shop}.myshopify.com/admin/api/2024-10/graphql.json \
|
|
143
|
-
-H 'Content-Type: application/graphql' \
|
|
144
|
-
-H 'X-Shopify-Access-Token: {access_token}' \
|
|
145
|
-
-d '
|
|
146
|
-
mutation {
|
|
147
|
-
metafieldDefinitionCreate(
|
|
148
|
-
definition: {
|
|
149
|
-
name: "StarRatingSnippet"
|
|
150
|
-
namespace: "app--1576377--reviews"
|
|
151
|
-
key: "star_rating_snippet"
|
|
152
|
-
type: "multi_line_text_field"
|
|
153
|
-
ownerType: PRODUCT
|
|
154
|
-
access: {
|
|
155
|
-
admin: PUBLIC_READ
|
|
156
|
-
storefront: PUBLIC_READ
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
) {
|
|
160
|
-
createdDefinition { id name }
|
|
161
|
-
userErrors { field message code }
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
'
|
|
165
|
-
```
|
|
166
|
-
|
|
167
|
-
5. Run the following command to expose the `review_count` product metafield:
|
|
168
|
-
|
|
169
|
-
```bash
|
|
170
|
-
curl -X POST \
|
|
171
|
-
https://{shop}.myshopify.com/admin/api/2024-10/graphql.json \
|
|
172
|
-
-H 'Content-Type: application/graphql' \
|
|
173
|
-
-H 'X-Shopify-Access-Token: {access_token}' \
|
|
174
|
-
-d '
|
|
175
|
-
mutation {
|
|
176
|
-
metafieldDefinitionCreate(
|
|
177
|
-
definition: {
|
|
178
|
-
name: "ReviewCount"
|
|
179
|
-
namespace: "app--1576377--reviews"
|
|
180
|
-
key: "review_count"
|
|
181
|
-
type: "number_integer"
|
|
182
|
-
ownerType: PRODUCT
|
|
183
|
-
access: {
|
|
184
|
-
admin: PUBLIC_READ
|
|
185
|
-
storefront: PUBLIC_READ
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
) {
|
|
189
|
-
createdDefinition { id name }
|
|
190
|
-
userErrors { field message code }
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
'
|
|
194
|
-
```
|
|
195
|
-
|
|
196
|
-
6. Run the following command to expose the `average_rating` product metafield:
|
|
197
|
-
|
|
198
|
-
```bash
|
|
199
|
-
curl -X POST \
|
|
200
|
-
https://{shop}.myshopify.com/admin/api/2024-10/graphql.json \
|
|
201
|
-
-H 'Content-Type: application/graphql' \
|
|
202
|
-
-H 'X-Shopify-Access-Token: {access_token}' \
|
|
203
|
-
-d '
|
|
204
|
-
mutation {
|
|
205
|
-
metafieldDefinitionCreate(
|
|
206
|
-
definition: {
|
|
207
|
-
name: "AverageRating"
|
|
208
|
-
namespace: "app--1576377--reviews"
|
|
209
|
-
key: "average_rating"
|
|
210
|
-
type: "rating"
|
|
211
|
-
ownerType: PRODUCT
|
|
212
|
-
access: {
|
|
213
|
-
admin: PUBLIC_READ
|
|
214
|
-
storefront: PUBLIC_READ
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
) {
|
|
218
|
-
createdDefinition { id name }
|
|
219
|
-
userErrors { field message code }
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
'
|
|
223
|
-
```
|
|
224
|
-
|
|
225
|
-
### Using GraphQL IDE
|
|
226
|
-
|
|
227
|
-
Open your GraphQL IDE (such as Postman) and make `POST` requests with the following details:
|
|
228
|
-
|
|
229
|
-
- **URL:** https://{shop}.myshopify.com/admin/api/2024-10/graphql.json
|
|
230
|
-
- **Headers:** - X-Shopify-Access-Token: {access_token} - Content-Type: application/json
|
|
231
|
-
|
|
232
|
-
1. Execute the following request to expose the `widget_pre_render_style_tags` shop metafield:
|
|
233
|
-
|
|
234
|
-
```graphql
|
|
235
|
-
mutation {
|
|
236
|
-
metafieldDefinitionCreate(
|
|
237
|
-
definition: {
|
|
238
|
-
name: "WidgetPreRenderStyleTags"
|
|
239
|
-
namespace: "app--1576377--reviews"
|
|
240
|
-
key: "widget_pre_render_style_tags"
|
|
241
|
-
type: "multi_line_text_field"
|
|
242
|
-
ownerType: SHOP
|
|
243
|
-
access: {
|
|
244
|
-
admin: PUBLIC_READ,
|
|
245
|
-
storefront: PUBLIC_READ
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
) {
|
|
249
|
-
createdDefinition {
|
|
250
|
-
id
|
|
251
|
-
name
|
|
252
|
-
}
|
|
253
|
-
userErrors {
|
|
254
|
-
field
|
|
255
|
-
message
|
|
256
|
-
code
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
```
|
|
261
|
-
|
|
262
|
-
2. Execute the following request to expose the `widget_pre_render_body_style_tags` shop metafield:
|
|
263
|
-
|
|
264
|
-
```graphql
|
|
265
|
-
mutation {
|
|
266
|
-
metafieldDefinitionCreate(
|
|
267
|
-
definition: {
|
|
268
|
-
name: "WidgetPreRenderBodyStyleTags"
|
|
269
|
-
namespace: "app--1576377--reviews"
|
|
270
|
-
key: "widget_pre_render_body_style_tags"
|
|
271
|
-
type: "multi_line_text_field"
|
|
272
|
-
ownerType: SHOP
|
|
273
|
-
access: {
|
|
274
|
-
admin: PUBLIC_READ,
|
|
275
|
-
storefront: PUBLIC_READ
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
) {
|
|
279
|
-
createdDefinition {
|
|
280
|
-
id
|
|
281
|
-
name
|
|
282
|
-
}
|
|
283
|
-
userErrors {
|
|
284
|
-
field
|
|
285
|
-
message
|
|
286
|
-
code
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
```
|
|
291
|
-
|
|
292
|
-
3. Execute the following request to expose the `reviews_widget_snippet` product metafield:
|
|
293
|
-
|
|
294
|
-
```graphql
|
|
295
|
-
mutation {
|
|
296
|
-
metafieldDefinitionCreate(
|
|
297
|
-
definition: {
|
|
298
|
-
name: "ReviewsWidgetSnippet"
|
|
299
|
-
namespace: "app--1576377--reviews"
|
|
300
|
-
key: "reviews_widget_snippet"
|
|
301
|
-
type: "multi_line_text_field"
|
|
302
|
-
ownerType: PRODUCT
|
|
303
|
-
access: {
|
|
304
|
-
admin: PUBLIC_READ,
|
|
305
|
-
storefront: PUBLIC_READ
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
) {
|
|
309
|
-
createdDefinition {
|
|
310
|
-
id
|
|
311
|
-
name
|
|
312
|
-
}
|
|
313
|
-
userErrors {
|
|
314
|
-
field
|
|
315
|
-
message
|
|
316
|
-
code
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
```
|
|
321
|
-
|
|
322
|
-
4. Execute the following request to expose the `star_rating_snippet` product metafield:
|
|
323
|
-
|
|
324
|
-
```graphql
|
|
325
|
-
mutation {
|
|
326
|
-
metafieldDefinitionCreate(
|
|
327
|
-
definition: {
|
|
328
|
-
name: "StarRatingSnippet"
|
|
329
|
-
namespace: "app--1576377--reviews"
|
|
330
|
-
key: "star_rating_snippet"
|
|
331
|
-
type: "multi_line_text_field"
|
|
332
|
-
ownerType: PRODUCT
|
|
333
|
-
access: {
|
|
334
|
-
admin: PUBLIC_READ,
|
|
335
|
-
storefront: PUBLIC_READ
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
) {
|
|
339
|
-
createdDefinition {
|
|
340
|
-
id
|
|
341
|
-
name
|
|
342
|
-
}
|
|
343
|
-
userErrors {
|
|
344
|
-
field
|
|
345
|
-
message
|
|
346
|
-
code
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
```
|
|
351
|
-
|
|
352
|
-
5. Execute the following request to expose the `review_count` product metafield:
|
|
353
|
-
|
|
354
|
-
```graphql
|
|
355
|
-
mutation {
|
|
356
|
-
metafieldDefinitionCreate(
|
|
357
|
-
definition: {
|
|
358
|
-
name: "ReviewCount"
|
|
359
|
-
namespace: "app--1576377--reviews"
|
|
360
|
-
key: "review_count"
|
|
361
|
-
type: "number_integer"
|
|
362
|
-
ownerType: PRODUCT
|
|
363
|
-
access: {
|
|
364
|
-
admin: PUBLIC_READ,
|
|
365
|
-
storefront: PUBLIC_READ
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
) {
|
|
369
|
-
createdDefinition {
|
|
370
|
-
id
|
|
371
|
-
name
|
|
372
|
-
}
|
|
373
|
-
userErrors {
|
|
374
|
-
field
|
|
375
|
-
message
|
|
376
|
-
code
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
```
|
|
381
|
-
|
|
382
|
-
6. Execute the following request to expose the `average_rating` product metafield:
|
|
383
|
-
|
|
384
|
-
```graphql
|
|
385
|
-
mutation {
|
|
386
|
-
metafieldDefinitionCreate(
|
|
387
|
-
definition: {
|
|
388
|
-
name: "AverageRating"
|
|
389
|
-
namespace: "app--1576377--reviews"
|
|
390
|
-
key: "average_rating"
|
|
391
|
-
type: "rating"
|
|
392
|
-
ownerType: PRODUCT
|
|
393
|
-
access: {
|
|
394
|
-
admin: PUBLIC_READ,
|
|
395
|
-
storefront: PUBLIC_READ
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
) {
|
|
399
|
-
createdDefinition {
|
|
400
|
-
id
|
|
401
|
-
name
|
|
402
|
-
}
|
|
403
|
-
userErrors {
|
|
404
|
-
field
|
|
405
|
-
message
|
|
406
|
-
code
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
```
|
|
411
|
-
|
|
412
|
-
**References**
|
|
413
|
-
|
|
414
|
-
- [https://shopify.dev/api/examples/metafields#step-1-expose-metafields](https://shopify.dev/api/examples/metafields#step-1-expose-metafields)
|
|
415
|
-
- [https://shopify.dev/docs/api/admin-graphql/2024-10/mutations/metafieldDefinitionCreate](https://shopify.dev/docs/api/admin-graphql/2024-10/mutations/metafieldDefinitionCreate)
|
|
416
|
-
|
|
417
|
-
</details>
|
|
418
|
-
|
|
419
20
|
## Installation
|
|
420
21
|
|
|
421
22
|
This package provides:
|
|
422
23
|
|
|
423
24
|
- one function: `getOkendoProviderData`,
|
|
424
25
|
- one provider: `OkendoProvider`,
|
|
425
|
-
-
|
|
26
|
+
- three React components: `OkendoStarRating`, `OkendoReviews`, and `OkendoReviewsCarousel`.
|
|
426
27
|
|
|
427
|
-
The function `getOkendoProviderData` needs to be called in the `loader` function of `root.tsx` in
|
|
28
|
+
The function `getOkendoProviderData` needs to be called in the `loader` function of `root.tsx` in your Hydrogen store. The data is then passed to `OkendoProvider`, which is added to your website's `body` and wraps everything in it.
|
|
428
29
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
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.
|
|
30
|
+
Then, the React components can be added on your store pages. There are a few more bits of configuration to do, please see below.
|
|
432
31
|
|
|
433
32
|
> 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).
|
|
434
33
|
|
|
@@ -440,64 +39,42 @@ npm i @okendo/shopify-hydrogen
|
|
|
440
39
|
|
|
441
40
|
### `app/root.tsx`
|
|
442
41
|
|
|
443
|
-
`OkendoProvider` supports two ways of loading the data returned by `getOkendoProviderData`:
|
|
444
|
-
|
|
445
|
-
- **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.
|
|
446
|
-
- **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.
|
|
447
|
-
|
|
448
|
-
To summarise the differences between the two behaviours:
|
|
449
|
-
|
|
450
|
-
- Pass the promise to `OkendoProvider` — so don't use `await` with `getOkendoProviderData`:
|
|
451
|
-
|
|
452
|
-
- The page loading time won't be increased at all.
|
|
453
|
-
- The widgets will be rendered client-side.
|
|
454
|
-
- Placeholders (which are customisable) are shown until the widgets are rendered.
|
|
455
|
-
|
|
456
|
-
- Pass the data to `OkendoProvider` — so use `await` with `getOkendoProviderData`:
|
|
457
|
-
- The page loading time can be increased by a couple hundreds milliseconds.
|
|
458
|
-
- The widgets will be rendered server-side.
|
|
459
|
-
- The widgets are shown as soon as the page loads — no placeholders needed.
|
|
460
|
-
|
|
461
|
-
You can easily experiment with the two ways, and decide which is the one you'd like to keep for your store.
|
|
462
|
-
|
|
463
42
|
Open `app/root.tsx` and add the following import:
|
|
464
43
|
|
|
465
44
|
```ts
|
|
466
45
|
import {
|
|
467
|
-
|
|
468
|
-
|
|
46
|
+
OkendoProvider,
|
|
47
|
+
getOkendoProviderData,
|
|
469
48
|
} from '@okendo/shopify-hydrogen';
|
|
470
49
|
```
|
|
471
50
|
|
|
472
|
-
Locate the `
|
|
473
|
-
|
|
474
|
-
As explained above, set `okendoProviderData` to either `getOkendoProviderData(...)`, or `await getOkendoProviderData(...)`:
|
|
51
|
+
Locate the `loadDeferredData` function, append `okendoProviderData` to the returned data as shown below, and set `subscriberId` to your Okendo subscriber ID.
|
|
475
52
|
|
|
476
53
|
```ts
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
}
|
|
54
|
+
// ...
|
|
55
|
+
return {
|
|
56
|
+
cart: cart.get(),
|
|
57
|
+
isLoggedIn: customerAccount.isLoggedIn(),
|
|
58
|
+
footer,
|
|
59
|
+
okendoProviderData: getOkendoProviderData({
|
|
60
|
+
context,
|
|
61
|
+
subscriberId: '<your-okendo-subscriber-id>',
|
|
62
|
+
}),
|
|
63
|
+
};
|
|
487
64
|
```
|
|
488
65
|
|
|
489
66
|
Locate the `Layout` component, add the `meta` tag `oke:subscriber_id` to `head`, and place your Okendo subscriber ID in its content:
|
|
490
67
|
|
|
491
68
|
```tsx
|
|
492
69
|
<head>
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
70
|
+
<meta charSet="utf-8" />
|
|
71
|
+
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
|
72
|
+
<meta name="oke:subscriber_id" content="<your-okendo-subscriber-id>" />
|
|
73
|
+
...
|
|
497
74
|
</head>
|
|
498
75
|
```
|
|
499
76
|
|
|
500
|
-
Append `OkendoProvider` to `body`, and pass it the promise
|
|
77
|
+
Append `OkendoProvider` to `body`, and pass it the promise 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):
|
|
501
78
|
|
|
502
79
|
```tsx
|
|
503
80
|
<body>
|
|
@@ -507,10 +84,10 @@ Append `OkendoProvider` to `body`, and pass it the promise — or the data — r
|
|
|
507
84
|
cart={data.cart}
|
|
508
85
|
shop={data.shop}
|
|
509
86
|
consent={data.consent}
|
|
510
|
-
|
|
87
|
+
>
|
|
511
88
|
<PageLayout {...data}>{children}</PageLayout>
|
|
512
89
|
</Analytics.Provider>
|
|
513
|
-
|
|
90
|
+
</OkendoProvider>
|
|
514
91
|
) : (
|
|
515
92
|
children
|
|
516
93
|
)}
|
|
@@ -524,7 +101,8 @@ Append `OkendoProvider` to `body`, and pass it the promise — or the data — r
|
|
|
524
101
|
> This is only necessary if Content Security Policy is active in your project.
|
|
525
102
|
|
|
526
103
|
Locate the call to `createContentSecurityPolicy`, and ensure your configuration includes the entries below:
|
|
527
|
-
|
|
104
|
+
|
|
105
|
+
> 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:
|
|
528
106
|
|
|
529
107
|
```ts
|
|
530
108
|
const { nonce, header, NonceProvider } = createContentSecurityPolicy({
|
|
@@ -532,8 +110,8 @@ const { nonce, header, NonceProvider } = createContentSecurityPolicy({
|
|
|
532
110
|
checkoutDomain: context.env.PUBLIC_CHECKOUT_DOMAIN,
|
|
533
111
|
storeDomain: context.env.PUBLIC_STORE_DOMAIN,
|
|
534
112
|
},
|
|
535
|
-
|
|
536
|
-
|
|
113
|
+
defaultSrc: [
|
|
114
|
+
"'self'",
|
|
537
115
|
'localhost:*',
|
|
538
116
|
'https://cdn.shopify.com',
|
|
539
117
|
'https://www.google.com',
|
|
@@ -545,9 +123,9 @@ const { nonce, header, NonceProvider } = createContentSecurityPolicy({
|
|
|
545
123
|
'https://surveys.okendo.io',
|
|
546
124
|
'https://api.okendo.io',
|
|
547
125
|
'data:',
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
126
|
+
],
|
|
127
|
+
imgSrc: [
|
|
128
|
+
"'self'",
|
|
551
129
|
'https://cdn.shopify.com',
|
|
552
130
|
'data:',
|
|
553
131
|
'https://d3hw6dc1ow8pp2.cloudfront.net',
|
|
@@ -555,26 +133,26 @@ const { nonce, header, NonceProvider } = createContentSecurityPolicy({
|
|
|
555
133
|
'https://dov7r31oq5dkj.cloudfront.net',
|
|
556
134
|
'https://cdn-static.okendo.io',
|
|
557
135
|
'https://surveys.okendo.io',
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
136
|
+
],
|
|
137
|
+
mediaSrc: [
|
|
138
|
+
"'self'",
|
|
561
139
|
'https://d3hw6dc1ow8pp2.cloudfront.net',
|
|
562
140
|
'https://d3g5hqndtiniji.cloudfront.net',
|
|
563
141
|
'https://dov7r31oq5dkj.cloudfront.net',
|
|
564
142
|
'https://cdn-static.okendo.io',
|
|
565
|
-
|
|
143
|
+
],
|
|
566
144
|
styleSrc: [
|
|
567
|
-
|
|
568
|
-
|
|
145
|
+
"'self'",
|
|
146
|
+
"'unsafe-inline'",
|
|
569
147
|
'https://cdn.shopify.com',
|
|
570
148
|
'https://fonts.googleapis.com',
|
|
571
149
|
'https://fonts.gstatic.com',
|
|
572
150
|
'https://d3hw6dc1ow8pp2.cloudfront.net',
|
|
573
151
|
'https://cdn-static.okendo.io',
|
|
574
152
|
'https://surveys.okendo.io',
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
153
|
+
],
|
|
154
|
+
scriptSrc: [
|
|
155
|
+
"'self'",
|
|
578
156
|
'https://cdn.shopify.com',
|
|
579
157
|
'https://d3hw6dc1ow8pp2.cloudfront.net',
|
|
580
158
|
'https://dov7r31oq5dkj.cloudfront.net',
|
|
@@ -583,18 +161,18 @@ const { nonce, header, NonceProvider } = createContentSecurityPolicy({
|
|
|
583
161
|
'https://api.okendo.io',
|
|
584
162
|
'https://www.google.com',
|
|
585
163
|
'https://www.gstatic.com',
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
164
|
+
],
|
|
165
|
+
fontSrc: [
|
|
166
|
+
"'self'",
|
|
589
167
|
'https://fonts.gstatic.com',
|
|
590
168
|
'https://d3hw6dc1ow8pp2.cloudfront.net',
|
|
591
169
|
'https://dov7r31oq5dkj.cloudfront.net',
|
|
592
170
|
'https://cdn.shopify.com',
|
|
593
171
|
'https://cdn-static.okendo.io',
|
|
594
172
|
'https://surveys.okendo.io',
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
173
|
+
],
|
|
174
|
+
connectSrc: [
|
|
175
|
+
"'self'",
|
|
598
176
|
'https://monorail-edge.shopifysvc.com',
|
|
599
177
|
'localhost:*',
|
|
600
178
|
'ws://localhost:*',
|
|
@@ -605,223 +183,313 @@ const { nonce, header, NonceProvider } = createContentSecurityPolicy({
|
|
|
605
183
|
'https://api.raygun.com',
|
|
606
184
|
'https://www.google.com',
|
|
607
185
|
'https://www.gstatic.com',
|
|
608
|
-
|
|
186
|
+
],
|
|
609
187
|
frameSrc: ['https://www.google.com', 'https://www.gstatic.com'],
|
|
610
188
|
});
|
|
611
189
|
```
|
|
612
190
|
|
|
191
|
+
### `app/lib/fragments.ts`
|
|
192
|
+
|
|
193
|
+
Add the following GraphQL fragment at the bottom of the file:
|
|
194
|
+
|
|
195
|
+
```ts
|
|
196
|
+
export const OKENDO_PRODUCT_STAR_RATING_FRAGMENT = `#graphql
|
|
197
|
+
fragment OkendoStarRatingSnippet on Product {
|
|
198
|
+
okendoStarRatingSnippet: metafield(
|
|
199
|
+
namespace: "app--1576377--reviews"
|
|
200
|
+
key: "star_rating_snippet"
|
|
201
|
+
) {
|
|
202
|
+
value
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
` as const;
|
|
206
|
+
|
|
207
|
+
export const OKENDO_PRODUCT_REVIEWS_FRAGMENT = `#graphql
|
|
208
|
+
fragment OkendoReviewsSnippet on Product {
|
|
209
|
+
okendoReviewsSnippet: metafield(
|
|
210
|
+
namespace: "app--1576377--reviews"
|
|
211
|
+
key: "reviews_widget_snippet"
|
|
212
|
+
) {
|
|
213
|
+
value
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
` as const;
|
|
217
|
+
```
|
|
218
|
+
|
|
613
219
|
### `app/routes/_index.tsx`
|
|
614
220
|
|
|
615
221
|
Add the following import:
|
|
616
222
|
|
|
617
223
|
```ts
|
|
618
|
-
import {
|
|
224
|
+
import { OKENDO_PRODUCT_STAR_RATING_FRAGMENT } from '~/lib/fragments';
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
Then append `${OKENDO_PRODUCT_STAR_RATING_FRAGMENT}` and `...OkendoStarRatingSnippet` to `RECOMMENDED_PRODUCTS_QUERY`:
|
|
228
|
+
|
|
229
|
+
```ts
|
|
230
|
+
const RECOMMENDED_PRODUCTS_QUERY = `#graphql
|
|
231
|
+
${OKENDO_PRODUCT_STAR_RATING_FRAGMENT}
|
|
232
|
+
fragment RecommendedProduct on Product {
|
|
233
|
+
id
|
|
234
|
+
title
|
|
235
|
+
handle
|
|
236
|
+
priceRange {
|
|
237
|
+
minVariantPrice {
|
|
238
|
+
amount
|
|
239
|
+
currencyCode
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
images(first: 1) {
|
|
243
|
+
nodes {
|
|
244
|
+
id
|
|
245
|
+
url
|
|
246
|
+
altText
|
|
247
|
+
width
|
|
248
|
+
height
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
...OkendoStarRatingSnippet
|
|
252
|
+
}
|
|
253
|
+
query RecommendedProducts ($country: CountryCode, $language: LanguageCode)
|
|
254
|
+
@inContext(country: $country, language: $language) {
|
|
255
|
+
products(first: 4, sortKey: UPDATED_AT, reverse: true) {
|
|
256
|
+
nodes {
|
|
257
|
+
...RecommendedProduct
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
` as const;
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### `app/routes/collections.all.tsx`
|
|
265
|
+
|
|
266
|
+
Add the following import:
|
|
267
|
+
|
|
268
|
+
```ts
|
|
269
|
+
import { OKENDO_PRODUCT_STAR_RATING_FRAGMENT } from '~/lib/fragments';
|
|
619
270
|
```
|
|
620
271
|
|
|
621
|
-
|
|
272
|
+
Then append `${OKENDO_PRODUCT_STAR_RATING_FRAGMENT}` and `...OkendoStarRatingSnippet` to `COLLECTION_ITEM_FRAGMENT`:
|
|
622
273
|
|
|
623
274
|
```ts
|
|
624
|
-
const
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
275
|
+
const COLLECTION_ITEM_FRAGMENT = `#graphql
|
|
276
|
+
${OKENDO_PRODUCT_STAR_RATING_FRAGMENT}
|
|
277
|
+
fragment MoneyCollectionItem on MoneyV2 {
|
|
278
|
+
amount
|
|
279
|
+
currencyCode
|
|
280
|
+
}
|
|
281
|
+
fragment CollectionItem on Product {
|
|
282
|
+
id
|
|
283
|
+
handle
|
|
284
|
+
title
|
|
285
|
+
featuredImage {
|
|
286
|
+
id
|
|
287
|
+
altText
|
|
288
|
+
url
|
|
289
|
+
width
|
|
290
|
+
height
|
|
291
|
+
}
|
|
292
|
+
priceRange {
|
|
293
|
+
minVariantPrice {
|
|
294
|
+
...MoneyCollectionItem
|
|
295
|
+
}
|
|
296
|
+
maxVariantPrice {
|
|
297
|
+
...MoneyCollectionItem
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
...OkendoStarRatingSnippet
|
|
301
|
+
}
|
|
633
302
|
` as const;
|
|
634
303
|
```
|
|
635
304
|
|
|
636
|
-
|
|
305
|
+
### `app/routes/collections.$handle.tsx`
|
|
306
|
+
|
|
307
|
+
Add the following import:
|
|
637
308
|
|
|
638
309
|
```ts
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
310
|
+
import { OKENDO_PRODUCT_STAR_RATING_FRAGMENT } from '~/lib/fragments';
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
Then append `${OKENDO_PRODUCT_STAR_RATING_FRAGMENT}` and `...OkendoStarRatingSnippet` to `COLLECTION_ITEM_FRAGMENT`:
|
|
314
|
+
|
|
315
|
+
```ts
|
|
316
|
+
const PRODUCT_ITEM_FRAGMENT = `#graphql
|
|
317
|
+
${OKENDO_PRODUCT_STAR_RATING_FRAGMENT}
|
|
318
|
+
fragment MoneyProductItem on MoneyV2 {
|
|
319
|
+
amount
|
|
320
|
+
currencyCode
|
|
321
|
+
}
|
|
322
|
+
fragment ProductItem on Product {
|
|
323
|
+
id
|
|
324
|
+
handle
|
|
325
|
+
title
|
|
326
|
+
featuredImage {
|
|
327
|
+
id
|
|
328
|
+
altText
|
|
329
|
+
url
|
|
330
|
+
width
|
|
331
|
+
height
|
|
332
|
+
}
|
|
333
|
+
priceRange {
|
|
334
|
+
minVariantPrice {
|
|
335
|
+
...MoneyProductItem
|
|
336
|
+
}
|
|
337
|
+
maxVariantPrice {
|
|
338
|
+
...MoneyProductItem
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
...OkendoStarRatingSnippet
|
|
342
|
+
}
|
|
670
343
|
` as const;
|
|
671
344
|
```
|
|
672
345
|
|
|
673
|
-
|
|
346
|
+
### `app/components/ProductItem.tsx`
|
|
347
|
+
|
|
348
|
+
Add the following import:
|
|
349
|
+
|
|
350
|
+
```ts
|
|
351
|
+
import { OkendoStarRating } from '@okendo/shopify-hydrogen';
|
|
352
|
+
```
|
|
674
353
|
|
|
675
354
|
Add `OkendoStarRating` to the `RecommendedProducts` component — for instance, we can add it below the product title, like this:
|
|
676
355
|
|
|
677
356
|
```tsx
|
|
678
357
|
<Image
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
358
|
+
data={product.images.nodes[0]}
|
|
359
|
+
aspectRatio="1/1"
|
|
360
|
+
sizes="(min-width: 45em) 20vw, 50vw"
|
|
682
361
|
/>
|
|
683
362
|
<h4>{product.title}</h4>
|
|
684
363
|
<OkendoStarRating
|
|
685
|
-
|
|
686
|
-
|
|
364
|
+
className="mb-2"
|
|
365
|
+
productId={product.id}
|
|
366
|
+
okendoStarRatingSnippet={product.okendoStarRatingSnippet}
|
|
687
367
|
/>
|
|
688
368
|
<small>
|
|
689
|
-
|
|
369
|
+
<Money data={product.priceRange.minVariantPrice} />
|
|
690
370
|
</small>
|
|
691
371
|
```
|
|
692
372
|
|
|
693
|
-
> Note: if
|
|
373
|
+
> Note: if you get a type error on `product`, restart the dev server to get the types (`storefrontapi.generated.d.ts`) regenerated from the GraphQL fragments.
|
|
694
374
|
|
|
695
375
|
We now have the Okendo Star Rating widget visible on our page:
|
|
696
376
|
|
|
697
|
-

|
|
700
378
|
|
|
701
379
|
### `app/routes/products.$handle.tsx`
|
|
702
380
|
|
|
703
|
-
Add the following
|
|
381
|
+
Add the following imports:
|
|
704
382
|
|
|
705
383
|
```ts
|
|
706
384
|
import { OkendoReviews, OkendoStarRating } from '@okendo/shopify-hydrogen';
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
```ts
|
|
712
|
-
const OKENDO_PRODUCT_STAR_RATING_FRAGMENT = `#graphql
|
|
713
|
-
fragment OkendoStarRatingSnippet on Product {
|
|
714
|
-
okendoStarRatingSnippet: metafield(
|
|
715
|
-
namespace: "app--1576377--reviews"
|
|
716
|
-
key: "star_rating_snippet"
|
|
717
|
-
) {
|
|
718
|
-
value
|
|
719
|
-
}
|
|
720
|
-
}
|
|
721
|
-
` as const;
|
|
722
|
-
|
|
723
|
-
const OKENDO_PRODUCT_REVIEWS_FRAGMENT = `#graphql
|
|
724
|
-
fragment OkendoReviewsSnippet on Product {
|
|
725
|
-
okendoReviewsSnippet: metafield(
|
|
726
|
-
namespace: "app--1576377--reviews"
|
|
727
|
-
key: "reviews_widget_snippet"
|
|
728
|
-
) {
|
|
729
|
-
value
|
|
730
|
-
}
|
|
731
|
-
}
|
|
732
|
-
` as const;
|
|
385
|
+
import {
|
|
386
|
+
OKENDO_PRODUCT_REVIEWS_FRAGMENT,
|
|
387
|
+
OKENDO_PRODUCT_STAR_RATING_FRAGMENT,
|
|
388
|
+
} from '~/lib/fragments';
|
|
733
389
|
```
|
|
734
390
|
|
|
735
391
|
Then append `${OKENDO_PRODUCT_STAR_RATING_FRAGMENT}`, `${OKENDO_PRODUCT_REVIEWS_FRAGMENT}`, `...OkendoStarRatingSnippet`, and `...OkendoReviewsSnippet` to `PRODUCT_FRAGMENT`:
|
|
736
392
|
|
|
737
393
|
```ts
|
|
738
394
|
const PRODUCT_FRAGMENT = `#graphql
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
395
|
+
${OKENDO_PRODUCT_STAR_RATING_FRAGMENT}
|
|
396
|
+
${OKENDO_PRODUCT_REVIEWS_FRAGMENT}
|
|
397
|
+
fragment Product on Product {
|
|
398
|
+
id
|
|
399
|
+
title
|
|
400
|
+
vendor
|
|
401
|
+
handle
|
|
402
|
+
descriptionHtml
|
|
403
|
+
description
|
|
404
|
+
encodedVariantExistence
|
|
405
|
+
encodedVariantAvailability
|
|
406
|
+
options {
|
|
407
|
+
name
|
|
408
|
+
optionValues {
|
|
409
|
+
name
|
|
410
|
+
firstSelectableVariant {
|
|
411
|
+
...ProductVariant
|
|
412
|
+
}
|
|
413
|
+
swatch {
|
|
414
|
+
color
|
|
415
|
+
image {
|
|
416
|
+
previewImage {
|
|
417
|
+
url
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
selectedOrFirstAvailableVariant(selectedOptions: $selectedOptions, ignoreUnknownOptions: true, caseInsensitiveMatch: true) {
|
|
424
|
+
...ProductVariant
|
|
425
|
+
}
|
|
426
|
+
adjacentVariants (selectedOptions: $selectedOptions) {
|
|
427
|
+
...ProductVariant
|
|
428
|
+
}
|
|
429
|
+
seo {
|
|
430
|
+
description
|
|
431
|
+
title
|
|
432
|
+
}
|
|
433
|
+
...OkendoStarRatingSnippet
|
|
434
|
+
...OkendoReviewsSnippet
|
|
435
|
+
}
|
|
436
|
+
${PRODUCT_VARIANT_FRAGMENT}
|
|
768
437
|
` as const;
|
|
769
438
|
```
|
|
770
439
|
|
|
771
|
-
> Note: if you get a type error on `product`, restart the dev server to get the types (`storefrontapi.generated.d.ts`) regenerated from the GraphQL fragments.
|
|
772
|
-
|
|
773
440
|
Add `OkendoStarRating` and `OkendoReviews` to the `Product` component:
|
|
774
441
|
|
|
775
442
|
```tsx
|
|
776
443
|
<>
|
|
777
|
-
|
|
778
|
-
|
|
444
|
+
<div className="product">
|
|
445
|
+
<ProductImage image={selectedVariant?.image} />
|
|
779
446
|
<div className="product-main">
|
|
780
447
|
<h1>{title}</h1>
|
|
781
448
|
<OkendoStarRating
|
|
449
|
+
className="mb-4"
|
|
782
450
|
productId={product.id}
|
|
783
451
|
okendoStarRatingSnippet={product.okendoStarRatingSnippet}
|
|
784
|
-
|
|
452
|
+
/>
|
|
785
453
|
<ProductPrice
|
|
786
454
|
price={selectedVariant?.price}
|
|
787
455
|
compareAtPrice={selectedVariant?.compareAtPrice}
|
|
788
456
|
/>
|
|
789
457
|
...
|
|
790
|
-
|
|
458
|
+
</div>
|
|
791
459
|
...
|
|
792
460
|
</div>
|
|
793
461
|
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
462
|
+
<OkendoReviews
|
|
463
|
+
productId={product.id}
|
|
464
|
+
okendoReviewsSnippet={product.okendoReviewsSnippet}
|
|
465
|
+
/>
|
|
798
466
|
</>
|
|
799
467
|
```
|
|
800
468
|
|
|
801
|
-
> Note: if
|
|
469
|
+
> Note: if you get a type error on `product`, restart the dev server to get the types (`storefrontapi.generated.d.ts`) regenerated from the GraphQL fragments.
|
|
802
470
|
|
|
803
471
|
We now have the Okendo Star Rating and Reviews widgets visible on our product page:
|
|
804
472
|
|
|
805
|
-

|
|
806
474
|
|
|
807
|
-
### All
|
|
475
|
+
### All-Reviews Widget - Client Side Only
|
|
808
476
|
|
|
809
|
-
If you would like to include a copy of the Okendo Reviews Widget which displays all reviews for a given store (to be used on a reviews page for example), please add
|
|
477
|
+
If you would like to include a copy of the Okendo Reviews Widget which displays all reviews for a given store (to be used on a reviews page for example), please add `OkendoReviews` without supplying the `productId`.
|
|
810
478
|
|
|
811
|
-
Please note the all
|
|
479
|
+
Please note the all-reviews widget loads on the client, not the server.
|
|
812
480
|
|
|
813
481
|
```tsx
|
|
814
|
-
import { type MetaFunction } from '
|
|
482
|
+
import { type MetaFunction } from 'react-router';
|
|
815
483
|
import { OkendoReviews } from '@okendo/shopify-hydrogen';
|
|
816
484
|
|
|
817
485
|
export const meta: MetaFunction = () => {
|
|
818
|
-
return [{title: `Hydrogen | Okendo All Reviews`}];
|
|
486
|
+
return [{ title: `Hydrogen | Okendo All Reviews` }];
|
|
819
487
|
};
|
|
820
488
|
|
|
821
489
|
export default function ReviewsPage() {
|
|
822
490
|
return (
|
|
823
491
|
<div className="all-reviews">
|
|
824
|
-
<h1>All
|
|
492
|
+
<h1>All-Reviews Widget</h1>
|
|
825
493
|
<OkendoReviews />
|
|
826
494
|
</div>
|
|
827
495
|
);
|
|
@@ -829,32 +497,48 @@ export default function ReviewsPage() {
|
|
|
829
497
|
```
|
|
830
498
|
|
|
831
499
|
### Okendo Reviews Carousel Widget - Client Side Only
|
|
832
|
-
If you would like to include a copy of the Okendo Reviews Carousel Widget which displays reviews by product or group for a given store (to be used on a homepage or featured page for example), please add the `OkendoReviewsCarouselWidget` with or without the the `productId` or `groupId`.
|
|
833
500
|
|
|
834
|
-
|
|
501
|
+
If you would like to include a copy of the Okendo Reviews Carousel Widget which displays reviews by product or group for a given store (to be used on a homepage or featured page for example), please add `OkendoReviewsCarousel` with or without the `productId` or `groupId`.
|
|
502
|
+
|
|
503
|
+
Please note the carousel widget loads on the client, not the server.
|
|
835
504
|
|
|
836
505
|
```tsx
|
|
837
|
-
import { type MetaFunction } from '
|
|
506
|
+
import { type MetaFunction } from 'react-router';
|
|
838
507
|
import { OkendoReviews } from '@okendo/shopify-hydrogen';
|
|
839
508
|
|
|
840
509
|
export const meta: MetaFunction = () => {
|
|
841
|
-
return [{title: `Hydrogen | Okendo Reviews Carousel`}];
|
|
510
|
+
return [{ title: `Hydrogen | Okendo Reviews Carousel` }];
|
|
842
511
|
};
|
|
843
512
|
|
|
844
513
|
export default function AFeaturedPage() {
|
|
845
514
|
return (
|
|
846
515
|
<div className="all-reviews">
|
|
847
516
|
<h1>Reviews Carousel Widget</h1>
|
|
848
|
-
<OkendoReviewsCarousel
|
|
849
|
-
|
|
850
|
-
|
|
517
|
+
<OkendoReviewsCarousel productId={product.id} />
|
|
518
|
+
</div>
|
|
519
|
+
);
|
|
520
|
+
}
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
You can also use `OkendoReviewsCarousel` without `productId`, in order to display reviews for all products. For instance, we can add it to the homepage in `app/routes/_index.tsx`:
|
|
524
|
+
|
|
525
|
+
```ts
|
|
526
|
+
export default function Homepage() {
|
|
527
|
+
const data = useLoaderData<typeof loader>();
|
|
528
|
+
return (
|
|
529
|
+
<div className="home">
|
|
530
|
+
<FeaturedCollection collection={data.featuredCollection} />
|
|
531
|
+
<RecommendedProducts products={data.recommendedProducts} />
|
|
532
|
+
<OkendoReviewsCarousel />
|
|
851
533
|
</div>
|
|
852
534
|
);
|
|
853
535
|
}
|
|
854
536
|
```
|
|
855
537
|
|
|
856
538
|
# Loyalty Widgets
|
|
539
|
+
|
|
857
540
|
## Installation
|
|
541
|
+
|
|
858
542
|
To include Loyalty Widgets in your Shopify Hydrogen store, you will need to make the following changes:
|
|
859
543
|
|
|
860
544
|
1. Add `customerAccessToken: await args.context.customerAccount.getAccessToken(),` to your `loader` function, this will be used to log your customer into the Loyalty App.
|
|
@@ -864,35 +548,35 @@ To include Loyalty Widgets in your Shopify Hydrogen store, you will need to make
|
|
|
864
548
|
> Note: If you only wish to use the Loyalty product and not reviews then simply leave out the `'reviews'` from the array like so: `okendoProducts: ['loyalty'],`.
|
|
865
549
|
|
|
866
550
|
The relevant section should now look something like this:
|
|
551
|
+
|
|
867
552
|
```ts
|
|
868
553
|
return defer({
|
|
869
554
|
// ...
|
|
870
555
|
customerAccessToken: await args.context.customerAccount.getAccessToken(),
|
|
871
|
-
okendoProviderData:
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
okendoProducts: ['reviews', 'loyalty'],
|
|
877
|
-
},
|
|
878
|
-
),
|
|
556
|
+
okendoProviderData: getOkendoProviderData({
|
|
557
|
+
context: args.context,
|
|
558
|
+
subscriberId: '<your-okendo-subscriber-id>',
|
|
559
|
+
okendoProducts: ['reviews', 'loyalty'],
|
|
560
|
+
}),
|
|
879
561
|
});
|
|
880
562
|
```
|
|
881
563
|
|
|
882
564
|
3. Add `customerAccessToken={data.customerAccessToken}` to the `OkendoProvider` component, it should now look like:
|
|
883
|
-
|
|
565
|
+
|
|
566
|
+
```tsx
|
|
884
567
|
<OkendoProvider
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
568
|
+
nonce={nonce}
|
|
569
|
+
okendoProviderData={data.okendoProviderData}
|
|
570
|
+
customerAccessToken={data.customerAccessToken}
|
|
888
571
|
>
|
|
889
|
-
|
|
572
|
+
...
|
|
890
573
|
</OkendoProvider>
|
|
891
574
|
```
|
|
892
575
|
|
|
893
576
|
If your Okendo Loyalty Settings are [correctly set up](https://support.okendo.io/en/collections/8270193-okendo-loyalty) and your program has launched, the Loyalty Floating Widget will now appear on your store.
|
|
894
577
|
|
|
895
578
|
## Dedicated Loyalty Page
|
|
579
|
+
|
|
896
580
|
Add `<OkendoLoyaltyEmbeddedWidget />` to any components/pages where you wish to have the Dedicated Loyalty Page appear.
|
|
897
581
|
|
|
898
|
-
|
|
582
|
+
_Make sure you are importing the component from the `okendo-shopify-hydrogen` package: `import {OkendoLoyaltyEmbeddedWidget} from '@okendo/shopify-hydrogen';`_
|