@nosto/nosto-react 2.6.0 → 2.7.1
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 +12 -277
- package/dist/index.d.ts +12 -7
- package/dist/index.es.js +85 -80
- package/dist/index.umd.js +1 -1
- package/package.json +8 -5
- package/src/components/NostoPlacement.tsx +7 -3
- package/src/components/NostoProvider.tsx +7 -2
- package/src/context.ts +1 -3
- package/src/hooks/useNostoHome.tsx +1 -1
- package/src/hooks/useRenderCampaigns.tsx +5 -1
- package/src/index.ts +24 -22
- package/src/utils/types.ts +2 -2
package/README.md
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
# Nosto React
|
|
2
2
|
|
|
3
|
+
[](https://github.com/Nosto/nosto-react/releases)
|
|
4
|
+
[](https://github.com/Nosto/nosto-react/actions/workflows/ci.yml)
|
|
5
|
+
[](https://github.com/semantic-release/semantic-release)
|
|
6
|
+
[](https://github.com/Nosto/nosto-react/blob/master/LICENSE)
|
|
7
|
+
|
|
3
8
|
Nosto React is a React component library to make it even easier to implement Nosto.
|
|
4
9
|
|
|
5
10
|
The library provides you everything to get started with personalisation on your React site. It's dead simple, and beginner friendly.
|
|
6
11
|
|
|
7
|
-
|
|
12
|
+
## Why?
|
|
8
13
|
|
|
9
14
|
You should be using Nosto React if you want to:
|
|
10
15
|
|
|
@@ -12,284 +17,14 @@ You should be using Nosto React if you want to:
|
|
|
12
17
|
- Customize your components at will and with ease
|
|
13
18
|
- Follow React principles
|
|
14
19
|
|
|
15
|
-
##
|
|
16
|
-
|
|
17
|
-
Our React component library includes the following features:
|
|
18
|
-
|
|
19
|
-
- Recommendations, including client-side rendering
|
|
20
|
-
- Onsite content personalisation
|
|
21
|
-
- Dynamic bundles
|
|
22
|
-
- Debug toolbar\* (excluding advanced use cases)
|
|
23
|
-
- Pop-ups & personalised emails
|
|
24
|
-
- A/B testing
|
|
25
|
-
- Segmentation and Insights
|
|
26
|
-
- Analytics
|
|
27
|
-
- Search\*\* (when implemented via our code editor)
|
|
28
|
-
|
|
29
|
-
_\*Note: Our React component library currently does not support advanced use cases of the debug toolbar, but we are constantly working to improve our library and provide you with the best possible integration options. We support most use-cases with page tagging and debug workflows._
|
|
30
|
-
|
|
31
|
-
_\*\*Note: The search feature is available when implemented via our code editor._
|
|
32
|
-
|
|
33
|
-
### Additional features
|
|
34
|
-
|
|
35
|
-
- ✓ Lightweight. Almost zero bloat.
|
|
36
|
-
- ✓ Full support for the Facebook Pixel and Google Analytics.
|
|
37
|
-
- ✓ Full support for leveraging overlays.
|
|
38
|
-
- ✓ Full support for the JS API.
|
|
39
|
-
- ✓ Full support for placements.
|
|
40
|
-
|
|
41
|
-
### Building
|
|
42
|
-
|
|
43
|
-
#### Required versions
|
|
44
|
-
|
|
45
|
-
- npm: 10.9.0
|
|
46
|
-
- node: v22.12.0
|
|
47
|
-
|
|
48
|
-
### Installation
|
|
49
|
-
|
|
50
|
-
##### Yarn:
|
|
51
|
-
|
|
52
|
-
yarn add @nosto/nosto-react
|
|
53
|
-
|
|
54
|
-
##### NPM:
|
|
55
|
-
|
|
56
|
-
npm install @nosto/nosto-react
|
|
57
|
-
|
|
58
|
-
### Getting Started
|
|
59
|
-
|
|
60
|
-
##### The root widget
|
|
61
|
-
|
|
62
|
-
There’s one very specific widget in Nosto React and it is the `NostoProvider` one.
|
|
63
|
-
|
|
64
|
-
This widget is what we call the Nosto root widget, which is responsible for adding the actual Nosto script and the JS API stub. This widget wraps all other React Nosto widgets. Here’s how:
|
|
65
|
-
|
|
66
|
-
```jsx
|
|
67
|
-
import { NostoProvider } from "@nosto/nosto-react";
|
|
68
|
-
|
|
69
|
-
<NostoProvider
|
|
70
|
-
account="your-nosto-account-id"
|
|
71
|
-
recommendationComponent={<NostoSlot />}
|
|
72
|
-
>
|
|
73
|
-
<App />
|
|
74
|
-
</NostoProvider>;
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
**Note:** the component also accepts a prop to configure the host `host="connect.nosto.com"`. In advanced use-cases, the need to configure the host may surface.
|
|
78
|
-
|
|
79
|
-
#### Client side rendering for recommendations
|
|
80
|
-
|
|
81
|
-
In order to implement client-side rendering, the <NostoProvider> requires a designated component to render the recommendations provided by Nosto. This component should be capable of processing the JSON response received from our backend. Notice the `recommendationComponent={<NostoSlot />}` prop passed to `<NostoProvider>` above.
|
|
82
|
-
|
|
83
|
-
Learn more [here](https://github.com/Nosto/shopify-hydrogen/blob/main/README.md#client-side-rendering-for-recommendations) and see a [live example](https://github.com/Nosto/shopify-hydrogen-demo) on our demo store.
|
|
84
|
-
|
|
85
|
-
##### Understanding Placements
|
|
86
|
-
|
|
87
|
-
Nosto React has a special component called `NostoPlacement`. The component is a simply a <u>hidden</u> `<div>` placeholder into which Nosto injects recommendations or personalises the content between the tags.
|
|
88
|
-
|
|
89
|
-
We recommend adding as many placements across your views as needed as these are hidden and only populated when a corresponding campaign (targeting that placement) is configured.
|
|
90
|
-
|
|
91
|
-
##### Managing the session
|
|
92
|
-
|
|
93
|
-
Nosto React requires that you pass it the details of current cart contents and the details of the currently logged-in customer, if any, on every route change. This makes it easier to add attribution.
|
|
94
|
-
|
|
95
|
-
The `NostoSession` component makes it very easy to keep the session up to date so long as the cart and the customer are provided.
|
|
96
|
-
|
|
97
|
-
The `cart` prop requires a value that adheres to the type `Cart`, while the `customer` prop requires a value that adheres to the type `Customer`.
|
|
98
|
-
|
|
99
|
-
```jsx
|
|
100
|
-
import { NostoSession } from "@nosto/nosto-react";
|
|
101
|
-
|
|
102
|
-
<>
|
|
103
|
-
<Meta />
|
|
104
|
-
<header>
|
|
105
|
-
<MainMenu />
|
|
106
|
-
<NostoSession cart={currentCart} customer={currentUser} />
|
|
107
|
-
</header>
|
|
108
|
-
<Routes />
|
|
109
|
-
<Footer />
|
|
110
|
-
</>;
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
### Adding Personalisation
|
|
114
|
-
|
|
115
|
-
Nosto React ships with canned components for the different page types. Each component contains all lifecycle methods to dispatch the necessary events.
|
|
116
|
-
|
|
117
|
-
##### Personalising your home page
|
|
118
|
-
|
|
119
|
-
The `NostoHome` component must be used to personalise the home page. The component does not require any props.
|
|
120
|
-
|
|
121
|
-
By default, your account, when created, has <u>four</u> front-page placements named `frontpage-nosto-1`, `frontpage-nosto-2`, `frontpage-nosto-3` and `frontpage-nosto-4`. You may omit these and use any identifier you need. The identifiers used here are simply provided to illustrate the example.
|
|
122
|
-
|
|
123
|
-
The `<NostoHome \>` component needs to be added after the placements. Content and recommendations will be rendered through this component.
|
|
124
|
-
|
|
125
|
-
```jsx
|
|
126
|
-
import { NostoHome, NostoPlacement } from "@nosto/nosto-react";
|
|
127
|
-
|
|
128
|
-
<div className="front-page">
|
|
129
|
-
... ... ...
|
|
130
|
-
<NostoPlacement id="frontpage-nosto-1" />
|
|
131
|
-
<NostoPlacement id="frontpage-nosto-2" />
|
|
132
|
-
<NostoPlacement id="frontpage-nosto-3" />
|
|
133
|
-
<NostoPlacement id="frontpage-nosto-4" />
|
|
134
|
-
<NostoHome />
|
|
135
|
-
</div>;
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
##### Personalising your product pages
|
|
139
|
-
|
|
140
|
-
The `NostoProduct` component must be used to personalise the product page. The component requires that you provide it the identifier of the current product being viewed.
|
|
141
|
-
|
|
142
|
-
By default, your account, when created, has <u>three</u> product-page placements named `productpage-nosto-1`, `productpage-nosto-2` and `productpage-nosto-3`. You may omit these and use any identifier you need. The identifiers used here are simply provided to illustrate the example.
|
|
143
|
-
|
|
144
|
-
The `<NostoProduct \>` component needs to be added after the placements. Content and recommendations will be rendered through this component. Pass in the product ID via the `product` prop to pass this information back to Nosto.
|
|
145
|
-
|
|
146
|
-
```jsx
|
|
147
|
-
import { NostoPlacement, NostoProduct } from "@nosto/nosto-react";
|
|
148
|
-
|
|
149
|
-
<div className="product-page">
|
|
150
|
-
... ... ...
|
|
151
|
-
<NostoPlacement id="productpage-nosto-1" />
|
|
152
|
-
<NostoPlacement id="productpage-nosto-2" />
|
|
153
|
-
<NostoPlacement id="productpage-nosto-3" />
|
|
154
|
-
<NostoProduct product={product.id} />
|
|
155
|
-
</div>;
|
|
156
|
-
```
|
|
157
|
-
|
|
158
|
-
##### Personalising your search result pages
|
|
159
|
-
|
|
160
|
-
You can personalise your search pages by using the `NostoSearch` component. The component requires that you provide it the current search term.
|
|
161
|
-
|
|
162
|
-
By default, your account, when created, has <u>two</u> search-page placements named `searchpage-nosto-1` and `searchpage-nosto-2`. You may omit these and use any identifier you need. The identifiers used here are simply provided to illustrate the example.
|
|
163
|
-
|
|
164
|
-
```jsx
|
|
165
|
-
import { NostoPlacement, NostoSearch } from "@nosto/nosto-react";
|
|
166
|
-
|
|
167
|
-
<div className="search-page">
|
|
168
|
-
... ... ...
|
|
169
|
-
<NostoPlacement id="searchpage-nosto-1" />
|
|
170
|
-
<NostoPlacement id="searchpage-nosto-2" />
|
|
171
|
-
<NostoSearch query={search} />
|
|
172
|
-
</div>;
|
|
173
|
-
```
|
|
174
|
-
|
|
175
|
-
**Note:** Do not encode the search term in any way. It should be provided an unencoded string. A query for "black shoes" must be provided as-is and not as "black+shoes". Doing so will lead to invalid results.
|
|
176
|
-
|
|
177
|
-
##### Personalising your category list pages
|
|
178
|
-
|
|
179
|
-
You can personalise your category and collection pages by using the `NostoCategory` component. The component requires that you provide it the the slash-delimited slug representation of the current category.
|
|
180
|
-
|
|
181
|
-
By default, your account, when created, has <u>two</u> category placements named `categorypage-nosto-1` and `categorypage-nosto-2`. You may omit these and use any identifier you need. The identifiers used here are simply provided to illustrate the example.
|
|
182
|
-
|
|
183
|
-
```jsx
|
|
184
|
-
import { NostoCategory, NostoPlacement } from "@nosto/nosto-react";
|
|
185
|
-
|
|
186
|
-
<div className="category-page">
|
|
187
|
-
... ... ...
|
|
188
|
-
<NostoPlacement id="categorypage-nosto-1" />
|
|
189
|
-
<NostoPlacement id="categorypage-nosto-2" />
|
|
190
|
-
<NostoCategory category={category.name} />
|
|
191
|
-
</div>;
|
|
192
|
-
```
|
|
193
|
-
|
|
194
|
-
**Note:** Be sure to pass in the correct category representation. If the category being viewed is Mens >> Jackets, you must provide the name as `/Mens/Jackets` . You must ensure that the category path provided here matches that of the categories tagged in your products.
|
|
195
|
-
|
|
196
|
-
##### Personalising your cart checkout pages
|
|
197
|
-
|
|
198
|
-
You can personalise your cart and checkout pages by using the `NostoCheckout` component. The component does not require any props.
|
|
199
|
-
|
|
200
|
-
By default, your account, when created, has <u>two</u> cart-page placements named `categorypage-nosto-1` and `categorypage-nosto-2`. You may omit these and use any identifier you need. The identifiers used here are simply provided to illustrate the example.
|
|
201
|
-
|
|
202
|
-
```jsx
|
|
203
|
-
import { NostoCheckout, NostoPlacement } from "@nosto/nosto-react";
|
|
204
|
-
|
|
205
|
-
<div className="checkout-page">
|
|
206
|
-
... ... ...
|
|
207
|
-
<NostoPlacement id="checkout-nosto-1" />
|
|
208
|
-
<NostoPlacement id="checkout-nosto-2" />
|
|
209
|
-
<NostoCheckout />
|
|
210
|
-
</div>;
|
|
211
|
-
```
|
|
212
|
-
|
|
213
|
-
##### Personalising your 404 error pages
|
|
214
|
-
|
|
215
|
-
You can personalise not found pages by using the `Nosto404` component. The component does not require any props.
|
|
216
|
-
|
|
217
|
-
By default, your account, when created, has three 404-page placements named `notfound-nosto-1`, `notfound-nosto-2` and `notfound-nosto-2`. You may omit these and use any identifier you need. The identifiers used here are simply provided to illustrate the example.
|
|
218
|
-
|
|
219
|
-
```jsx
|
|
220
|
-
import { Nosto404, NostoPlacement } from "@nosto/nosto-react";
|
|
221
|
-
|
|
222
|
-
<div className="notfound-page">
|
|
223
|
-
... ... ...
|
|
224
|
-
<NostoPlacement id="notfound-nosto-1" />
|
|
225
|
-
<NostoPlacement id="notfound-nosto-2" />
|
|
226
|
-
<NostoPlacement id="notfound-nosto-3" />
|
|
227
|
-
<Nosto404 />
|
|
228
|
-
</div>;
|
|
229
|
-
```
|
|
230
|
-
|
|
231
|
-
##### Personalising your miscellaneous pages
|
|
232
|
-
|
|
233
|
-
You can personalise your miscellaneous pages by using the `NostoOther` component. The component does not require any props.
|
|
234
|
-
|
|
235
|
-
By default, your account, when created, has two other-page placements named `other-nosto-1` and `other-nosto-2`. You may omit these and use any identifier you need. The identifiers used here are simply provided to illustrate the example.
|
|
236
|
-
|
|
237
|
-
```jsx
|
|
238
|
-
import { NostoOther, NostoPlacement } from "@nosto/nosto-react";
|
|
239
|
-
|
|
240
|
-
<div className="other-page">
|
|
241
|
-
... ... ...
|
|
242
|
-
<NostoPlacement id="other-nosto-1" />
|
|
243
|
-
<NostoPlacement id="other-nosto-2" />
|
|
244
|
-
<NostoOther />
|
|
245
|
-
</div>;
|
|
246
|
-
```
|
|
247
|
-
|
|
248
|
-
##### Personalising your order confirmation page
|
|
249
|
-
|
|
250
|
-
You can personalise your order-confirmation/thank-you page by using the `NostoOrder` component. The component requires that you provide it with the details of the order.
|
|
251
|
-
|
|
252
|
-
By default, your account, when created, has one other-page placement named `thankyou-nosto-1`. You may omit this and use any identifier you need. The identifier used here is simply provided to illustrate the example.
|
|
253
|
-
|
|
254
|
-
```jsx
|
|
255
|
-
import {
|
|
256
|
-
NostoOrder,
|
|
257
|
-
NostoPlacement,
|
|
258
|
-
} from "@nosto/nosto-react";
|
|
259
|
-
|
|
260
|
-
<div className="thankyou-page">
|
|
261
|
-
... ... ...
|
|
262
|
-
<NostoPlacement id="thankyou-nosto-1" />
|
|
263
|
-
<NostoOrder order={ order } />
|
|
264
|
-
</div>;
|
|
265
|
-
```
|
|
266
|
-
|
|
267
|
-
### Hook alternatives
|
|
268
|
-
|
|
269
|
-
For all the page type specific components hooks are also provided with the same props
|
|
270
|
-
|
|
271
|
-
- useNosto404
|
|
272
|
-
- useNostoOther
|
|
273
|
-
- useNostoCheckout
|
|
274
|
-
- useNostoProduct
|
|
275
|
-
- useNostoCategory
|
|
276
|
-
- useNostoSearch
|
|
277
|
-
- useNostOrder
|
|
278
|
-
- useNostoHome
|
|
279
|
-
|
|
280
|
-
### Detailed technical documentation
|
|
281
|
-
Find our latest technical specs and documentation hosted [here](https://nosto.github.io/nosto-react).
|
|
282
|
-
|
|
283
|
-
### Feedback
|
|
284
|
-
|
|
285
|
-
If you've found a feature missing or you would like to report an issue, simply [open up an issue](https://github.com/nosto/nosto-react/issues/new) and let us know.
|
|
20
|
+
## Installation
|
|
286
21
|
|
|
287
|
-
|
|
22
|
+
yarn add @nosto/nosto-react
|
|
288
23
|
|
|
289
|
-
|
|
24
|
+
npm install @nosto/nosto-react
|
|
290
25
|
|
|
291
|
-
|
|
26
|
+
## Documentation
|
|
292
27
|
|
|
293
|
-
|
|
28
|
+
Read [Nosto Techdocs](https://docs.nosto.com/techdocs/apis/frontend/oss/react-support) for more information on how to use the library.
|
|
294
29
|
|
|
295
|
-
|
|
30
|
+
[Library TypeDoc page](https://nosto.github.io/nosto-react/) includes detailed library helpers documentation and examples.
|
package/dist/index.d.ts
CHANGED
|
@@ -9,8 +9,6 @@ import { ReactElement } from 'react';
|
|
|
9
9
|
import { ReactNode } from 'react';
|
|
10
10
|
import { RenderMode } from '@nosto/nosto-js/client';
|
|
11
11
|
|
|
12
|
-
declare type AnyFunction = (...args: unknown[]) => unknown;
|
|
13
|
-
|
|
14
12
|
export { Cart }
|
|
15
13
|
|
|
16
14
|
export { Customer }
|
|
@@ -116,7 +114,7 @@ export declare interface NostoContextType {
|
|
|
116
114
|
account: string;
|
|
117
115
|
clientScriptLoaded: boolean;
|
|
118
116
|
currentVariation?: string;
|
|
119
|
-
renderFunction?:
|
|
117
|
+
renderFunction?: (...args: unknown[]) => unknown;
|
|
120
118
|
responseMode: RenderMode;
|
|
121
119
|
recommendationComponent?: RecommendationComponent;
|
|
122
120
|
}
|
|
@@ -223,7 +221,7 @@ export declare type NostoOtherProps = {
|
|
|
223
221
|
*
|
|
224
222
|
* @group Components
|
|
225
223
|
*/
|
|
226
|
-
export declare function NostoPlacement({ id, pageType }: NostoPlacementProps): JSX.Element;
|
|
224
|
+
export declare function NostoPlacement({ id, pageType, children }: NostoPlacementProps): JSX.Element;
|
|
227
225
|
|
|
228
226
|
/**
|
|
229
227
|
* @group Components
|
|
@@ -231,6 +229,7 @@ export declare function NostoPlacement({ id, pageType }: NostoPlacementProps): J
|
|
|
231
229
|
export declare type NostoPlacementProps = {
|
|
232
230
|
id: string;
|
|
233
231
|
pageType?: string;
|
|
232
|
+
children?: React.ReactNode;
|
|
234
233
|
};
|
|
235
234
|
|
|
236
235
|
/**
|
|
@@ -320,6 +319,10 @@ export declare interface NostoProviderProps {
|
|
|
320
319
|
* Recommendation component which holds nostoRecommendation object
|
|
321
320
|
*/
|
|
322
321
|
recommendationComponent?: RecommendationComponent;
|
|
322
|
+
/**
|
|
323
|
+
* Recommendation render mode. See {@link https://nosto.github.io/nosto-js/types/client.RenderMode.html}
|
|
324
|
+
*/
|
|
325
|
+
renderMode?: RenderMode;
|
|
323
326
|
/**
|
|
324
327
|
* Enables Shopify markets with language and market id
|
|
325
328
|
*/
|
|
@@ -407,10 +410,12 @@ export declare interface Recommendation {
|
|
|
407
410
|
params: unknown;
|
|
408
411
|
}
|
|
409
412
|
|
|
410
|
-
declare type RecommendationComponent = ReactElement<{
|
|
413
|
+
export declare type RecommendationComponent = ReactElement<{
|
|
411
414
|
nostoRecommendation: Recommendation;
|
|
412
415
|
}>;
|
|
413
416
|
|
|
417
|
+
export { RenderMode }
|
|
418
|
+
|
|
414
419
|
/**
|
|
415
420
|
* @group Types
|
|
416
421
|
*/
|
|
@@ -425,9 +430,9 @@ export declare type ScriptLoadOptions = {
|
|
|
425
430
|
attributes?: Record<string, string>;
|
|
426
431
|
};
|
|
427
432
|
|
|
428
|
-
declare type SnakeToCamelCase<S extends string> = S extends `${infer T}_${infer U}` ? `${T}${Capitalize<SnakeToCamelCase<U>>}` : S;
|
|
433
|
+
export declare type SnakeToCamelCase<S extends string> = S extends `${infer T}_${infer U}` ? `${T}${Capitalize<SnakeToCamelCase<U>>}` : S;
|
|
429
434
|
|
|
430
|
-
declare type ToCamelCase<T> = T extends (infer U)[] ? ToCamelCase<U>[] : T extends Date ? T : T extends object ? {
|
|
435
|
+
export declare type ToCamelCase<T> = T extends (infer U)[] ? ToCamelCase<U>[] : T extends Date ? T : T extends object ? {
|
|
431
436
|
[K in keyof T as SnakeToCamelCase<K & string>]: ToCamelCase<T[K]>;
|
|
432
437
|
} : T;
|
|
433
438
|
|
package/dist/index.es.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { createContext as k, useContext as x, useEffect as S, useRef as h, useMemo as I, cloneElement as _, useState as
|
|
1
|
+
import { createContext as k, useContext as x, useEffect as S, useRef as h, useMemo as I, cloneElement as _, useState as M, isValidElement as T } from "react";
|
|
2
2
|
import { jsx as v } from "react/jsx-runtime";
|
|
3
|
-
import { createRoot as
|
|
3
|
+
import { createRoot as D } from "react-dom/client";
|
|
4
4
|
const O = k({
|
|
5
5
|
account: "",
|
|
6
6
|
currentVariation: "",
|
|
@@ -10,13 +10,13 @@ const O = k({
|
|
|
10
10
|
function P() {
|
|
11
11
|
return x(O);
|
|
12
12
|
}
|
|
13
|
-
const
|
|
13
|
+
const g = (e) => String(e) === "[object Object]";
|
|
14
14
|
function L(e) {
|
|
15
|
-
if (!
|
|
15
|
+
if (!g(e)) return !1;
|
|
16
16
|
const t = e.constructor;
|
|
17
17
|
if (t === void 0) return !0;
|
|
18
18
|
const n = t.prototype;
|
|
19
|
-
return !(!
|
|
19
|
+
return !(!g(n) || !n.hasOwnProperty("isPrototypeOf"));
|
|
20
20
|
}
|
|
21
21
|
function y(e, t) {
|
|
22
22
|
if (e === t)
|
|
@@ -31,66 +31,69 @@ function y(e, t) {
|
|
|
31
31
|
}
|
|
32
32
|
return !1;
|
|
33
33
|
}
|
|
34
|
-
function
|
|
34
|
+
function j(e, t) {
|
|
35
35
|
return S(e, V(t));
|
|
36
36
|
}
|
|
37
37
|
function V(e) {
|
|
38
38
|
const t = h(e), n = h(0);
|
|
39
39
|
return y(e, t.current) || (t.current = e, n.current += 1), I(() => t.current, [n.current]);
|
|
40
40
|
}
|
|
41
|
-
function
|
|
41
|
+
function N() {
|
|
42
42
|
window.nostojs = window.nostojs ?? function(e) {
|
|
43
43
|
(window.nostojs.q = window.nostojs.q ?? []).push(e);
|
|
44
44
|
};
|
|
45
45
|
}
|
|
46
|
-
function
|
|
46
|
+
async function m(e) {
|
|
47
|
+
return window.nostojs(e);
|
|
48
|
+
}
|
|
49
|
+
typeof window < "u" && (N(), m((e) => {
|
|
50
|
+
e.internal.getSettings();
|
|
51
|
+
}));
|
|
52
|
+
function q() {
|
|
47
53
|
return typeof window.nosto < "u";
|
|
48
54
|
}
|
|
49
|
-
const
|
|
55
|
+
const H = {
|
|
50
56
|
production: "https://connect.nosto.com/",
|
|
51
57
|
staging: "https://connect.staging.nosto.com/",
|
|
52
58
|
local: "https://connect.nosto.com/"
|
|
53
59
|
};
|
|
54
60
|
function R(e) {
|
|
55
|
-
return
|
|
61
|
+
return H[e ?? "production"];
|
|
56
62
|
}
|
|
57
63
|
function F({ merchantId: e, env: t, options: n, shopifyInternational: o, scriptLoader: r }) {
|
|
58
|
-
var c,
|
|
59
|
-
const s = document.querySelector("script[nosto-language], script[nosto-market-id]"),
|
|
64
|
+
var c, i;
|
|
65
|
+
const s = document.querySelector("script[nosto-language], script[nosto-market-id]"), a = String((o == null ? void 0 : o.marketId) || ""), u = (o == null ? void 0 : o.language) || "", C = (s == null ? void 0 : s.getAttribute("nosto-language")) !== u || (s == null ? void 0 : s.getAttribute("nosto-market-id")) !== a;
|
|
60
66
|
if (!s || C) {
|
|
61
|
-
const
|
|
62
|
-
(c = s == null ? void 0 : s.parentNode) == null || c.removeChild(s), (
|
|
67
|
+
const f = document.querySelector("#nosto-sandbox");
|
|
68
|
+
(c = s == null ? void 0 : s.parentNode) == null || c.removeChild(s), (i = f == null ? void 0 : f.parentNode) == null || i.removeChild(f);
|
|
63
69
|
const p = new URL("/script/shopify/market/nosto.js", R(t));
|
|
64
|
-
p.searchParams.append("merchant", e), p.searchParams.append("market",
|
|
65
|
-
const
|
|
70
|
+
p.searchParams.append("merchant", e), p.searchParams.append("market", a), p.searchParams.append("locale", u.toLowerCase());
|
|
71
|
+
const b = {
|
|
66
72
|
...n == null ? void 0 : n.attributes,
|
|
67
|
-
"nosto-language":
|
|
68
|
-
"nosto-market-id":
|
|
73
|
+
"nosto-language": u,
|
|
74
|
+
"nosto-market-id": a
|
|
69
75
|
};
|
|
70
|
-
return (r ?? E)(p.toString(), { ...n, attributes:
|
|
76
|
+
return (r ?? E)(p.toString(), { ...n, attributes: b });
|
|
71
77
|
}
|
|
72
78
|
return Promise.resolve();
|
|
73
79
|
}
|
|
74
80
|
function z(e) {
|
|
75
81
|
if (e.shopifyInternational)
|
|
76
82
|
return F(e);
|
|
77
|
-
const { merchantId: t, env: n, options: o, scriptLoader: r } = e, c = r ?? E,
|
|
78
|
-
return c(
|
|
83
|
+
const { merchantId: t, env: n, options: o, scriptLoader: r } = e, c = r ?? E, i = new URL(`/include/${t}`, R(n));
|
|
84
|
+
return c(i.toString(), o);
|
|
79
85
|
}
|
|
80
86
|
function E(e, t) {
|
|
81
87
|
return new Promise((n, o) => {
|
|
82
88
|
const r = document.createElement("script");
|
|
83
|
-
r.src = e, r.async = !0, r.type = "text/javascript", r.onload = () => n(), r.onerror = () => o(), Object.entries((t == null ? void 0 : t.attributes) ?? {}).forEach(([c,
|
|
89
|
+
r.src = e, r.async = !0, r.type = "text/javascript", r.onload = () => n(), r.onerror = () => o(), Object.entries((t == null ? void 0 : t.attributes) ?? {}).forEach(([c, i]) => r.setAttribute(c, i)), (t == null ? void 0 : t.position) === "head" ? document.head.appendChild(r) : document.body.appendChild(r);
|
|
84
90
|
});
|
|
85
91
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
}
|
|
89
|
-
typeof window < "u" && j();
|
|
90
|
-
function u(e, t, n) {
|
|
92
|
+
typeof window < "u" && N();
|
|
93
|
+
function d(e, t, n) {
|
|
91
94
|
const { clientScriptLoaded: o } = P();
|
|
92
|
-
(n != null && n.deep ?
|
|
93
|
-
o &&
|
|
95
|
+
(n != null && n.deep ? j : S)(() => {
|
|
96
|
+
o && m(e);
|
|
94
97
|
}, [o, ...t ?? []]);
|
|
95
98
|
}
|
|
96
99
|
function U(e) {
|
|
@@ -99,30 +102,32 @@ function U(e) {
|
|
|
99
102
|
nostoRecommendation: e.nostoRecommendation
|
|
100
103
|
});
|
|
101
104
|
}
|
|
102
|
-
function
|
|
103
|
-
|
|
105
|
+
function A(e) {
|
|
106
|
+
m((t) => t.placements.injectCampaigns(e));
|
|
104
107
|
}
|
|
105
108
|
function $(e) {
|
|
106
109
|
if (!window.nostojs)
|
|
107
110
|
throw new Error("Nosto has not yet been initialized");
|
|
108
|
-
|
|
111
|
+
A(e.recommendations);
|
|
109
112
|
}
|
|
110
|
-
function
|
|
113
|
+
function l() {
|
|
111
114
|
const { responseMode: e, recommendationComponent: t } = P(), n = h({});
|
|
112
115
|
if (e == "HTML")
|
|
113
116
|
return { renderCampaigns: $ };
|
|
117
|
+
if (!t)
|
|
118
|
+
throw new Error("recommendationComponent is required for client-side rendering using hook");
|
|
114
119
|
function o(r) {
|
|
115
|
-
var
|
|
116
|
-
|
|
120
|
+
var i, s;
|
|
121
|
+
A(((i = r.campaigns) == null ? void 0 : i.content) ?? {});
|
|
117
122
|
const c = ((s = r.campaigns) == null ? void 0 : s.recommendations) ?? {};
|
|
118
|
-
for (const
|
|
119
|
-
const
|
|
120
|
-
|
|
123
|
+
for (const a in c) {
|
|
124
|
+
const u = c[a], C = "#" + a, f = document.querySelector(C);
|
|
125
|
+
f && (n.current[a] || (n.current[a] = D(f)), n.current[a].render(
|
|
121
126
|
/* @__PURE__ */ v(
|
|
122
127
|
U,
|
|
123
128
|
{
|
|
124
129
|
recommendationComponent: t,
|
|
125
|
-
nostoRecommendation:
|
|
130
|
+
nostoRecommendation: u
|
|
126
131
|
}
|
|
127
132
|
)
|
|
128
133
|
));
|
|
@@ -131,18 +136,18 @@ function d() {
|
|
|
131
136
|
return { renderCampaigns: o };
|
|
132
137
|
}
|
|
133
138
|
function G(e) {
|
|
134
|
-
const { renderCampaigns: t } =
|
|
135
|
-
|
|
139
|
+
const { renderCampaigns: t } = l();
|
|
140
|
+
d(async (n) => {
|
|
136
141
|
const o = await n.defaultSession().viewNotFound().setPlacements((e == null ? void 0 : e.placements) || n.placements.getPlacements()).load();
|
|
137
142
|
t(o);
|
|
138
143
|
});
|
|
139
144
|
}
|
|
140
|
-
function
|
|
145
|
+
function ae(e) {
|
|
141
146
|
return G(e), null;
|
|
142
147
|
}
|
|
143
148
|
function J({ category: e, placements: t }) {
|
|
144
|
-
const { renderCampaigns: n } =
|
|
145
|
-
|
|
149
|
+
const { renderCampaigns: n } = l();
|
|
150
|
+
d(
|
|
146
151
|
async (o) => {
|
|
147
152
|
const r = await o.defaultSession().viewCategory(e).setPlacements(t || o.placements.getPlacements()).load();
|
|
148
153
|
n(r);
|
|
@@ -154,8 +159,8 @@ function ue(e) {
|
|
|
154
159
|
return J(e), null;
|
|
155
160
|
}
|
|
156
161
|
function W(e) {
|
|
157
|
-
const { renderCampaigns: t } =
|
|
158
|
-
|
|
162
|
+
const { renderCampaigns: t } = l();
|
|
163
|
+
d(async (n) => {
|
|
159
164
|
const o = await n.defaultSession().viewCart().setPlacements((e == null ? void 0 : e.placements) || n.placements.getPlacements()).load();
|
|
160
165
|
t(o);
|
|
161
166
|
});
|
|
@@ -164,8 +169,8 @@ function de(e) {
|
|
|
164
169
|
return W(e), null;
|
|
165
170
|
}
|
|
166
171
|
function Z(e) {
|
|
167
|
-
const { renderCampaigns: t } =
|
|
168
|
-
|
|
172
|
+
const { renderCampaigns: t } = l();
|
|
173
|
+
d(async (n) => {
|
|
169
174
|
const o = await n.defaultSession().viewFrontPage().setPlacements((e == null ? void 0 : e.placements) || n.placements.getPlacements()).load();
|
|
170
175
|
t(o);
|
|
171
176
|
});
|
|
@@ -186,8 +191,8 @@ function K(e) {
|
|
|
186
191
|
return Object.prototype.toString.call(e) === "[object RegExp]";
|
|
187
192
|
}
|
|
188
193
|
function Q({ order: e, placements: t }) {
|
|
189
|
-
const { renderCampaigns: n } =
|
|
190
|
-
|
|
194
|
+
const { renderCampaigns: n } = l();
|
|
195
|
+
d(
|
|
191
196
|
async (o) => {
|
|
192
197
|
const r = await o.defaultSession().addOrder(w(e)).setPlacements(t || o.placements.getPlacements()).load();
|
|
193
198
|
n(r);
|
|
@@ -200,8 +205,8 @@ function me(e) {
|
|
|
200
205
|
return Q(e), null;
|
|
201
206
|
}
|
|
202
207
|
function X(e) {
|
|
203
|
-
const { renderCampaigns: t } =
|
|
204
|
-
|
|
208
|
+
const { renderCampaigns: t } = l();
|
|
209
|
+
d(async (n) => {
|
|
205
210
|
const o = await n.defaultSession().viewOther().setPlacements((e == null ? void 0 : e.placements) || n.placements.getPlacements()).load();
|
|
206
211
|
t(o);
|
|
207
212
|
});
|
|
@@ -209,20 +214,20 @@ function X(e) {
|
|
|
209
214
|
function fe(e) {
|
|
210
215
|
return X(e), null;
|
|
211
216
|
}
|
|
212
|
-
function pe({ id: e, pageType: t }) {
|
|
213
|
-
return /* @__PURE__ */ v("div", { className: "nosto_element", id: e }, e + (t || ""));
|
|
217
|
+
function pe({ id: e, pageType: t, children: n }) {
|
|
218
|
+
return /* @__PURE__ */ v("div", { className: "nosto_element", id: e, children: n }, e + (t || ""));
|
|
214
219
|
}
|
|
215
220
|
function Y({ product: e, tagging: t, placements: n, reference: o }) {
|
|
216
|
-
const { renderCampaigns: r } =
|
|
221
|
+
const { renderCampaigns: r } = l();
|
|
217
222
|
if (t && !t.product_id)
|
|
218
223
|
throw new Error("The product object must contain a product_id property");
|
|
219
224
|
const c = (t == null ? void 0 : t.product_id) ?? e;
|
|
220
|
-
|
|
221
|
-
async (
|
|
222
|
-
const s =
|
|
225
|
+
d(
|
|
226
|
+
async (i) => {
|
|
227
|
+
const s = i.defaultSession().viewProduct(t ?? e).setPlacements(n || i.placements.getPlacements());
|
|
223
228
|
o && s.setRef(c, o);
|
|
224
|
-
const
|
|
225
|
-
r(
|
|
229
|
+
const a = await s.load();
|
|
230
|
+
r(a);
|
|
226
231
|
},
|
|
227
232
|
[c, t == null ? void 0 : t.selected_sku_id]
|
|
228
233
|
);
|
|
@@ -233,7 +238,7 @@ function we(e) {
|
|
|
233
238
|
function ee(e, t) {
|
|
234
239
|
return new Promise((n, o) => {
|
|
235
240
|
const r = document.createElement("script");
|
|
236
|
-
r.type = "text/javascript", r.src = e, r.async = !0, r.onload = () => n(), r.onerror = () => o(), Object.entries((t == null ? void 0 : t.attributes) ?? {}).forEach(([c,
|
|
241
|
+
r.type = "text/javascript", r.src = e, r.async = !0, r.onload = () => n(), r.onerror = () => o(), Object.entries((t == null ? void 0 : t.attributes) ?? {}).forEach(([c, i]) => r.setAttribute(c, i)), (t == null ? void 0 : t.position) === "head" ? document.head.appendChild(r) : document.body.appendChild(r);
|
|
237
242
|
});
|
|
238
243
|
}
|
|
239
244
|
const te = { "nosto-client-script": "" };
|
|
@@ -243,16 +248,16 @@ function ne(e) {
|
|
|
243
248
|
account: n,
|
|
244
249
|
shopifyMarkets: o,
|
|
245
250
|
loadScript: r = !0
|
|
246
|
-
} = e, [c,
|
|
251
|
+
} = e, [c, i] = M(!1);
|
|
247
252
|
return S(() => {
|
|
248
253
|
function s() {
|
|
249
|
-
|
|
254
|
+
i(!0);
|
|
250
255
|
}
|
|
251
|
-
if (
|
|
252
|
-
|
|
256
|
+
if (N(), m((u) => u.setAutoLoad(!1)), !r) {
|
|
257
|
+
m(s);
|
|
253
258
|
return;
|
|
254
259
|
}
|
|
255
|
-
async function
|
|
260
|
+
async function a() {
|
|
256
261
|
await z({
|
|
257
262
|
merchantId: n,
|
|
258
263
|
shopifyInternational: o,
|
|
@@ -262,26 +267,26 @@ function ne(e) {
|
|
|
262
267
|
scriptLoader: t
|
|
263
268
|
}), s();
|
|
264
269
|
}
|
|
265
|
-
(!
|
|
270
|
+
(!q() || o) && a();
|
|
266
271
|
}, [o == null ? void 0 : o.marketId, o == null ? void 0 : o.language]), { clientScriptLoaded: c };
|
|
267
272
|
}
|
|
268
273
|
function Ce(e) {
|
|
269
|
-
const { account: t, multiCurrency: n = !1, children: o, recommendationComponent: r } = e,
|
|
270
|
-
if (r && !
|
|
274
|
+
const { account: t, multiCurrency: n = !1, children: o, recommendationComponent: r, renderMode: c } = e, i = n ? e.currentVariation : "";
|
|
275
|
+
if (r && !T(r))
|
|
271
276
|
throw new Error(
|
|
272
277
|
"The recommendationComponent prop must be a valid React element. Please provide a valid React element."
|
|
273
278
|
);
|
|
274
|
-
const
|
|
275
|
-
return
|
|
276
|
-
|
|
279
|
+
const s = c || (r ? "JSON_ORIGINAL" : "HTML"), { clientScriptLoaded: a } = ne(e);
|
|
280
|
+
return a && m((u) => {
|
|
281
|
+
u.defaultSession().setVariation(i).setResponseMode(s);
|
|
277
282
|
}), /* @__PURE__ */ v(
|
|
278
283
|
O.Provider,
|
|
279
284
|
{
|
|
280
285
|
value: {
|
|
281
286
|
account: t,
|
|
282
|
-
clientScriptLoaded:
|
|
283
|
-
currentVariation:
|
|
284
|
-
responseMode:
|
|
287
|
+
clientScriptLoaded: a,
|
|
288
|
+
currentVariation: i,
|
|
289
|
+
responseMode: s,
|
|
285
290
|
recommendationComponent: r
|
|
286
291
|
},
|
|
287
292
|
children: o
|
|
@@ -289,8 +294,8 @@ function Ce(e) {
|
|
|
289
294
|
);
|
|
290
295
|
}
|
|
291
296
|
function oe({ query: e, placements: t }) {
|
|
292
|
-
const { renderCampaigns: n } =
|
|
293
|
-
|
|
297
|
+
const { renderCampaigns: n } = l();
|
|
298
|
+
d(
|
|
294
299
|
async (o) => {
|
|
295
300
|
const r = await o.defaultSession().viewSearch(e).setPlacements(t || o.placements.getPlacements()).load();
|
|
296
301
|
n(r);
|
|
@@ -303,9 +308,9 @@ function he(e) {
|
|
|
303
308
|
}
|
|
304
309
|
function re({ cart: e, customer: t } = {}) {
|
|
305
310
|
const { clientScriptLoaded: n } = P();
|
|
306
|
-
|
|
311
|
+
j(() => {
|
|
307
312
|
const o = e ? w(e) : void 0, r = t ? w(t) : void 0;
|
|
308
|
-
n &&
|
|
313
|
+
n && m((c) => {
|
|
309
314
|
c.defaultSession().setCart(o).setCustomer(r).viewOther().load({ skipPageViews: !0 });
|
|
310
315
|
});
|
|
311
316
|
}, [n, e, t]);
|
|
@@ -314,7 +319,7 @@ function ye(e) {
|
|
|
314
319
|
return re(e), null;
|
|
315
320
|
}
|
|
316
321
|
export {
|
|
317
|
-
|
|
322
|
+
ae as Nosto404,
|
|
318
323
|
ue as NostoCategory,
|
|
319
324
|
de as NostoCheckout,
|
|
320
325
|
O as NostoContext,
|
package/dist/index.umd.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
(function(
|
|
1
|
+
(function(s,u){typeof exports=="object"&&typeof module<"u"?u(exports,require("react"),require("react/jsx-runtime"),require("react-dom/client")):typeof define=="function"&&define.amd?define(["exports","react","react/jsx-runtime","react-dom/client"],u):(s=typeof globalThis<"u"?globalThis:s||self,u(s["@nosto/nosto-react"]={},s.React,s["react/jsx-runtime"],s.client))})(this,function(s,u,y,V){"use strict";const S=u.createContext({account:"",currentVariation:"",responseMode:"HTML",clientScriptLoaded:!1});function N(){return u.useContext(S)}const g=e=>String(e)==="[object Object]";function j(e){if(!g(e))return!1;const t=e.constructor;if(t===void 0)return!0;const n=t.prototype;return!(!g(n)||!n.hasOwnProperty("isPrototypeOf"))}function P(e,t){if(e===t)return!0;if(e instanceof Date&&t instanceof Date)return e.getTime()===t.getTime();if(e instanceof Array&&t instanceof Array)return e.length!==t.length?!1:e.every((n,o)=>P(n,t[o]));if(j(e)&&j(t)){const n=Object.entries(e);return n.length!==Object.keys(t).length?!1:n.every(([o,r])=>P(r,t[o]))}return!1}function L(e,t){return u.useEffect(e,F(t))}function F(e){const t=u.useRef(e),n=u.useRef(0);return P(e,t.current)||(t.current=e,n.current+=1),u.useMemo(()=>t.current,[n.current])}function v(){window.nostojs=window.nostojs??function(e){(window.nostojs.q=window.nostojs.q??[]).push(e)}}async function p(e){return window.nostojs(e)}typeof window<"u"&&(v(),p(e=>{e.internal.getSettings()}));function z(){return typeof window.nosto<"u"}const U={production:"https://connect.nosto.com/",staging:"https://connect.staging.nosto.com/",local:"https://connect.nosto.com/"};function R(e){return U[e??"production"]}function $({merchantId:e,env:t,options:n,shopifyInternational:o,scriptLoader:r}){var c,a;const i=document.querySelector("script[nosto-language], script[nosto-market-id]"),d=String((o==null?void 0:o.marketId)||""),l=(o==null?void 0:o.language)||"",O=(i==null?void 0:i.getAttribute("nosto-language"))!==l||(i==null?void 0:i.getAttribute("nosto-market-id"))!==d;if(!i||O){const h=document.querySelector("#nosto-sandbox");(c=i==null?void 0:i.parentNode)==null||c.removeChild(i),(a=h==null?void 0:h.parentNode)==null||a.removeChild(h);const w=new URL("/script/shopify/market/nosto.js",R(t));w.searchParams.append("merchant",e),w.searchParams.append("market",d),w.searchParams.append("locale",l.toLowerCase());const ue={...n==null?void 0:n.attributes,"nosto-language":l,"nosto-market-id":d};return(r??E)(w.toString(),{...n,attributes:ue})}return Promise.resolve()}function G(e){if(e.shopifyInternational)return $(e);const{merchantId:t,env:n,options:o,scriptLoader:r}=e,c=r??E,a=new URL(`/include/${t}`,R(n));return c(a.toString(),o)}function E(e,t){return new Promise((n,o)=>{const r=document.createElement("script");r.src=e,r.async=!0,r.type="text/javascript",r.onload=()=>n(),r.onerror=()=>o(),Object.entries((t==null?void 0:t.attributes)??{}).forEach(([c,a])=>r.setAttribute(c,a)),(t==null?void 0:t.position)==="head"?document.head.appendChild(r):document.body.appendChild(r)})}typeof window<"u"&&v();function m(e,t,n){const{clientScriptLoaded:o}=N();(n!=null&&n.deep?L:u.useEffect)(()=>{o&&p(e)},[o,...t??[]])}function J(e){return u.cloneElement(e.recommendationComponent,{nostoRecommendation:e.nostoRecommendation})}function k(e){p(t=>t.placements.injectCampaigns(e))}function W(e){if(!window.nostojs)throw new Error("Nosto has not yet been initialized");k(e.recommendations)}function f(){const{responseMode:e,recommendationComponent:t}=N(),n=u.useRef({});if(e=="HTML")return{renderCampaigns:W};if(!t)throw new Error("recommendationComponent is required for client-side rendering using hook");function o(r){var a,i;k(((a=r.campaigns)==null?void 0:a.content)??{});const c=((i=r.campaigns)==null?void 0:i.recommendations)??{};for(const d in c){const l=c[d],O="#"+d,h=document.querySelector(O);h&&(n.current[d]||(n.current[d]=V.createRoot(h)),n.current[d].render(y.jsx(J,{recommendationComponent:t,nostoRecommendation:l})))}}return{renderCampaigns:o}}function A(e){const{renderCampaigns:t}=f();m(async n=>{const o=await n.defaultSession().viewNotFound().setPlacements((e==null?void 0:e.placements)||n.placements.getPlacements()).load();t(o)})}function Z(e){return A(e),null}function b({category:e,placements:t}){const{renderCampaigns:n}=f();m(async o=>{const r=await o.defaultSession().viewCategory(e).setPlacements(t||o.placements.getPlacements()).load();n(r)},[e])}function B(e){return b(e),null}function I(e){const{renderCampaigns:t}=f();m(async n=>{const o=await n.defaultSession().viewCart().setPlacements((e==null?void 0:e.placements)||n.placements.getPlacements()).load();t(o)})}function K(e){return I(e),null}function T(e){const{renderCampaigns:t}=f();m(async n=>{const o=await n.defaultSession().viewFrontPage().setPlacements((e==null?void 0:e.placements)||n.placements.getPlacements()).load();t(o)})}function Q(e){return T(e),null}function C(e){return!e||typeof e!="object"||X(e)||Y(e)?e:Array.isArray(e)?e.map(C):Object.keys(e).reduce((t,n)=>{const o=n[0].toLowerCase()+n.slice(1).replace(/([A-Z]+)/g,(r,c)=>"_"+c.toLowerCase());return t[o]=C(e[n]),t},{})}function X(e){return Object.prototype.toString.call(e)==="[object Date]"}function Y(e){return Object.prototype.toString.call(e)==="[object RegExp]"}function _({order:e,placements:t}){const{renderCampaigns:n}=f();m(async o=>{const r=await o.defaultSession().addOrder(C(e)).setPlacements(t||o.placements.getPlacements()).load();n(r)},[e],{deep:!0})}function x(e){return _(e),null}function q(e){const{renderCampaigns:t}=f();m(async n=>{const o=await n.defaultSession().viewOther().setPlacements((e==null?void 0:e.placements)||n.placements.getPlacements()).load();t(o)})}function ee(e){return q(e),null}function te({id:e,pageType:t,children:n}){return y.jsx("div",{className:"nosto_element",id:e,children:n},e+(t||""))}function M({product:e,tagging:t,placements:n,reference:o}){const{renderCampaigns:r}=f();if(t&&!t.product_id)throw new Error("The product object must contain a product_id property");const c=(t==null?void 0:t.product_id)??e;m(async a=>{const i=a.defaultSession().viewProduct(t??e).setPlacements(n||a.placements.getPlacements());o&&i.setRef(c,o);const d=await i.load();r(d)},[c,t==null?void 0:t.selected_sku_id])}function ne(e){return M(e),null}function oe(e,t){return new Promise((n,o)=>{const r=document.createElement("script");r.type="text/javascript",r.src=e,r.async=!0,r.onload=()=>n(),r.onerror=()=>o(),Object.entries((t==null?void 0:t.attributes)??{}).forEach(([c,a])=>r.setAttribute(c,a)),(t==null?void 0:t.position)==="head"?document.head.appendChild(r):document.body.appendChild(r)})}const re={"nosto-client-script":""};function se(e){const{scriptLoader:t=oe,account:n,shopifyMarkets:o,loadScript:r=!0}=e,[c,a]=u.useState(!1);return u.useEffect(()=>{function i(){a(!0)}if(v(),p(l=>l.setAutoLoad(!1)),!r){p(i);return}async function d(){await G({merchantId:n,shopifyInternational:o,options:{attributes:re},scriptLoader:t}),i()}(!z()||o)&&d()},[o==null?void 0:o.marketId,o==null?void 0:o.language]),{clientScriptLoaded:c}}function ce(e){const{account:t,multiCurrency:n=!1,children:o,recommendationComponent:r,renderMode:c}=e,a=n?e.currentVariation:"";if(r&&!u.isValidElement(r))throw new Error("The recommendationComponent prop must be a valid React element. Please provide a valid React element.");const i=c||(r?"JSON_ORIGINAL":"HTML"),{clientScriptLoaded:d}=se(e);return d&&p(l=>{l.defaultSession().setVariation(a).setResponseMode(i)}),y.jsx(S.Provider,{value:{account:t,clientScriptLoaded:d,currentVariation:a,responseMode:i,recommendationComponent:r},children:o})}function H({query:e,placements:t}){const{renderCampaigns:n}=f();m(async o=>{const r=await o.defaultSession().viewSearch(e).setPlacements(t||o.placements.getPlacements()).load();n(r)},[e])}function ie(e){return H(e),null}function D({cart:e,customer:t}={}){const{clientScriptLoaded:n}=N();L(()=>{const o=e?C(e):void 0,r=t?C(t):void 0;n&&p(c=>{c.defaultSession().setCart(o).setCustomer(r).viewOther().load({skipPageViews:!0})})},[n,e,t])}function ae(e){return D(e),null}s.Nosto404=Z,s.NostoCategory=B,s.NostoCheckout=K,s.NostoContext=S,s.NostoHome=Q,s.NostoOrder=x,s.NostoOther=ee,s.NostoPlacement=te,s.NostoProduct=ne,s.NostoProvider=ce,s.NostoSearch=ie,s.NostoSession=ae,s.useNosto404=A,s.useNostoCategory=b,s.useNostoCheckout=I,s.useNostoContext=N,s.useNostoHome=T,s.useNostoOrder=_,s.useNostoOther=q,s.useNostoProduct=M,s.useNostoSearch=H,s.useNostoSession=D,Object.defineProperty(s,Symbol.toStringTag,{value:"Module"})});
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nosto/nosto-react",
|
|
3
3
|
"description": "Component library to simply implementing Nosto on React.",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.7.1",
|
|
5
5
|
"author": "Mridang Agarwalla, Dominik Gilg",
|
|
6
6
|
"license": "ISC",
|
|
7
7
|
"repository": {
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"prettier:fix": "prettier '{src,spec}/**' --write",
|
|
25
25
|
"test": "vitest run --silent",
|
|
26
26
|
"test-loud": "vitest run",
|
|
27
|
-
"typedoc": "typedoc src/index.ts"
|
|
27
|
+
"typedoc": "typedoc --treatWarningsAsErrors src/index.ts"
|
|
28
28
|
},
|
|
29
29
|
"files": [
|
|
30
30
|
"dist",
|
|
@@ -44,6 +44,7 @@
|
|
|
44
44
|
"@types/user-event": "^4.1.1",
|
|
45
45
|
"@vitejs/plugin-react": "^4.3.1",
|
|
46
46
|
"eslint": "^9.13.0",
|
|
47
|
+
"eslint-plugin-barrel-files": "^3.0.1",
|
|
47
48
|
"eslint-plugin-promise": "^7.1.0",
|
|
48
49
|
"eslint-plugin-react": "^7.33.2",
|
|
49
50
|
"prettier": "^3.3.3",
|
|
@@ -53,12 +54,12 @@
|
|
|
53
54
|
"react-router-dom": "^7.0.1",
|
|
54
55
|
"rimraf": "^6.0.1",
|
|
55
56
|
"rollup-plugin-visualizer": "^5.13.1",
|
|
56
|
-
"typedoc": "^0.
|
|
57
|
+
"typedoc": "^0.28.0",
|
|
57
58
|
"typescript": "^5.6.3",
|
|
58
59
|
"typescript-eslint": "^8.13.0",
|
|
59
60
|
"vite": "^6.0.3",
|
|
60
61
|
"vite-plugin-dts": "^4.2.2",
|
|
61
|
-
"vitest": "^
|
|
62
|
+
"vitest": "^3.0.2"
|
|
62
63
|
},
|
|
63
64
|
"main": "./dist/index.umd.js",
|
|
64
65
|
"module": "./dist/index.es.js",
|
|
@@ -73,5 +74,7 @@
|
|
|
73
74
|
"bugs": {
|
|
74
75
|
"url": "https://github.com/Nosto/nosto-react/issues"
|
|
75
76
|
},
|
|
76
|
-
"
|
|
77
|
+
"publishConfig": {
|
|
78
|
+
"access": "public"
|
|
79
|
+
}
|
|
77
80
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @group Components
|
|
3
3
|
*/
|
|
4
|
-
export type NostoPlacementProps = { id: string; pageType?: string }
|
|
4
|
+
export type NostoPlacementProps = { id: string; pageType?: string; children?: React.ReactNode }
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Nosto React has a special component called NostoPlacement.
|
|
@@ -16,6 +16,10 @@ export type NostoPlacementProps = { id: string; pageType?: string }
|
|
|
16
16
|
*
|
|
17
17
|
* @group Components
|
|
18
18
|
*/
|
|
19
|
-
export function NostoPlacement({ id, pageType }: NostoPlacementProps) {
|
|
20
|
-
return
|
|
19
|
+
export function NostoPlacement({ id, pageType, children }: NostoPlacementProps) {
|
|
20
|
+
return (
|
|
21
|
+
<div className="nosto_element" id={id} key={id + (pageType || "")}>
|
|
22
|
+
{children}
|
|
23
|
+
</div>
|
|
24
|
+
)
|
|
21
25
|
}
|
|
@@ -4,6 +4,7 @@ import type { ReactNode } from "react"
|
|
|
4
4
|
import { ScriptLoadOptions } from "../hooks/scriptLoader"
|
|
5
5
|
import { useLoadClientScript } from "../hooks/useLoadClientScript"
|
|
6
6
|
import { nostojs } from "@nosto/nosto-js"
|
|
7
|
+
import { RenderMode } from "@nosto/nosto-js/client"
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* @group Components
|
|
@@ -33,6 +34,10 @@ export interface NostoProviderProps {
|
|
|
33
34
|
* Recommendation component which holds nostoRecommendation object
|
|
34
35
|
*/
|
|
35
36
|
recommendationComponent?: RecommendationComponent
|
|
37
|
+
/**
|
|
38
|
+
* Recommendation render mode. See {@link https://nosto.github.io/nosto-js/types/client.RenderMode.html}
|
|
39
|
+
*/
|
|
40
|
+
renderMode?: RenderMode
|
|
36
41
|
/**
|
|
37
42
|
* Enables Shopify markets with language and market id
|
|
38
43
|
*/
|
|
@@ -72,7 +77,7 @@ export interface NostoProviderProps {
|
|
|
72
77
|
* @group Components
|
|
73
78
|
*/
|
|
74
79
|
export function NostoProvider(props: NostoProviderProps) {
|
|
75
|
-
const { account, multiCurrency = false, children, recommendationComponent } = props
|
|
80
|
+
const { account, multiCurrency = false, children, recommendationComponent, renderMode } = props
|
|
76
81
|
|
|
77
82
|
// Pass currentVariation as empty string if multiCurrency is disabled
|
|
78
83
|
const currentVariation = multiCurrency ? props.currentVariation : ""
|
|
@@ -84,7 +89,7 @@ export function NostoProvider(props: NostoProviderProps) {
|
|
|
84
89
|
}
|
|
85
90
|
|
|
86
91
|
// Set responseMode for loading campaigns:
|
|
87
|
-
const responseMode = recommendationComponent ? "JSON_ORIGINAL" : "HTML"
|
|
92
|
+
const responseMode = renderMode || (recommendationComponent ? "JSON_ORIGINAL" : "HTML")
|
|
88
93
|
|
|
89
94
|
const { clientScriptLoaded } = useLoadClientScript(props)
|
|
90
95
|
|
package/src/context.ts
CHANGED
|
@@ -2,8 +2,6 @@ import { createContext, ReactElement } from "react"
|
|
|
2
2
|
import { Recommendation } from "./types"
|
|
3
3
|
import { RenderMode } from "@nosto/nosto-js/client"
|
|
4
4
|
|
|
5
|
-
type AnyFunction = (...args: unknown[]) => unknown
|
|
6
|
-
|
|
7
5
|
export type RecommendationComponent = ReactElement<{
|
|
8
6
|
nostoRecommendation: Recommendation
|
|
9
7
|
}>
|
|
@@ -15,7 +13,7 @@ export interface NostoContextType {
|
|
|
15
13
|
account: string
|
|
16
14
|
clientScriptLoaded: boolean
|
|
17
15
|
currentVariation?: string
|
|
18
|
-
renderFunction?:
|
|
16
|
+
renderFunction?: (...args: unknown[]) => unknown
|
|
19
17
|
responseMode: RenderMode
|
|
20
18
|
recommendationComponent?: RecommendationComponent
|
|
21
19
|
}
|
|
@@ -20,7 +20,7 @@ function RecommendationComponentWrapper(props: {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
function injectPlacements(data: Record<string, unknown>) {
|
|
23
|
-
nostojs(api => api.placements.injectCampaigns(data as Parameters<API[
|
|
23
|
+
nostojs(api => api.placements.injectCampaigns(data as Parameters<API["placements"]["injectCampaigns"]>[0]))
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
function injectCampaigns(data: CampaignData) {
|
|
@@ -39,6 +39,10 @@ export function useRenderCampaigns() {
|
|
|
39
39
|
return { renderCampaigns: injectCampaigns }
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
if (!recommendationComponent) {
|
|
43
|
+
throw new Error("recommendationComponent is required for client-side rendering using hook")
|
|
44
|
+
}
|
|
45
|
+
|
|
42
46
|
function renderCampaigns(data: CampaignData) {
|
|
43
47
|
// inject Onsite content campaigns directly
|
|
44
48
|
injectPlacements(data.campaigns?.content ?? {})
|
package/src/index.ts
CHANGED
|
@@ -1,25 +1,27 @@
|
|
|
1
|
-
|
|
1
|
+
/* eslint-disable barrel-files/avoid-barrel-files */
|
|
2
|
+
export type { Product, PushedCustomer as Customer, Cart, WebsiteOrder as Order, RenderMode } from "@nosto/nosto-js/client"
|
|
2
3
|
export type { Recommendation } from "./types"
|
|
4
|
+
export type { SnakeToCamelCase, ToCamelCase } from "./utils/types"
|
|
3
5
|
export { type ScriptLoadOptions } from "./hooks/scriptLoader"
|
|
4
|
-
export { NostoContext, type NostoContextType } from "./context"
|
|
6
|
+
export { NostoContext, type NostoContextType, type RecommendationComponent } from "./context"
|
|
5
7
|
export { useNostoContext } from "./hooks/useNostoContext"
|
|
6
|
-
export
|
|
7
|
-
export
|
|
8
|
-
export
|
|
9
|
-
export
|
|
10
|
-
export
|
|
11
|
-
export
|
|
12
|
-
export
|
|
13
|
-
export
|
|
14
|
-
export
|
|
15
|
-
export
|
|
16
|
-
export
|
|
17
|
-
export
|
|
18
|
-
export
|
|
19
|
-
export
|
|
20
|
-
export
|
|
21
|
-
export
|
|
22
|
-
export
|
|
23
|
-
export
|
|
24
|
-
export
|
|
25
|
-
export
|
|
8
|
+
export { Nosto404 } from "./components/Nosto404"
|
|
9
|
+
export { NostoCategory } from "./components/NostoCategory"
|
|
10
|
+
export { NostoCheckout } from "./components/NostoCheckout"
|
|
11
|
+
export { NostoHome } from "./components/NostoHome"
|
|
12
|
+
export { NostoOrder } from "./components/NostoOrder"
|
|
13
|
+
export { NostoOther } from "./components/NostoOther"
|
|
14
|
+
export { NostoPlacement, type NostoPlacementProps } from "./components/NostoPlacement"
|
|
15
|
+
export { NostoProduct } from "./components/NostoProduct"
|
|
16
|
+
export { NostoProvider, type NostoProviderProps } from "./components/NostoProvider"
|
|
17
|
+
export { NostoSearch } from "./components/NostoSearch"
|
|
18
|
+
export { NostoSession } from "./components/NostoSession"
|
|
19
|
+
export { useNosto404, type Nosto404Props } from "./hooks/useNosto404"
|
|
20
|
+
export { useNostoCategory, type NostoCategoryProps } from "./hooks/useNostoCategory"
|
|
21
|
+
export { useNostoCheckout, type NostoCheckoutProps } from "./hooks/useNostoCheckout"
|
|
22
|
+
export { useNostoHome, type NostoHomeProps } from "./hooks/useNostoHome"
|
|
23
|
+
export { useNostoOrder, type NostoOrderProps } from "./hooks/useNostoOrder"
|
|
24
|
+
export { useNostoOther, type NostoOtherProps } from "./hooks/useNostoOther"
|
|
25
|
+
export { useNostoProduct, type NostoProductProps } from "./hooks/useNostoProduct"
|
|
26
|
+
export { useNostoSearch, type NostoSearchProps }from "./hooks/useNostoSearch"
|
|
27
|
+
export { useNostoSession, type NostoSessionProps } from "./hooks/useNostoSession"
|
package/src/utils/types.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
type SnakeToCamelCase<S extends string> = S extends `${infer T}_${infer U}`
|
|
1
|
+
export type SnakeToCamelCase<S extends string> = S extends `${infer T}_${infer U}`
|
|
2
2
|
? `${T}${Capitalize<SnakeToCamelCase<U>>}`
|
|
3
3
|
: S
|
|
4
4
|
|
|
@@ -13,7 +13,7 @@ export type ToCamelCase<T> = T extends (infer U)[]
|
|
|
13
13
|
}
|
|
14
14
|
: T
|
|
15
15
|
|
|
16
|
-
type CamelToSnakeCase<S extends string> = S extends `${infer T}${infer U}`
|
|
16
|
+
export type CamelToSnakeCase<S extends string> = S extends `${infer T}${infer U}`
|
|
17
17
|
? U extends Uncapitalize<U>
|
|
18
18
|
? `${Lowercase<T>}${CamelToSnakeCase<U>}`
|
|
19
19
|
: `${Lowercase<T>}_${CamelToSnakeCase<Uncapitalize<U>>}`
|