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

|
|
680
378
|
|
|
681
379
|
### `app/routes/products.$handle.tsx`
|
|
682
380
|
|
|
683
|
-
Add the following
|
|
381
|
+
Add the following imports:
|
|
684
382
|
|
|
685
383
|
```ts
|
|
686
384
|
import { OkendoReviews, OkendoStarRating } from '@okendo/shopify-hydrogen';
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
```ts
|
|
692
|
-
const OKENDO_PRODUCT_STAR_RATING_FRAGMENT = `#graphql
|
|
693
|
-
fragment OkendoStarRatingSnippet on Product {
|
|
694
|
-
okendoStarRatingSnippet: metafield(
|
|
695
|
-
namespace: "$app:reviews"
|
|
696
|
-
key: "star_rating_snippet"
|
|
697
|
-
) {
|
|
698
|
-
value
|
|
699
|
-
}
|
|
700
|
-
}
|
|
701
|
-
` as const;
|
|
702
|
-
|
|
703
|
-
const OKENDO_PRODUCT_REVIEWS_FRAGMENT = `#graphql
|
|
704
|
-
fragment OkendoReviewsSnippet on Product {
|
|
705
|
-
okendoReviewsSnippet: metafield(
|
|
706
|
-
namespace: "$app:reviews"
|
|
707
|
-
key: "reviews_widget_snippet"
|
|
708
|
-
) {
|
|
709
|
-
value
|
|
710
|
-
}
|
|
711
|
-
}
|
|
712
|
-
` as const;
|
|
385
|
+
import {
|
|
386
|
+
OKENDO_PRODUCT_REVIEWS_FRAGMENT,
|
|
387
|
+
OKENDO_PRODUCT_STAR_RATING_FRAGMENT,
|
|
388
|
+
} from '~/lib/fragments';
|
|
713
389
|
```
|
|
714
390
|
|
|
715
391
|
Then append `${OKENDO_PRODUCT_STAR_RATING_FRAGMENT}`, `${OKENDO_PRODUCT_REVIEWS_FRAGMENT}`, `...OkendoStarRatingSnippet`, and `...OkendoReviewsSnippet` to `PRODUCT_FRAGMENT`:
|
|
716
392
|
|
|
717
393
|
```ts
|
|
718
394
|
const PRODUCT_FRAGMENT = `#graphql
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
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}
|
|
748
437
|
` as const;
|
|
749
438
|
```
|
|
750
439
|
|
|
751
|
-
> 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.
|
|
752
|
-
|
|
753
440
|
Add `OkendoStarRating` and `OkendoReviews` to the `Product` component:
|
|
754
441
|
|
|
755
442
|
```tsx
|
|
756
443
|
<>
|
|
757
|
-
|
|
758
|
-
|
|
444
|
+
<div className="product">
|
|
445
|
+
<ProductImage image={selectedVariant?.image} />
|
|
759
446
|
<div className="product-main">
|
|
760
447
|
<h1>{title}</h1>
|
|
761
448
|
<OkendoStarRating
|
|
449
|
+
className="mb-4"
|
|
762
450
|
productId={product.id}
|
|
763
451
|
okendoStarRatingSnippet={product.okendoStarRatingSnippet}
|
|
764
|
-
|
|
452
|
+
/>
|
|
765
453
|
<ProductPrice
|
|
766
454
|
price={selectedVariant?.price}
|
|
767
455
|
compareAtPrice={selectedVariant?.compareAtPrice}
|
|
768
456
|
/>
|
|
769
457
|
...
|
|
770
|
-
|
|
458
|
+
</div>
|
|
771
459
|
...
|
|
772
460
|
</div>
|
|
773
461
|
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
462
|
+
<OkendoReviews
|
|
463
|
+
productId={product.id}
|
|
464
|
+
okendoReviewsSnippet={product.okendoReviewsSnippet}
|
|
465
|
+
/>
|
|
778
466
|
</>
|
|
779
467
|
```
|
|
780
468
|
|
|
781
|
-
> 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.
|
|
782
470
|
|
|
783
471
|
We now have the Okendo Star Rating and Reviews widgets visible on our product page:
|
|
784
472
|
|
|
785
|
-

|
|
786
474
|
|
|
787
|
-
### All
|
|
475
|
+
### All-Reviews Widget - Client Side Only
|
|
788
476
|
|
|
789
|
-
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`.
|
|
790
478
|
|
|
791
|
-
Please note the all
|
|
479
|
+
Please note the all-reviews widget loads on the client, not the server.
|
|
792
480
|
|
|
793
481
|
```tsx
|
|
794
|
-
import { type MetaFunction } from '
|
|
482
|
+
import { type MetaFunction } from 'react-router';
|
|
795
483
|
import { OkendoReviews } from '@okendo/shopify-hydrogen';
|
|
796
484
|
|
|
797
485
|
export const meta: MetaFunction = () => {
|
|
798
|
-
return [{title: `Hydrogen | Okendo All Reviews`}];
|
|
486
|
+
return [{ title: `Hydrogen | Okendo All Reviews` }];
|
|
799
487
|
};
|
|
800
488
|
|
|
801
489
|
export default function ReviewsPage() {
|
|
802
490
|
return (
|
|
803
491
|
<div className="all-reviews">
|
|
804
|
-
<h1>All
|
|
492
|
+
<h1>All-Reviews Widget</h1>
|
|
805
493
|
<OkendoReviews />
|
|
806
494
|
</div>
|
|
807
495
|
);
|
|
@@ -809,26 +497,86 @@ export default function ReviewsPage() {
|
|
|
809
497
|
```
|
|
810
498
|
|
|
811
499
|
### Okendo Reviews Carousel Widget - Client Side Only
|
|
812
|
-
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`.
|
|
813
500
|
|
|
814
|
-
|
|
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.
|
|
815
504
|
|
|
816
505
|
```tsx
|
|
817
|
-
import { type MetaFunction } from '
|
|
506
|
+
import { type MetaFunction } from 'react-router';
|
|
818
507
|
import { OkendoReviews } from '@okendo/shopify-hydrogen';
|
|
819
508
|
|
|
820
509
|
export const meta: MetaFunction = () => {
|
|
821
|
-
return [{title: `Hydrogen | Okendo Reviews Carousel`}];
|
|
510
|
+
return [{ title: `Hydrogen | Okendo Reviews Carousel` }];
|
|
822
511
|
};
|
|
823
512
|
|
|
824
513
|
export default function AFeaturedPage() {
|
|
825
514
|
return (
|
|
826
515
|
<div className="all-reviews">
|
|
827
516
|
<h1>Reviews Carousel Widget</h1>
|
|
828
|
-
<OkendoReviewsCarousel
|
|
829
|
-
productId={product.id}
|
|
830
|
-
/>
|
|
517
|
+
<OkendoReviewsCarousel productId={product.id} />
|
|
831
518
|
</div>
|
|
832
519
|
);
|
|
833
520
|
}
|
|
834
|
-
```
|
|
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 />
|
|
533
|
+
</div>
|
|
534
|
+
);
|
|
535
|
+
}
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
# Loyalty Widgets
|
|
539
|
+
|
|
540
|
+
## Installation
|
|
541
|
+
|
|
542
|
+
To include Loyalty Widgets in your Shopify Hydrogen store, you will need to make the following changes:
|
|
543
|
+
|
|
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.
|
|
545
|
+
|
|
546
|
+
2. Add `okendoProducts: ['reviews', 'loyalty'],` as a property to `getOkendoProviderData` in your `loader` function, alongside the existing `context` and `subscriberId` arguments.
|
|
547
|
+
|
|
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'],`.
|
|
549
|
+
|
|
550
|
+
The relevant section should now look something like this:
|
|
551
|
+
|
|
552
|
+
```ts
|
|
553
|
+
return defer({
|
|
554
|
+
// ...
|
|
555
|
+
customerAccessToken: await args.context.customerAccount.getAccessToken(),
|
|
556
|
+
okendoProviderData: getOkendoProviderData({
|
|
557
|
+
context: args.context,
|
|
558
|
+
subscriberId: '<your-okendo-subscriber-id>',
|
|
559
|
+
okendoProducts: ['reviews', 'loyalty'],
|
|
560
|
+
}),
|
|
561
|
+
});
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
3. Add `customerAccessToken={data.customerAccessToken}` to the `OkendoProvider` component, it should now look like:
|
|
565
|
+
|
|
566
|
+
```tsx
|
|
567
|
+
<OkendoProvider
|
|
568
|
+
nonce={nonce}
|
|
569
|
+
okendoProviderData={data.okendoProviderData}
|
|
570
|
+
customerAccessToken={data.customerAccessToken}
|
|
571
|
+
>
|
|
572
|
+
...
|
|
573
|
+
</OkendoProvider>
|
|
574
|
+
```
|
|
575
|
+
|
|
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.
|
|
577
|
+
|
|
578
|
+
## Dedicated Loyalty Page
|
|
579
|
+
|
|
580
|
+
Add `<OkendoLoyaltyEmbeddedWidget />` to any components/pages where you wish to have the Dedicated Loyalty Page appear.
|
|
581
|
+
|
|
582
|
+
_Make sure you are importing the component from the `okendo-shopify-hydrogen` package: `import {OkendoLoyaltyEmbeddedWidget} from '@okendo/shopify-hydrogen';`_
|