@okendo/shopify-hydrogen 2.1.6 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +50 -22
- package/dist/cjs/index.js +1 -1
- package/dist/cjs/types/components/OkendoProvider/OkendoProvider.d.ts +15 -14
- package/dist/cjs/types/components/OkendoReviews/OkendoReviews.d.ts +8 -3
- package/dist/cjs/types/components/OkendoStarRating/OkendoStarRating.d.ts +8 -3
- package/dist/cjs/types/internal/OkendoContext.d.ts +10 -0
- package/dist/cjs/types/internal/OkendoWidget/OkendoWidget.d.ts +6 -6
- package/dist/esm/index.js +1 -1
- package/dist/esm/types/components/OkendoProvider/OkendoProvider.d.ts +15 -14
- package/dist/esm/types/components/OkendoReviews/OkendoReviews.d.ts +8 -3
- package/dist/esm/types/components/OkendoStarRating/OkendoStarRating.d.ts +8 -3
- package/dist/esm/types/internal/OkendoContext.d.ts +10 -0
- package/dist/esm/types/internal/OkendoWidget/OkendoWidget.d.ts +6 -6
- package/dist/index.d.ts +26 -16
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
> Note: this package is to be used on stores built with Hydrogen v2, based on Remix. If your store is built with the
|
|
1
|
+
> Note: this package is to be used on stores built with Hydrogen v2, based on Remix. If your store is built with the deprecated Hydrogen v1, please use the [version 1](https://www.npmjs.com/package/@okendo/shopify-hydrogen/v/1.3.0) of this package.
|
|
2
2
|
|
|
3
3
|
# Okendo Hydrogen 2 (Remix) React Components
|
|
4
4
|
|
|
@@ -15,24 +15,13 @@ This package brings [Okendo's review widgets](https://www.okendo.io/blog/widget-
|
|
|
15
15
|
|
|
16
16
|
Our demo store, which is based on the demo store provided by Shopify, can be found [here](https://github.com/okendo/okendo-shopify-hydrogen-demo).
|
|
17
17
|
|
|
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
|
|
18
|
+
> Note: there have been multiple versions of Shopify's Hydrogen demo store. If your project is based on an old version of it, consult the history of our dome store's repository.
|
|
19
19
|
|
|
20
|
-
##
|
|
20
|
+
## Exposition of Shopify Metafields <a id="expose-shopify-metafields" name="expose-shopify-metafields"></a>
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
- one function: `getOkendoProviderData`
|
|
25
|
-
- three React components: `OkendoProvider`, `OkendoStarRating`, and `OkendoReviews`
|
|
26
|
-
|
|
27
|
-
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`.
|
|
28
|
-
|
|
29
|
-
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
|
-
|
|
31
|
-
## Expose Shopify Metafields <a id="expose-shopify-metafields" name="expose-shopify-metafields"></a>
|
|
32
|
-
|
|
33
|
-
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.
|
|
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.
|
|
34
23
|
|
|
35
|
-
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).
|
|
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).
|
|
36
25
|
|
|
37
26
|
<details>
|
|
38
27
|
|
|
@@ -502,6 +491,18 @@ mutation {
|
|
|
502
491
|
|
|
503
492
|
## Installation
|
|
504
493
|
|
|
494
|
+
This package provides:
|
|
495
|
+
|
|
496
|
+
- one function: `getOkendoProviderData`,
|
|
497
|
+
- one provider: `OkendoProvider`,
|
|
498
|
+
- two React components: `OkendoStarRating` and `OkendoReviews`.
|
|
499
|
+
|
|
500
|
+
The function `getOkendoProviderData` needs to be called in the `loader` function of `root.tsx` in the Hydrogen 2 store. The data is then passed to `OkendoProvider`, which is added to your website's `body` and wraps everything in it.
|
|
501
|
+
|
|
502
|
+
> Important: `OkendoProvider` supports two ways of loading the data returned by `getOkendoProviderData`: either as a promise, or as the data itself. Its behaviour is different between the two ways — this is explained below.
|
|
503
|
+
|
|
504
|
+
Then, the components `OkendoStarRating` and `OkendoReviews` can be added on the store pages. There are a few more bits of configuration to do, please see below.
|
|
505
|
+
|
|
505
506
|
> The code examples provided in this section are based on the Shopify template store created by running `npm create @shopify/hydrogen@latest` (see [Shopify's documentation](https://shopify.dev/docs/custom-storefronts/hydrogen/getting-started)). You will find the following steps already done in [our demo store](https://github.com/okendo/okendo-shopify-hydrogen-demo).
|
|
506
507
|
|
|
507
508
|
Run:
|
|
@@ -512,6 +513,26 @@ npm i @okendo/shopify-hydrogen
|
|
|
512
513
|
|
|
513
514
|
### `app/root.tsx`
|
|
514
515
|
|
|
516
|
+
`OkendoProvider` supports two ways of loading the data returned by `getOkendoProviderData`:
|
|
517
|
+
|
|
518
|
+
- **as a promise**: in this case, the query getting the data is deferred, which allows your page to load as quickly as it does without Okendo's widgets. When the data is ready, it is sent to the browser, and Okendo's widgets are rendered. Blank placeholders are shown until the widgets are rendered. You can customise these placeholders to show loading spinners or skeletons that fit well with your store's theme.
|
|
519
|
+
- **as the data**: in this case, the query getting the data needs to complete before your page loads, which can add a couple hundreds milliseconds of loading time. Widgets are then rendered server-side, and so appear as soon as your page loads.
|
|
520
|
+
|
|
521
|
+
To summarise the differences between the two behaviours:
|
|
522
|
+
|
|
523
|
+
- Pass the promise to `OkendoProvider` — so don't use `await` with `getOkendoProviderData`:
|
|
524
|
+
|
|
525
|
+
- The page loading time won't be increased at all.
|
|
526
|
+
- The widgets will be rendered client-side.
|
|
527
|
+
- Placeholders (which are customisable) are shown until the widgets are rendered.
|
|
528
|
+
|
|
529
|
+
- Pass the data to `OkendoProvider` — so use `await` with `getOkendoProviderData`:
|
|
530
|
+
- The page loading time can be increased by a couple hundreds milliseconds.
|
|
531
|
+
- The widgets will be rendered server-side.
|
|
532
|
+
- The widgets are shown as soon as the page loads — no placeholders needed.
|
|
533
|
+
|
|
534
|
+
You can easily experiment with the two ways, and decide which is the one you'd like to keep for your store.
|
|
535
|
+
|
|
515
536
|
Open `app/root.tsx` and add the following import:
|
|
516
537
|
|
|
517
538
|
```ts
|
|
@@ -521,17 +542,19 @@ import {
|
|
|
521
542
|
} from "@okendo/shopify-hydrogen";
|
|
522
543
|
```
|
|
523
544
|
|
|
524
|
-
Locate the `loader` function, append `okendoProviderData` to the returned data as shown below, and set `subscriberId` to your Okendo subscriber ID
|
|
545
|
+
Locate the `loader` function, append `okendoProviderData` to the returned data as shown below, and set `subscriberId` to your Okendo subscriber ID.
|
|
546
|
+
|
|
547
|
+
As explained above, set `okendoProviderData` to either `getOkendoProviderData(...)`, or `await getOkendoProviderData(...)`:
|
|
525
548
|
|
|
526
549
|
```ts
|
|
527
550
|
return defer(
|
|
528
551
|
{
|
|
529
552
|
...
|
|
530
|
-
okendoProviderData: await getOkendoProviderData({
|
|
553
|
+
okendoProviderData: /* place `await` here if you want server-rendered widgets */ getOkendoProviderData({
|
|
531
554
|
context,
|
|
532
555
|
subscriberId: "<your-okendo-subscriber-id>",
|
|
533
556
|
}),
|
|
534
|
-
}
|
|
557
|
+
},
|
|
535
558
|
);
|
|
536
559
|
```
|
|
537
560
|
|
|
@@ -545,7 +568,7 @@ Locate the `App` function, add the `meta` tag `oke:subscriber_id` to `head`, and
|
|
|
545
568
|
...
|
|
546
569
|
```
|
|
547
570
|
|
|
548
|
-
Append `OkendoProvider` to `body`, and pass it the data returned by `getOkendoProviderData`. If Content Security Policy is active in your project, you also need to provide the `nonce` (available with `const nonce = useNonce()` in Shopify's Hydrogen demo store):
|
|
571
|
+
Append `OkendoProvider` to `body`, and pass it the promise — or the data — returned by `getOkendoProviderData`. If Content Security Policy is active in your project, you also need to provide the `nonce` (available with `const nonce = useNonce()` in Shopify's Hydrogen demo store):
|
|
549
572
|
|
|
550
573
|
```tsx
|
|
551
574
|
...
|
|
@@ -553,8 +576,9 @@ Append `OkendoProvider` to `body`, and pass it the data returned by `getOkendoPr
|
|
|
553
576
|
<OkendoProvider
|
|
554
577
|
nonce={nonce}
|
|
555
578
|
okendoProviderData={data.okendoProviderData}
|
|
556
|
-
|
|
557
|
-
|
|
579
|
+
>
|
|
580
|
+
...
|
|
581
|
+
</OkendoProvider>
|
|
558
582
|
</body>
|
|
559
583
|
...
|
|
560
584
|
```
|
|
@@ -737,6 +761,8 @@ For instance, we can add it below the product title, like this:
|
|
|
737
761
|
</small>
|
|
738
762
|
```
|
|
739
763
|
|
|
764
|
+
> Note: if the widgets are rendered client-side (if you don't use `await` when calling `getOkendoProviderData`), you can provide your own placeholder by using the `placeholder` property of `OkendoStarRating`.
|
|
765
|
+
|
|
740
766
|
We now have the Okendo Star Rating widget visible on our page:
|
|
741
767
|
|
|
742
768
|

|
|
@@ -847,6 +873,8 @@ For instance, we can add it below the product section, like this:
|
|
|
847
873
|
</>
|
|
848
874
|
```
|
|
849
875
|
|
|
876
|
+
> Note: if the widgets are rendered client-side (if you don't use `await` when calling `getOkendoProviderData`), you can provide your own placeholder by using the `placeholder` property of `OkendoReviews`.
|
|
877
|
+
|
|
850
878
|
Tweak the type of the `product` prop of `ProductMain`:
|
|
851
879
|
|
|
852
880
|
```ts
|
package/dist/cjs/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var t=require("react");const e=({
|
|
1
|
+
"use strict";var e=require("react"),t=require("@remix-run/react");const n=e.createContext(void 0),r=({children:t})=>{const[r,o]=e.useState(!1);return e.createElement(n.Provider,{value:{okendoDataLoaded:r,setOkendoDataLoaded:o}},t)},o=()=>{const t=e.useContext(n);if(void 0===t)throw new Error("useOkendoContext must be within OkendoContext");return t},a="cdn-static.okendo.io",i=({nonce:t="",okendoProviderData:n,productUrlFormatter:r,awaited:i,cdnDomain:s})=>{const{setOkendoDataLoaded:d}=o();if(e.useEffect((()=>{if(n&&d(!0),n&&i){const e=document.createElement("script");e.src=`https://${s||a}/reviews-widget-plus/js/okendo-reviews.js`,document.head.appendChild(e)}}),[n,d,i,s]),!n)return null;const{reviewsHeaderConfig:c,cssVariables:l,customCss:u,initScriptContents:p,preRenderStyleTags:m,starSymbols:g}=n,v=(l??"").replace('<style id="oke-css-vars">',"").replace("</style>",""),k=u?u.replace('<style id="oke-reviews-custom-css">',"").replace("</style>",""):"";return e.createElement(e.Fragment,null,e.createElement("script",{nonce:t,id:"oke-reviews-settings",type:"application/json",dangerouslySetInnerHTML:{__html:JSON.stringify(c)}}),e.createElement("style",{nonce:t,id:"oke-css-vars",dangerouslySetInnerHTML:{__html:v}}),k&&e.createElement("style",{nonce:t,id:"oke-reviews-custom-css",dangerouslySetInnerHTML:{__html:k}}),!i&&p&&e.createElement("script",{nonce:t,dangerouslySetInnerHTML:{__html:p}}),e.createElement("script",{nonce:t,type:"text/javascript",dangerouslySetInnerHTML:{__html:`window.okeProductUrlFormatter = ${"function"==typeof r?r.toString():"string"==typeof r?r:"(product) =>\n product && product.productHandle\n ? \"/products/\" + product.productHandle + \"/\" + (product.variantId ? '?variantId=' + product.variantId : '')\n : undefined"}`}}),m&&e.createElement("div",{dangerouslySetInnerHTML:{__html:m}}),g&&e.createElement("div",{dangerouslySetInnerHTML:{__html:g}}))};const s=({dataAttributes:t,metafieldContent:n=""})=>{const r=e.useRef(null),o=e.useRef(!1),a=function(t){const n=e.useRef();return e.useEffect((()=>{n.current=t})),n.current}(t),i=()=>{r.current&&(window.okeWidgetApi.initWidget(r.current),o.current=!0)};return e.useEffect((()=>{if(!a||t["data-oke-widget"]!==a["data-oke-widget"]||t["data-oke-star-rating"]!==a["data-oke-star-rating"]||t["data-oke-reviews-product-id"]!==a["data-oke-reviews-product-id"]||!o.current)return window.okeWidgetApi&&r.current?i():document.addEventListener("oke-script-loaded",i),()=>{document.removeEventListener("oke-script-loaded",i)}}),[t,a]),e.createElement("div",{ref:r,key:JSON.stringify(t),...t,dangerouslySetInnerHTML:n?{__html:n}:void 0})},d=/^[0-9]*$/;function c(e){if(e)return`shopify-${d.test(e)?e:e.split("/").slice(-1)[0]}`}exports.OKENDO_PRODUCT_REVIEWS_FRAGMENT='#graphql\n\tfragment OkendoReviewsSnippet on Product {\n\t\tokendoReviewsSnippet: metafield(\n\t\t\tnamespace: "okendo"\n\t\t\tkey: "ReviewsWidgetSnippet"\n\t\t) {\n\t\t\tvalue\n\t\t}\n\t}\n',exports.OKENDO_PRODUCT_STAR_RATING_FRAGMENT='#graphql\n\tfragment OkendoStarRatingSnippet on Product {\n\t\tokendoStarRatingSnippet: metafield(\n\t\t\tnamespace: "okendo"\n\t\t\tkey: "StarRatingSnippet"\n\t\t) {\n\t\t\tvalue\n\t\t}\n\t}\n',exports.OkendoProvider=({nonce:n="",okendoProviderData:o,productUrlFormatter:a,cdnDomain:s,children:d})=>e.createElement(r,null,o&&"then"in o?e.createElement(e.Suspense,null,e.createElement(t.Await,{resolve:o},(t=>e.createElement(i,{nonce:n,okendoProviderData:t,productUrlFormatter:a,cdnDomain:s,awaited:!0})))):e.createElement(i,{nonce:n,okendoProviderData:o,productUrlFormatter:a,cdnDomain:s}),d),exports.OkendoReviews=({productId:t,okendoReviewsSnippet:n,placeholder:r})=>{const{okendoDataLoaded:a}=o(),i={"data-oke-widget":"","data-oke-reviews-product-id":c(t)};return a?e.createElement(s,{dataAttributes:i,metafieldContent:n?.value}):r||e.createElement("div",{style:{height:"350px"}})},exports.OkendoStarRating=({productId:t,okendoStarRatingSnippet:n,placeholder:r})=>{const{okendoDataLoaded:a}=o(),i={"data-oke-star-rating":"","data-oke-reviews-product-id":c(t)};return a?e.createElement(s,{dataAttributes:i,metafieldContent:n?.value}):r||e.createElement("div",{style:{height:"20px"}})},exports.getOkendoProviderData=async({context:e,subscriberId:t,apiDomain:n,cdnDomain:r})=>{const o=`https://${n||"api.okendo.io/v1"}/stores/${t}/widget_plus_settings`,i=await fetch(o);if(!i.ok)return console.error(`Failed to retrieve subscriber settings for subscriber ID '${t}'.`),null;const{reviewsHeaderConfig:s,cssVariables:d,customCss:c,starSymbols:l}=await i.json(),u=await fetch(`https://${r||a}/reviews-widget-plus/js/okendo-reviews.js`);if(!u.ok)return console.error("Failed to retrieve widget initialisation script."),null;const p=await u.text(),{shop:{widgetPreRenderStyleTags:m}}=await e.storefront.query('#graphql\n\t\tquery metafields {\n\t\t\tshop {\n\t\t\t\twidgetPreRenderStyleTags: metafield(\n\t\t\t\t\tnamespace: "okendo"\n\t\t\t\t\tkey: "WidgetPreRenderStyleTags"\n\t\t\t\t) {\n\t\t\t\t\tvalue\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t'),g=m?.value??"";return g||console.warn("Failed to retrieve pre-rendered widget style settings."),{reviewsHeaderConfig:s,cssVariables:d,customCss:c,initScriptContents:p,preRenderStyleTags:g,starSymbols:l}};
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import {
|
|
1
|
+
import type { WidgetPlus } from '@okendo/reviews-common';
|
|
2
|
+
import type { Storefront } from '@shopify/hydrogen';
|
|
3
|
+
import React, { type PropsWithChildren } from 'react';
|
|
3
4
|
interface OkendoProviderData {
|
|
4
|
-
reviewsHeaderConfig:
|
|
5
|
+
reviewsHeaderConfig: WidgetPlus.ReviewsHeaderConfig;
|
|
5
6
|
cssVariables: string;
|
|
6
7
|
customCss?: string;
|
|
7
8
|
initScriptContents: string;
|
|
@@ -15,14 +16,7 @@ export declare const getOkendoProviderData: ({ context, subscriberId, apiDomain,
|
|
|
15
16
|
subscriberId: string;
|
|
16
17
|
apiDomain?: string;
|
|
17
18
|
cdnDomain?: string;
|
|
18
|
-
}) => Promise<
|
|
19
|
-
reviewsHeaderConfig: import("@okendo/reviews-common").WidgetPlus.ReviewsHeaderConfig;
|
|
20
|
-
cssVariables: string;
|
|
21
|
-
customCss: string | undefined;
|
|
22
|
-
initScriptContents: string;
|
|
23
|
-
preRenderStyleTags: string;
|
|
24
|
-
starSymbols: string;
|
|
25
|
-
} | null>;
|
|
19
|
+
}) => Promise<OkendoProviderData | null>;
|
|
26
20
|
interface ReviewProduct {
|
|
27
21
|
productHandle?: string;
|
|
28
22
|
productId: string;
|
|
@@ -34,10 +28,17 @@ interface OkendoProviderProps {
|
|
|
34
28
|
* this is required if Content Security Policy is set up on your store.
|
|
35
29
|
*/
|
|
36
30
|
nonce?: string;
|
|
37
|
-
/**
|
|
38
|
-
|
|
31
|
+
/**
|
|
32
|
+
* Either:
|
|
33
|
+
* - the data returned by the promise returned by `getOkendoProviderData`, using this will allow the
|
|
34
|
+
* Okendo widgets to show instantly on page load, but it will slightly increase the page load time;
|
|
35
|
+
* - the promise returned by `getOkendoProviderData`, using this will make the Okendo widgets
|
|
36
|
+
* show slightly after page load, but it have the advantage of not increasing the page load time.
|
|
37
|
+
*/
|
|
38
|
+
okendoProviderData: Partial<OkendoProviderData> | null | Promise<Partial<OkendoProviderData> | null>;
|
|
39
39
|
/** An optional product URL formatter */
|
|
40
40
|
productUrlFormatter?: (product: ReviewProduct) => string;
|
|
41
|
+
cdnDomain?: string;
|
|
41
42
|
}
|
|
42
|
-
export declare const OkendoProvider:
|
|
43
|
+
export declare const OkendoProvider: ({ nonce, okendoProviderData, productUrlFormatter, cdnDomain, children, }: OkendoProviderProps & PropsWithChildren) => React.JSX.Element;
|
|
43
44
|
export {};
|
|
@@ -1,11 +1,16 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { type MetafieldValue } from
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { type MetafieldValue } from '../../internal/types';
|
|
3
3
|
export interface WithOkendoReviewsSnippet {
|
|
4
4
|
okendoReviewsSnippet?: MetafieldValue | null;
|
|
5
5
|
}
|
|
6
6
|
interface OkendoReviewsProps {
|
|
7
7
|
productId: string;
|
|
8
8
|
okendoReviewsSnippet?: MetafieldValue | null;
|
|
9
|
+
/**
|
|
10
|
+
* Only used if the promise returned by `getOkendoProviderData` is given to the OkendoProvider
|
|
11
|
+
* without being awaited; in this case, the placeholder here will be shown until the widget is ready
|
|
12
|
+
*/
|
|
13
|
+
placeholder?: JSX.Element;
|
|
9
14
|
}
|
|
10
|
-
export declare const OkendoReviews:
|
|
15
|
+
export declare const OkendoReviews: ({ productId, okendoReviewsSnippet, placeholder }: OkendoReviewsProps) => React.JSX.Element;
|
|
11
16
|
export {};
|
|
@@ -1,11 +1,16 @@
|
|
|
1
|
-
import { type
|
|
2
|
-
import { type MetafieldValue } from
|
|
1
|
+
import React, { type ReactNode } from 'react';
|
|
2
|
+
import { type MetafieldValue } from '../../internal/types';
|
|
3
3
|
export interface WithOkendoStarRatingSnippet {
|
|
4
4
|
okendoStarRatingSnippet?: MetafieldValue | null;
|
|
5
5
|
}
|
|
6
6
|
interface OkendoStarRatingProps {
|
|
7
7
|
productId: string;
|
|
8
8
|
okendoStarRatingSnippet?: MetafieldValue | null;
|
|
9
|
+
/**
|
|
10
|
+
* Only used if the promise returned by `getOkendoProviderData` is given to the OkendoProvider
|
|
11
|
+
* without being awaited; in this case, the placeholder here will be shown until the widget is ready
|
|
12
|
+
*/
|
|
13
|
+
placeholder?: ReactNode;
|
|
9
14
|
}
|
|
10
|
-
export declare const OkendoStarRating:
|
|
15
|
+
export declare const OkendoStarRating: ({ productId, okendoStarRatingSnippet, placeholder }: OkendoStarRatingProps) => string | number | true | Iterable<React.ReactNode> | React.JSX.Element;
|
|
11
16
|
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React, { type Dispatch, type PropsWithChildren, type SetStateAction } from 'react';
|
|
2
|
+
export declare const OkendoContext: React.Context<{
|
|
3
|
+
okendoDataLoaded: boolean;
|
|
4
|
+
setOkendoDataLoaded: Dispatch<SetStateAction<boolean>>;
|
|
5
|
+
} | undefined>;
|
|
6
|
+
export declare const OkendoContextProvider: ({ children }: PropsWithChildren) => React.JSX.Element;
|
|
7
|
+
export declare const useOkendoContext: () => {
|
|
8
|
+
okendoDataLoaded: boolean;
|
|
9
|
+
setOkendoDataLoaded: Dispatch<SetStateAction<boolean>>;
|
|
10
|
+
};
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import type { ReviewsWidgetPlus } from
|
|
2
|
-
import
|
|
1
|
+
import type { ReviewsWidgetPlus } from '@okendo/reviews-widget-plus/dist-utils/ReviewsWidgetPlus';
|
|
2
|
+
import React from 'react';
|
|
3
3
|
export interface OkendoWidgetProps {
|
|
4
4
|
dataAttributes: {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
'data-oke-widget'?: string;
|
|
6
|
+
'data-oke-star-rating'?: string;
|
|
7
|
+
'data-oke-reviews-product-id': string | undefined;
|
|
8
8
|
};
|
|
9
9
|
metafieldContent?: string;
|
|
10
10
|
}
|
|
@@ -13,4 +13,4 @@ declare global {
|
|
|
13
13
|
okeWidgetApi: ReviewsWidgetPlus.WidgetWindowApi;
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
|
-
export declare const OkendoWidget:
|
|
16
|
+
export declare const OkendoWidget: ({ dataAttributes, metafieldContent }: OkendoWidgetProps) => React.JSX.Element;
|
package/dist/esm/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import
|
|
1
|
+
import e,{createContext as t,useState as n,useContext as r,Suspense as o,useEffect as a,useRef as i}from"react";import{Await as d}from"@remix-run/react";const s=t(void 0),c=({children:t})=>{const[r,o]=n(!1);return e.createElement(s.Provider,{value:{okendoDataLoaded:r,setOkendoDataLoaded:o}},t)},l=()=>{const e=r(s);if(void 0===e)throw new Error("useOkendoContext must be within OkendoContext");return e},u="cdn-static.okendo.io",p=async({context:e,subscriberId:t,apiDomain:n,cdnDomain:r})=>{const o=`https://${n||"api.okendo.io/v1"}/stores/${t}/widget_plus_settings`,a=await fetch(o);if(!a.ok)return console.error(`Failed to retrieve subscriber settings for subscriber ID '${t}'.`),null;const{reviewsHeaderConfig:i,cssVariables:d,customCss:s,starSymbols:c}=await a.json(),l=await fetch(`https://${r||u}/reviews-widget-plus/js/okendo-reviews.js`);if(!l.ok)return console.error("Failed to retrieve widget initialisation script."),null;const p=await l.text(),{shop:{widgetPreRenderStyleTags:m}}=await e.storefront.query('#graphql\n\t\tquery metafields {\n\t\t\tshop {\n\t\t\t\twidgetPreRenderStyleTags: metafield(\n\t\t\t\t\tnamespace: "okendo"\n\t\t\t\t\tkey: "WidgetPreRenderStyleTags"\n\t\t\t\t) {\n\t\t\t\t\tvalue\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t'),g=m?.value??"";return g||console.warn("Failed to retrieve pre-rendered widget style settings."),{reviewsHeaderConfig:i,cssVariables:d,customCss:s,initScriptContents:p,preRenderStyleTags:g,starSymbols:c}},m=({nonce:t="",okendoProviderData:n,productUrlFormatter:r,cdnDomain:a,children:i})=>e.createElement(c,null,n&&"then"in n?e.createElement(o,null,e.createElement(d,{resolve:n},(n=>e.createElement(g,{nonce:t,okendoProviderData:n,productUrlFormatter:r,cdnDomain:a,awaited:!0})))):e.createElement(g,{nonce:t,okendoProviderData:n,productUrlFormatter:r,cdnDomain:a}),i),g=({nonce:t="",okendoProviderData:n,productUrlFormatter:r,awaited:o,cdnDomain:i})=>{const{setOkendoDataLoaded:d}=l();if(a((()=>{if(n&&d(!0),n&&o){const e=document.createElement("script");e.src=`https://${i||u}/reviews-widget-plus/js/okendo-reviews.js`,document.head.appendChild(e)}}),[n,d,o,i]),!n)return null;const{reviewsHeaderConfig:s,cssVariables:c,customCss:p,initScriptContents:m,preRenderStyleTags:g,starSymbols:v}=n,k=(c??"").replace('<style id="oke-css-vars">',"").replace("</style>",""),w=p?p.replace('<style id="oke-reviews-custom-css">',"").replace("</style>",""):"";return e.createElement(e.Fragment,null,e.createElement("script",{nonce:t,id:"oke-reviews-settings",type:"application/json",dangerouslySetInnerHTML:{__html:JSON.stringify(s)}}),e.createElement("style",{nonce:t,id:"oke-css-vars",dangerouslySetInnerHTML:{__html:k}}),w&&e.createElement("style",{nonce:t,id:"oke-reviews-custom-css",dangerouslySetInnerHTML:{__html:w}}),!o&&m&&e.createElement("script",{nonce:t,dangerouslySetInnerHTML:{__html:m}}),e.createElement("script",{nonce:t,type:"text/javascript",dangerouslySetInnerHTML:{__html:`window.okeProductUrlFormatter = ${"function"==typeof r?r.toString():"string"==typeof r?r:"(product) =>\n product && product.productHandle\n ? \"/products/\" + product.productHandle + \"/\" + (product.variantId ? '?variantId=' + product.variantId : '')\n : undefined"}`}}),g&&e.createElement("div",{dangerouslySetInnerHTML:{__html:g}}),v&&e.createElement("div",{dangerouslySetInnerHTML:{__html:v}}))};const v=({dataAttributes:t,metafieldContent:n=""})=>{const r=i(null),o=i(!1),d=function(e){const t=i();return a((()=>{t.current=e})),t.current}(t),s=()=>{r.current&&(window.okeWidgetApi.initWidget(r.current),o.current=!0)};return a((()=>{if(!d||t["data-oke-widget"]!==d["data-oke-widget"]||t["data-oke-star-rating"]!==d["data-oke-star-rating"]||t["data-oke-reviews-product-id"]!==d["data-oke-reviews-product-id"]||!o.current)return window.okeWidgetApi&&r.current?s():document.addEventListener("oke-script-loaded",s),()=>{document.removeEventListener("oke-script-loaded",s)}}),[t,d]),e.createElement("div",{ref:r,key:JSON.stringify(t),...t,dangerouslySetInnerHTML:n?{__html:n}:void 0})},k=/^[0-9]*$/;function w(e){if(e)return`shopify-${k.test(e)?e:e.split("/").slice(-1)[0]}`}const y=({productId:t,okendoReviewsSnippet:n,placeholder:r})=>{const{okendoDataLoaded:o}=l(),a={"data-oke-widget":"","data-oke-reviews-product-id":w(t)};return o?e.createElement(v,{dataAttributes:a,metafieldContent:n?.value}):r||e.createElement("div",{style:{height:"350px"}})},f=({productId:t,okendoStarRatingSnippet:n,placeholder:r})=>{const{okendoDataLoaded:o}=l(),a={"data-oke-star-rating":"","data-oke-reviews-product-id":w(t)};return o?e.createElement(v,{dataAttributes:a,metafieldContent:n?.value}):r||e.createElement("div",{style:{height:"20px"}})},S='#graphql\n\tfragment OkendoStarRatingSnippet on Product {\n\t\tokendoStarRatingSnippet: metafield(\n\t\t\tnamespace: "okendo"\n\t\t\tkey: "StarRatingSnippet"\n\t\t) {\n\t\t\tvalue\n\t\t}\n\t}\n',h='#graphql\n\tfragment OkendoReviewsSnippet on Product {\n\t\tokendoReviewsSnippet: metafield(\n\t\t\tnamespace: "okendo"\n\t\t\tkey: "ReviewsWidgetSnippet"\n\t\t) {\n\t\t\tvalue\n\t\t}\n\t}\n';export{h as OKENDO_PRODUCT_REVIEWS_FRAGMENT,S as OKENDO_PRODUCT_STAR_RATING_FRAGMENT,m as OkendoProvider,y as OkendoReviews,f as OkendoStarRating,p as getOkendoProviderData};
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import {
|
|
1
|
+
import type { WidgetPlus } from '@okendo/reviews-common';
|
|
2
|
+
import type { Storefront } from '@shopify/hydrogen';
|
|
3
|
+
import React, { type PropsWithChildren } from 'react';
|
|
3
4
|
interface OkendoProviderData {
|
|
4
|
-
reviewsHeaderConfig:
|
|
5
|
+
reviewsHeaderConfig: WidgetPlus.ReviewsHeaderConfig;
|
|
5
6
|
cssVariables: string;
|
|
6
7
|
customCss?: string;
|
|
7
8
|
initScriptContents: string;
|
|
@@ -15,14 +16,7 @@ export declare const getOkendoProviderData: ({ context, subscriberId, apiDomain,
|
|
|
15
16
|
subscriberId: string;
|
|
16
17
|
apiDomain?: string;
|
|
17
18
|
cdnDomain?: string;
|
|
18
|
-
}) => Promise<
|
|
19
|
-
reviewsHeaderConfig: import("@okendo/reviews-common").WidgetPlus.ReviewsHeaderConfig;
|
|
20
|
-
cssVariables: string;
|
|
21
|
-
customCss: string | undefined;
|
|
22
|
-
initScriptContents: string;
|
|
23
|
-
preRenderStyleTags: string;
|
|
24
|
-
starSymbols: string;
|
|
25
|
-
} | null>;
|
|
19
|
+
}) => Promise<OkendoProviderData | null>;
|
|
26
20
|
interface ReviewProduct {
|
|
27
21
|
productHandle?: string;
|
|
28
22
|
productId: string;
|
|
@@ -34,10 +28,17 @@ interface OkendoProviderProps {
|
|
|
34
28
|
* this is required if Content Security Policy is set up on your store.
|
|
35
29
|
*/
|
|
36
30
|
nonce?: string;
|
|
37
|
-
/**
|
|
38
|
-
|
|
31
|
+
/**
|
|
32
|
+
* Either:
|
|
33
|
+
* - the data returned by the promise returned by `getOkendoProviderData`, using this will allow the
|
|
34
|
+
* Okendo widgets to show instantly on page load, but it will slightly increase the page load time;
|
|
35
|
+
* - the promise returned by `getOkendoProviderData`, using this will make the Okendo widgets
|
|
36
|
+
* show slightly after page load, but it have the advantage of not increasing the page load time.
|
|
37
|
+
*/
|
|
38
|
+
okendoProviderData: Partial<OkendoProviderData> | null | Promise<Partial<OkendoProviderData> | null>;
|
|
39
39
|
/** An optional product URL formatter */
|
|
40
40
|
productUrlFormatter?: (product: ReviewProduct) => string;
|
|
41
|
+
cdnDomain?: string;
|
|
41
42
|
}
|
|
42
|
-
export declare const OkendoProvider:
|
|
43
|
+
export declare const OkendoProvider: ({ nonce, okendoProviderData, productUrlFormatter, cdnDomain, children, }: OkendoProviderProps & PropsWithChildren) => React.JSX.Element;
|
|
43
44
|
export {};
|
|
@@ -1,11 +1,16 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { type MetafieldValue } from
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { type MetafieldValue } from '../../internal/types';
|
|
3
3
|
export interface WithOkendoReviewsSnippet {
|
|
4
4
|
okendoReviewsSnippet?: MetafieldValue | null;
|
|
5
5
|
}
|
|
6
6
|
interface OkendoReviewsProps {
|
|
7
7
|
productId: string;
|
|
8
8
|
okendoReviewsSnippet?: MetafieldValue | null;
|
|
9
|
+
/**
|
|
10
|
+
* Only used if the promise returned by `getOkendoProviderData` is given to the OkendoProvider
|
|
11
|
+
* without being awaited; in this case, the placeholder here will be shown until the widget is ready
|
|
12
|
+
*/
|
|
13
|
+
placeholder?: JSX.Element;
|
|
9
14
|
}
|
|
10
|
-
export declare const OkendoReviews:
|
|
15
|
+
export declare const OkendoReviews: ({ productId, okendoReviewsSnippet, placeholder }: OkendoReviewsProps) => React.JSX.Element;
|
|
11
16
|
export {};
|
|
@@ -1,11 +1,16 @@
|
|
|
1
|
-
import { type
|
|
2
|
-
import { type MetafieldValue } from
|
|
1
|
+
import React, { type ReactNode } from 'react';
|
|
2
|
+
import { type MetafieldValue } from '../../internal/types';
|
|
3
3
|
export interface WithOkendoStarRatingSnippet {
|
|
4
4
|
okendoStarRatingSnippet?: MetafieldValue | null;
|
|
5
5
|
}
|
|
6
6
|
interface OkendoStarRatingProps {
|
|
7
7
|
productId: string;
|
|
8
8
|
okendoStarRatingSnippet?: MetafieldValue | null;
|
|
9
|
+
/**
|
|
10
|
+
* Only used if the promise returned by `getOkendoProviderData` is given to the OkendoProvider
|
|
11
|
+
* without being awaited; in this case, the placeholder here will be shown until the widget is ready
|
|
12
|
+
*/
|
|
13
|
+
placeholder?: ReactNode;
|
|
9
14
|
}
|
|
10
|
-
export declare const OkendoStarRating:
|
|
15
|
+
export declare const OkendoStarRating: ({ productId, okendoStarRatingSnippet, placeholder }: OkendoStarRatingProps) => string | number | true | Iterable<React.ReactNode> | React.JSX.Element;
|
|
11
16
|
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React, { type Dispatch, type PropsWithChildren, type SetStateAction } from 'react';
|
|
2
|
+
export declare const OkendoContext: React.Context<{
|
|
3
|
+
okendoDataLoaded: boolean;
|
|
4
|
+
setOkendoDataLoaded: Dispatch<SetStateAction<boolean>>;
|
|
5
|
+
} | undefined>;
|
|
6
|
+
export declare const OkendoContextProvider: ({ children }: PropsWithChildren) => React.JSX.Element;
|
|
7
|
+
export declare const useOkendoContext: () => {
|
|
8
|
+
okendoDataLoaded: boolean;
|
|
9
|
+
setOkendoDataLoaded: Dispatch<SetStateAction<boolean>>;
|
|
10
|
+
};
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import type { ReviewsWidgetPlus } from
|
|
2
|
-
import
|
|
1
|
+
import type { ReviewsWidgetPlus } from '@okendo/reviews-widget-plus/dist-utils/ReviewsWidgetPlus';
|
|
2
|
+
import React from 'react';
|
|
3
3
|
export interface OkendoWidgetProps {
|
|
4
4
|
dataAttributes: {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
'data-oke-widget'?: string;
|
|
6
|
+
'data-oke-star-rating'?: string;
|
|
7
|
+
'data-oke-reviews-product-id': string | undefined;
|
|
8
8
|
};
|
|
9
9
|
metafieldContent?: string;
|
|
10
10
|
}
|
|
@@ -13,4 +13,4 @@ declare global {
|
|
|
13
13
|
okeWidgetApi: ReviewsWidgetPlus.WidgetWindowApi;
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
|
-
export declare const OkendoWidget:
|
|
16
|
+
export declare const OkendoWidget: ({ dataAttributes, metafieldContent }: OkendoWidgetProps) => React.JSX.Element;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { WidgetPlus } from '@okendo/reviews-common';
|
|
2
2
|
import { Storefront } from '@shopify/hydrogen';
|
|
3
|
-
import {
|
|
3
|
+
import React, { PropsWithChildren, ReactNode } from 'react';
|
|
4
4
|
|
|
5
5
|
interface OkendoProviderData {
|
|
6
|
-
reviewsHeaderConfig:
|
|
6
|
+
reviewsHeaderConfig: WidgetPlus.ReviewsHeaderConfig;
|
|
7
7
|
cssVariables: string;
|
|
8
8
|
customCss?: string;
|
|
9
9
|
initScriptContents: string;
|
|
@@ -17,14 +17,7 @@ declare const getOkendoProviderData: ({ context, subscriberId, apiDomain, cdnDom
|
|
|
17
17
|
subscriberId: string;
|
|
18
18
|
apiDomain?: string;
|
|
19
19
|
cdnDomain?: string;
|
|
20
|
-
}) => Promise<
|
|
21
|
-
reviewsHeaderConfig: _okendo_reviews_common.WidgetPlus.ReviewsHeaderConfig;
|
|
22
|
-
cssVariables: string;
|
|
23
|
-
customCss: string | undefined;
|
|
24
|
-
initScriptContents: string;
|
|
25
|
-
preRenderStyleTags: string;
|
|
26
|
-
starSymbols: string;
|
|
27
|
-
} | null>;
|
|
20
|
+
}) => Promise<OkendoProviderData | null>;
|
|
28
21
|
interface ReviewProduct {
|
|
29
22
|
productHandle?: string;
|
|
30
23
|
productId: string;
|
|
@@ -36,12 +29,19 @@ interface OkendoProviderProps {
|
|
|
36
29
|
* this is required if Content Security Policy is set up on your store.
|
|
37
30
|
*/
|
|
38
31
|
nonce?: string;
|
|
39
|
-
/**
|
|
40
|
-
|
|
32
|
+
/**
|
|
33
|
+
* Either:
|
|
34
|
+
* - the data returned by the promise returned by `getOkendoProviderData`, using this will allow the
|
|
35
|
+
* Okendo widgets to show instantly on page load, but it will slightly increase the page load time;
|
|
36
|
+
* - the promise returned by `getOkendoProviderData`, using this will make the Okendo widgets
|
|
37
|
+
* show slightly after page load, but it have the advantage of not increasing the page load time.
|
|
38
|
+
*/
|
|
39
|
+
okendoProviderData: Partial<OkendoProviderData> | null | Promise<Partial<OkendoProviderData> | null>;
|
|
41
40
|
/** An optional product URL formatter */
|
|
42
41
|
productUrlFormatter?: (product: ReviewProduct) => string;
|
|
42
|
+
cdnDomain?: string;
|
|
43
43
|
}
|
|
44
|
-
declare const OkendoProvider:
|
|
44
|
+
declare const OkendoProvider: ({ nonce, okendoProviderData, productUrlFormatter, cdnDomain, children, }: OkendoProviderProps & PropsWithChildren) => React.JSX.Element;
|
|
45
45
|
|
|
46
46
|
interface MetafieldValue {
|
|
47
47
|
value: string;
|
|
@@ -53,8 +53,13 @@ interface WithOkendoReviewsSnippet {
|
|
|
53
53
|
interface OkendoReviewsProps {
|
|
54
54
|
productId: string;
|
|
55
55
|
okendoReviewsSnippet?: MetafieldValue | null;
|
|
56
|
+
/**
|
|
57
|
+
* Only used if the promise returned by `getOkendoProviderData` is given to the OkendoProvider
|
|
58
|
+
* without being awaited; in this case, the placeholder here will be shown until the widget is ready
|
|
59
|
+
*/
|
|
60
|
+
placeholder?: JSX.Element;
|
|
56
61
|
}
|
|
57
|
-
declare const OkendoReviews:
|
|
62
|
+
declare const OkendoReviews: ({ productId, okendoReviewsSnippet, placeholder }: OkendoReviewsProps) => React.JSX.Element;
|
|
58
63
|
|
|
59
64
|
interface WithOkendoStarRatingSnippet {
|
|
60
65
|
okendoStarRatingSnippet?: MetafieldValue | null;
|
|
@@ -62,8 +67,13 @@ interface WithOkendoStarRatingSnippet {
|
|
|
62
67
|
interface OkendoStarRatingProps {
|
|
63
68
|
productId: string;
|
|
64
69
|
okendoStarRatingSnippet?: MetafieldValue | null;
|
|
70
|
+
/**
|
|
71
|
+
* Only used if the promise returned by `getOkendoProviderData` is given to the OkendoProvider
|
|
72
|
+
* without being awaited; in this case, the placeholder here will be shown until the widget is ready
|
|
73
|
+
*/
|
|
74
|
+
placeholder?: ReactNode;
|
|
65
75
|
}
|
|
66
|
-
declare const OkendoStarRating:
|
|
76
|
+
declare const OkendoStarRating: ({ productId, okendoStarRatingSnippet, placeholder }: OkendoStarRatingProps) => string | number | true | Iterable<React.ReactNode> | React.JSX.Element;
|
|
67
77
|
|
|
68
78
|
declare const OKENDO_PRODUCT_STAR_RATING_FRAGMENT: "#graphql\n\tfragment OkendoStarRatingSnippet on Product {\n\t\tokendoStarRatingSnippet: metafield(\n\t\t\tnamespace: \"okendo\"\n\t\t\tkey: \"StarRatingSnippet\"\n\t\t) {\n\t\t\tvalue\n\t\t}\n\t}\n";
|
|
69
79
|
declare const OKENDO_PRODUCT_REVIEWS_FRAGMENT: "#graphql\n\tfragment OkendoReviewsSnippet on Product {\n\t\tokendoReviewsSnippet: metafield(\n\t\t\tnamespace: \"okendo\"\n\t\t\tkey: \"ReviewsWidgetSnippet\"\n\t\t) {\n\t\t\tvalue\n\t\t}\n\t}\n";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@okendo/shopify-hydrogen",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"description": "Okendo React components for Shopify Hydrogen 2 (Remix)",
|
|
5
5
|
"author": "Okendo",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE.txt",
|
|
@@ -40,8 +40,9 @@
|
|
|
40
40
|
"typescript": "^5.3.3"
|
|
41
41
|
},
|
|
42
42
|
"peerDependencies": {
|
|
43
|
+
"@remix-run/react": "*",
|
|
43
44
|
"@shopify/hydrogen": "*",
|
|
44
45
|
"@shopify/remix-oxygen": "*",
|
|
45
|
-
"react": "
|
|
46
|
+
"react": "*"
|
|
46
47
|
}
|
|
47
48
|
}
|