@nosto/nosto-react 0.4.1 → 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/README.md +5 -2
  2. package/dist/{index.es.client.js → index.es.js} +123 -164
  3. package/dist/index.umd.js +9 -0
  4. package/package.json +15 -10
  5. package/src/components/Nosto404.tsx +47 -0
  6. package/src/components/{Category/index.client.tsx → NostoCategory.tsx} +18 -39
  7. package/src/components/NostoCheckout.tsx +47 -0
  8. package/src/components/{Home/index.client.tsx → NostoHome.tsx} +17 -36
  9. package/src/components/NostoOrder.tsx +55 -0
  10. package/src/components/NostoOther.tsx +46 -0
  11. package/src/components/{Placement/index.client.tsx → NostoPlacement.tsx} +5 -8
  12. package/src/components/{Product/index.client.tsx → NostoProduct.tsx} +37 -81
  13. package/src/components/NostoProvider.tsx +220 -0
  14. package/src/components/{Search/index.client.tsx → NostoSearch.tsx} +18 -39
  15. package/src/components/{Session/index.client.tsx → NostoSession.tsx} +14 -17
  16. package/src/components/context.ts +55 -0
  17. package/src/components/index.ts +14 -0
  18. package/src/index.ts +3 -0
  19. package/src/types.ts +112 -97
  20. package/src/utils/compare.ts +9 -9
  21. package/src/utils/hooks.ts +28 -8
  22. package/src/utils/object.ts +10 -11
  23. package/src/utils/snakeize.ts +11 -11
  24. package/dist/index.umd.client.js +0 -9
  25. package/src/components/Checkout/index.client.tsx +0 -66
  26. package/src/components/Fohofo/index.client.tsx +0 -66
  27. package/src/components/Order/index.client.tsx +0 -72
  28. package/src/components/Other/index.client.tsx +0 -64
  29. package/src/components/Provider/context.client.ts +0 -45
  30. package/src/components/Provider/index.client.tsx +0 -222
  31. package/src/index.client.ts +0 -33
package/README.md CHANGED
@@ -42,8 +42,8 @@ _\*\*Note: The search feature is available when implemented via our code editor.
42
42
 
43
43
  #### Required versions
44
44
 
45
- - npm: 8.5.5
46
- - node: v16.15.0
45
+ - npm: 9.8.1
46
+ - node: v18.18.0
47
47
 
48
48
  ### Installation
49
49
 
@@ -269,6 +269,9 @@ import {
269
269
  </div>;
270
270
  ```
271
271
 
272
+ ### Detailed technical documentation
273
+ Find our latest technical specs and documentation hosted [here](https://nosto.github.io/nosto-react).
274
+
272
275
  ### Feedback
273
276
 
274
277
  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.
@@ -6,7 +6,13 @@ const NostoContext = createContext({
6
6
  pageType: "",
7
7
  responseMode: "HTML",
8
8
  clientScriptLoaded: false,
9
- useRenderCampaigns: () => void 0
9
+ useRenderCampaigns: () => {
10
+ return {
11
+ renderCampaigns: () => {
12
+ },
13
+ pageTypeUpdated: false
14
+ };
15
+ }
10
16
  });
11
17
  function useNostoContext() {
12
18
  const context = useContext(NostoContext);
@@ -15,6 +21,67 @@ function useNostoContext() {
15
21
  }
16
22
  return context;
17
23
  }
24
+ const isObject = (v) => String(v) === "[object Object]";
25
+ function isPlainObject(value) {
26
+ if (!isObject(value))
27
+ return false;
28
+ const constructor = value.constructor;
29
+ if (constructor === void 0)
30
+ return true;
31
+ const prototype = constructor.prototype;
32
+ if (!isObject(prototype))
33
+ return false;
34
+ if (!prototype.hasOwnProperty("isPrototypeOf")) {
35
+ return false;
36
+ }
37
+ return true;
38
+ }
39
+ function deepCompare(a, b) {
40
+ if (a === b) {
41
+ return true;
42
+ }
43
+ if (a instanceof Date && b instanceof Date) {
44
+ return a.getTime() === b.getTime();
45
+ }
46
+ if (a instanceof Array && b instanceof Array) {
47
+ if (a.length !== b.length) {
48
+ return false;
49
+ }
50
+ return a.every((v, i) => deepCompare(v, b[i]));
51
+ }
52
+ if (isPlainObject(a) && isPlainObject(b)) {
53
+ const entriesA = Object.entries(a);
54
+ if (entriesA.length !== Object.keys(b).length) {
55
+ return false;
56
+ }
57
+ return entriesA.every(([k2, v]) => deepCompare(v, b[k2]));
58
+ }
59
+ return false;
60
+ }
61
+ function useDeepCompareEffect(callback, dependencies) {
62
+ return useEffect(callback, useDeepCompareMemoize(dependencies));
63
+ }
64
+ function useDeepCompareMemoize(value) {
65
+ const ref = useRef(value);
66
+ const signalRef = useRef(0);
67
+ if (!deepCompare(value, ref.current)) {
68
+ ref.current = value;
69
+ signalRef.current += 1;
70
+ }
71
+ return useMemo(() => ref.current, [signalRef.current]);
72
+ }
73
+ function useNostoApi(cb, deps, flags) {
74
+ const { clientScriptLoaded, currentVariation, responseMode } = useNostoContext();
75
+ const useEffectFn = (flags == null ? void 0 : flags.deep) ? useDeepCompareEffect : useEffect;
76
+ useEffectFn(() => {
77
+ if (clientScriptLoaded) {
78
+ window.nostojs((api) => {
79
+ api.defaultSession().setVariation(currentVariation).setResponseMode(responseMode);
80
+ cb(api);
81
+ });
82
+ }
83
+ }, [clientScriptLoaded, currentVariation, responseMode, ...deps != null ? deps : []]);
84
+ }
18
85
  var jsxRuntime = { exports: {} };
19
86
  var reactJsxRuntime_production_min = {};
20
87
  /**
@@ -50,9 +117,6 @@ const jsxs = jsxRuntime.exports.jsxs;
50
117
  const Fragment = jsxRuntime.exports.Fragment;
51
118
  function Nosto404(props) {
52
119
  const {
53
- clientScriptLoaded,
54
- currentVariation,
55
- responseMode,
56
120
  recommendationComponent,
57
121
  useRenderCampaigns
58
122
  } = useNostoContext();
@@ -60,15 +124,10 @@ function Nosto404(props) {
60
124
  renderCampaigns,
61
125
  pageTypeUpdated
62
126
  } = useRenderCampaigns("404");
63
- useEffect(() => {
64
- if (clientScriptLoaded && pageTypeUpdated) {
65
- window.nostojs((api) => {
66
- api.defaultSession().setVariation(currentVariation).setResponseMode(responseMode).viewNotFound().setPlacements(props.placements || api.placements.getPlacements()).load().then((data) => {
67
- renderCampaigns(data, api);
68
- });
69
- });
70
- }
71
- }, [clientScriptLoaded, currentVariation, recommendationComponent, pageTypeUpdated]);
127
+ useNostoApi(async (api) => {
128
+ const data = await api.defaultSession().viewNotFound().setPlacements(props.placements || api.placements.getPlacements()).load();
129
+ renderCampaigns(data, api);
130
+ }, [recommendationComponent, pageTypeUpdated]);
72
131
  return /* @__PURE__ */ jsx(Fragment, {
73
132
  children: /* @__PURE__ */ jsx("div", {
74
133
  className: "nosto_page_type",
@@ -81,9 +140,6 @@ function Nosto404(props) {
81
140
  }
82
141
  function NostoOther(props) {
83
142
  const {
84
- clientScriptLoaded,
85
- currentVariation,
86
- responseMode,
87
143
  recommendationComponent,
88
144
  useRenderCampaigns
89
145
  } = useNostoContext();
@@ -91,15 +147,10 @@ function NostoOther(props) {
91
147
  renderCampaigns,
92
148
  pageTypeUpdated
93
149
  } = useRenderCampaigns("other");
94
- useEffect(() => {
95
- if (clientScriptLoaded && pageTypeUpdated) {
96
- window.nostojs((api) => {
97
- api.defaultSession().setVariation(currentVariation).setResponseMode(responseMode).viewOther().setPlacements(props.placements || api.placements.getPlacements()).load().then((data) => {
98
- renderCampaigns(data, api);
99
- });
100
- });
101
- }
102
- }, [clientScriptLoaded, currentVariation, recommendationComponent, pageTypeUpdated]);
150
+ useNostoApi(async (api) => {
151
+ const data = await api.defaultSession().viewOther().setPlacements(props.placements || api.placements.getPlacements()).load();
152
+ renderCampaigns(data, api);
153
+ }, [recommendationComponent, pageTypeUpdated]);
103
154
  return /* @__PURE__ */ jsx(Fragment, {
104
155
  children: /* @__PURE__ */ jsx("div", {
105
156
  className: "nosto_page_type",
@@ -112,9 +163,6 @@ function NostoOther(props) {
112
163
  }
113
164
  function NostoCheckout(props) {
114
165
  const {
115
- clientScriptLoaded,
116
- currentVariation,
117
- responseMode,
118
166
  recommendationComponent,
119
167
  useRenderCampaigns
120
168
  } = useNostoContext();
@@ -122,15 +170,10 @@ function NostoCheckout(props) {
122
170
  renderCampaigns,
123
171
  pageTypeUpdated
124
172
  } = useRenderCampaigns("checkout");
125
- useEffect(() => {
126
- if (clientScriptLoaded && pageTypeUpdated) {
127
- window.nostojs((api) => {
128
- api.defaultSession().setVariation(currentVariation).setResponseMode(responseMode).viewCart().setPlacements(props.placements || api.placements.getPlacements()).load().then((data) => {
129
- renderCampaigns(data, api);
130
- });
131
- });
132
- }
133
- }, [clientScriptLoaded, currentVariation, recommendationComponent, pageTypeUpdated]);
173
+ useNostoApi(async (api) => {
174
+ const data = await api.defaultSession().viewCart().setPlacements(props.placements || api.placements.getPlacements()).load();
175
+ renderCampaigns(data, api);
176
+ }, [recommendationComponent, pageTypeUpdated]);
134
177
  return /* @__PURE__ */ jsx(Fragment, {
135
178
  children: /* @__PURE__ */ jsx("div", {
136
179
  className: "nosto_page_type",
@@ -141,64 +184,13 @@ function NostoCheckout(props) {
141
184
  })
142
185
  });
143
186
  }
144
- function isPlainObject(value) {
145
- const isObject = (v) => String(v) === "[object Object]";
146
- if (!isObject(value))
147
- return false;
148
- const constructor = value.constructor;
149
- if (constructor === void 0)
150
- return true;
151
- const prototype = constructor.prototype;
152
- if (!isObject(prototype))
153
- return false;
154
- if (!prototype.hasOwnProperty("isPrototypeOf")) {
155
- return false;
156
- }
157
- return true;
158
- }
159
- function deepCompare(a, b) {
160
- if (a === b) {
161
- return true;
162
- }
163
- if (a instanceof Date && b instanceof Date) {
164
- return a.getTime() === b.getTime();
165
- }
166
- if (a instanceof Array && b instanceof Array) {
167
- if (a.length !== b.length) {
168
- return false;
169
- }
170
- return a.every((v, i) => deepCompare(v, b[i]));
171
- }
172
- if (isPlainObject(a) && isPlainObject(b)) {
173
- const entriesA = Object.entries(a);
174
- if (entriesA.length !== Object.keys(b).length) {
175
- return false;
176
- }
177
- return entriesA.every(([k2, v]) => deepCompare(v, b[k2]));
178
- }
179
- return false;
180
- }
181
- function useDeepCompareEffect(callback, dependencies) {
182
- return useEffect(callback, useDeepCompareMemoize(dependencies));
183
- }
184
- function useDeepCompareMemoize(value) {
185
- const ref = useRef(value);
186
- const signalRef = useRef(0);
187
- if (!deepCompare(value, ref.current)) {
188
- ref.current = value;
189
- signalRef.current += 1;
190
- }
191
- return useMemo(() => ref.current, [signalRef.current]);
192
- }
193
187
  function NostoProduct(props) {
194
188
  const {
195
189
  product,
196
- tagging
190
+ tagging,
191
+ placements
197
192
  } = props;
198
193
  const {
199
- clientScriptLoaded,
200
- currentVariation,
201
- responseMode,
202
194
  recommendationComponent,
203
195
  useRenderCampaigns
204
196
  } = useNostoContext();
@@ -206,15 +198,12 @@ function NostoProduct(props) {
206
198
  renderCampaigns,
207
199
  pageTypeUpdated
208
200
  } = useRenderCampaigns("product");
209
- useDeepCompareEffect(() => {
210
- if (clientScriptLoaded && pageTypeUpdated) {
211
- window.nostojs((api) => {
212
- api.defaultSession().setResponseMode(responseMode).viewProduct(product).setPlacements(props.placements || api.placements.getPlacements()).load().then((data) => {
213
- renderCampaigns(data, api);
214
- });
215
- });
216
- }
217
- }, [clientScriptLoaded, currentVariation, product, recommendationComponent, pageTypeUpdated]);
201
+ useNostoApi(async (api) => {
202
+ const data = await api.defaultSession().viewProduct(product).setPlacements(placements || api.placements.getPlacements()).load();
203
+ renderCampaigns(data, api);
204
+ }, [product, recommendationComponent, pageTypeUpdated], {
205
+ deep: true
206
+ });
218
207
  return /* @__PURE__ */ jsxs(Fragment, {
219
208
  children: [/* @__PURE__ */ jsx("div", {
220
209
  className: "nosto_page_type",
@@ -335,12 +324,10 @@ function NostoProduct(props) {
335
324
  }
336
325
  function NostoCategory(props) {
337
326
  const {
338
- category
327
+ category,
328
+ placements
339
329
  } = props;
340
330
  const {
341
- clientScriptLoaded,
342
- currentVariation,
343
- responseMode,
344
331
  recommendationComponent,
345
332
  useRenderCampaigns
346
333
  } = useNostoContext();
@@ -348,15 +335,10 @@ function NostoCategory(props) {
348
335
  renderCampaigns,
349
336
  pageTypeUpdated
350
337
  } = useRenderCampaigns("home");
351
- useEffect(() => {
352
- if (clientScriptLoaded && pageTypeUpdated) {
353
- window.nostojs((api) => {
354
- api.defaultSession().setVariation(currentVariation).setResponseMode(responseMode).viewCategory(category).setPlacements(props.placements || api.placements.getPlacements()).load().then((data) => {
355
- renderCampaigns(data, api);
356
- });
357
- });
358
- }
359
- }, [clientScriptLoaded, category, currentVariation, recommendationComponent, pageTypeUpdated]);
338
+ useNostoApi(async (api) => {
339
+ const data = await api.defaultSession().viewCategory(category).setPlacements(placements || api.placements.getPlacements()).load();
340
+ renderCampaigns(data, api);
341
+ }, [category, recommendationComponent, pageTypeUpdated]);
360
342
  return /* @__PURE__ */ jsxs(Fragment, {
361
343
  children: [/* @__PURE__ */ jsx("div", {
362
344
  className: "nosto_page_type",
@@ -375,12 +357,10 @@ function NostoCategory(props) {
375
357
  }
376
358
  function NostoSearch(props) {
377
359
  const {
378
- query
360
+ query,
361
+ placements
379
362
  } = props;
380
363
  const {
381
- clientScriptLoaded,
382
- currentVariation,
383
- responseMode,
384
364
  recommendationComponent,
385
365
  useRenderCampaigns
386
366
  } = useNostoContext();
@@ -388,15 +368,10 @@ function NostoSearch(props) {
388
368
  renderCampaigns,
389
369
  pageTypeUpdated
390
370
  } = useRenderCampaigns("search");
391
- useEffect(() => {
392
- if (clientScriptLoaded && pageTypeUpdated) {
393
- window.nostojs((api) => {
394
- api.defaultSession().setVariation(currentVariation).setResponseMode(responseMode).viewSearch(query).setPlacements(props.placements || api.placements.getPlacements()).load().then((data) => {
395
- renderCampaigns(data, api);
396
- });
397
- });
398
- }
399
- }, [clientScriptLoaded, currentVariation, query, recommendationComponent, pageTypeUpdated]);
371
+ useNostoApi(async (api) => {
372
+ const data = await api.defaultSession().viewSearch(query).setPlacements(placements || api.placements.getPlacements()).load();
373
+ renderCampaigns(data, api);
374
+ }, [query, recommendationComponent, pageTypeUpdated]);
400
375
  return /* @__PURE__ */ jsxs(Fragment, {
401
376
  children: [/* @__PURE__ */ jsx("div", {
402
377
  className: "nosto_page_type",
@@ -424,7 +399,7 @@ function snakeize(obj) {
424
399
  return obj.map(snakeize);
425
400
  }
426
401
  return Object.keys(obj).reduce((acc, key) => {
427
- var camel = key[0].toLowerCase() + key.slice(1).replace(/([A-Z]+)/g, (_, x) => {
402
+ const camel = key[0].toLowerCase() + key.slice(1).replace(/([A-Z]+)/g, (_, x) => {
428
403
  return "_" + x.toLowerCase();
429
404
  });
430
405
  acc[camel] = snakeize(obj[key]);
@@ -439,12 +414,10 @@ function isRegex(obj) {
439
414
  }
440
415
  function NostoOrder(props) {
441
416
  const {
442
- order
417
+ order,
418
+ placements
443
419
  } = props;
444
420
  const {
445
- clientScriptLoaded,
446
- currentVariation,
447
- responseMode,
448
421
  recommendationComponent,
449
422
  useRenderCampaigns
450
423
  } = useNostoContext();
@@ -452,15 +425,10 @@ function NostoOrder(props) {
452
425
  renderCampaigns,
453
426
  pageTypeUpdated
454
427
  } = useRenderCampaigns("order");
455
- useEffect(() => {
456
- if (clientScriptLoaded && pageTypeUpdated) {
457
- window.nostojs((api) => {
458
- api.defaultSession().setVariation(currentVariation).setResponseMode(responseMode).addOrder(snakeize(order)).setPlacements(props.placements || api.placements.getPlacements()).load().then((data) => {
459
- renderCampaigns(data, api);
460
- });
461
- });
462
- }
463
- }, [clientScriptLoaded, currentVariation, recommendationComponent, pageTypeUpdated]);
428
+ useNostoApi(async (api) => {
429
+ const data = await api.defaultSession().addOrder(snakeize(order)).setPlacements(placements || api.placements.getPlacements()).load();
430
+ renderCampaigns(data, api);
431
+ }, [recommendationComponent, pageTypeUpdated]);
464
432
  return /* @__PURE__ */ jsxs(Fragment, {
465
433
  children: [/* @__PURE__ */ jsx("div", {
466
434
  className: "nosto_page_type",
@@ -479,9 +447,6 @@ function NostoOrder(props) {
479
447
  }
480
448
  function NostoHome(props) {
481
449
  const {
482
- clientScriptLoaded,
483
- currentVariation,
484
- responseMode,
485
450
  recommendationComponent,
486
451
  useRenderCampaigns
487
452
  } = useNostoContext();
@@ -489,15 +454,10 @@ function NostoHome(props) {
489
454
  renderCampaigns,
490
455
  pageTypeUpdated
491
456
  } = useRenderCampaigns("home");
492
- useEffect(() => {
493
- if (clientScriptLoaded && pageTypeUpdated) {
494
- window.nostojs((api) => {
495
- api.defaultSession().setVariation(currentVariation).setResponseMode(responseMode).viewFrontPage().setPlacements(props.placements || api.placements.getPlacements()).load().then((data) => {
496
- renderCampaigns(data, api);
497
- });
498
- });
499
- }
500
- }, [clientScriptLoaded, currentVariation, recommendationComponent, pageTypeUpdated]);
457
+ useNostoApi(async (api) => {
458
+ const data = await api.defaultSession().viewFrontPage().setPlacements(props.placements || api.placements.getPlacements()).load();
459
+ renderCampaigns(data, api);
460
+ }, [recommendationComponent, pageTypeUpdated]);
501
461
  return /* @__PURE__ */ jsx(Fragment, {
502
462
  children: /* @__PURE__ */ jsx("div", {
503
463
  className: "nosto_page_type",
@@ -519,9 +479,8 @@ function NostoPlacement(props) {
519
479
  }, id + (pageType || ""));
520
480
  }
521
481
  function NostoProvider(props) {
522
- let {
482
+ const {
523
483
  account,
524
- currentVariation = "",
525
484
  multiCurrency = false,
526
485
  host,
527
486
  children,
@@ -530,7 +489,7 @@ function NostoProvider(props) {
530
489
  } = props;
531
490
  const [clientScriptLoadedState, setClientScriptLoadedState] = React.useState(false);
532
491
  const clientScriptLoaded = React.useMemo(() => clientScriptLoadedState, [clientScriptLoadedState]);
533
- currentVariation = multiCurrency ? currentVariation : "";
492
+ const currentVariation = multiCurrency ? props.currentVariation : "";
534
493
  const responseMode = isValidElement(recommendationComponent) ? "JSON_ORIGINAL" : "HTML";
535
494
  function RecommendationComponentWrapper(props2) {
536
495
  return React.cloneElement(recommendationComponent, {
@@ -538,24 +497,24 @@ function NostoProvider(props) {
538
497
  });
539
498
  }
540
499
  const [pageType, setPageType] = useState("");
541
- const useRenderCampaigns = function(type = "") {
500
+ function useRenderCampaigns(type = "") {
542
501
  const placementRefs = useRef({});
543
502
  useEffect(() => {
544
- if (pageType != type) {
503
+ if (pageType !== type) {
545
504
  setPageType(type);
546
505
  }
547
506
  }, []);
548
- const pageTypeUpdated = type == pageType;
507
+ const pageTypeUpdated = type === pageType;
549
508
  function renderCampaigns(data, api) {
550
509
  if (responseMode == "HTML") {
551
510
  api.placements.injectCampaigns(data.recommendations);
552
511
  } else {
553
512
  const recommendations = data.campaigns.recommendations;
554
513
  for (const key in recommendations) {
555
- let recommendation = recommendations[key];
556
- let placementSelector = "#" + key;
557
- let placement = () => document.querySelector(placementSelector);
558
- if (!!placement()) {
514
+ const recommendation = recommendations[key];
515
+ const placementSelector = "#" + key;
516
+ const placement = () => document.querySelector(placementSelector);
517
+ if (placement()) {
559
518
  if (!placementRefs.current[key])
560
519
  placementRefs.current[key] = createRoot(placement());
561
520
  const root = placementRefs.current[key];
@@ -570,7 +529,7 @@ function NostoProvider(props) {
570
529
  renderCampaigns,
571
530
  pageTypeUpdated
572
531
  };
573
- };
532
+ }
574
533
  useEffect(() => {
575
534
  var _a, _b, _c;
576
535
  if (!window.nostojs) {
@@ -596,10 +555,10 @@ function NostoProvider(props) {
596
555
  };
597
556
  document.body.appendChild(script);
598
557
  }
599
- if (!!shopifyMarkets) {
558
+ if (shopifyMarkets) {
600
559
  const existingScript = document.querySelector("[nosto-client-script]");
601
560
  const nostoSandbox = document.querySelector("#nosto-sandbox");
602
- if (!existingScript || (existingScript == null ? void 0 : existingScript.getAttribute("nosto-language")) != (shopifyMarkets == null ? void 0 : shopifyMarkets.language) || (existingScript == null ? void 0 : existingScript.getAttribute("nosto-market-id")) != (shopifyMarkets == null ? void 0 : shopifyMarkets.marketId)) {
561
+ if (!existingScript || (existingScript == null ? void 0 : existingScript.getAttribute("nosto-language")) !== (shopifyMarkets == null ? void 0 : shopifyMarkets.language) || (existingScript == null ? void 0 : existingScript.getAttribute("nosto-market-id")) !== (shopifyMarkets == null ? void 0 : shopifyMarkets.marketId)) {
603
562
  if (clientScriptLoadedState) {
604
563
  setClientScriptLoadedState(false);
605
564
  }
@@ -0,0 +1,9 @@
1
+ (function(m,p){typeof exports=="object"&&typeof module!="undefined"?p(exports,require("react"),require("react-dom/client")):typeof define=="function"&&define.amd?define(["exports","react","react-dom/client"],p):(m=typeof globalThis!="undefined"?globalThis:m||self,p(m["@nosto/nosto-react"]={},m.React,m.ReactDOM))})(this,function(m,p,H){"use strict";function M(t){return t&&typeof t=="object"&&"default"in t?t:{default:t}}var T=M(p);const F=p.createContext({account:"",currentVariation:"",pageType:"",responseMode:"HTML",clientScriptLoaded:!1,useRenderCampaigns:()=>({renderCampaigns:()=>{},pageTypeUpdated:!1})});function f(){const t=p.useContext(F);if(!t)throw new Error("No nosto context found");return t}const I=t=>String(t)==="[object Object]";function q(t){if(!I(t))return!1;const n=t.constructor;if(n===void 0)return!0;const e=n.prototype;return!(!I(e)||!e.hasOwnProperty("isPrototypeOf"))}function U(t,n){if(t===n)return!0;if(t instanceof Date&&n instanceof Date)return t.getTime()===n.getTime();if(t instanceof Array&&n instanceof Array)return t.length!==n.length?!1:t.every((e,a)=>U(e,n[a]));if(q(t)&&q(n)){const e=Object.entries(t);return e.length!==Object.keys(n).length?!1:e.every(([a,i])=>U(i,n[a]))}return!1}function x(t,n){return p.useEffect(t,$(n))}function $(t){const n=p.useRef(t),e=p.useRef(0);return U(t,n.current)||(n.current=t,e.current+=1),p.useMemo(()=>n.current,[e.current])}function C(t,n,e){const{clientScriptLoaded:a,currentVariation:i,responseMode:c}=f();(e!=null&&e.deep?x:p.useEffect)(()=>{a&&window.nostojs(d=>{d.defaultSession().setVariation(i).setResponseMode(c),t(d)})},[a,i,c,...n!=null?n:[]])}var R={exports:{}},b={};/**
2
+ * @license React
3
+ * react-jsx-runtime.production.min.js
4
+ *
5
+ * Copyright (c) Facebook, Inc. and its affiliates.
6
+ *
7
+ * This source code is licensed under the MIT license found in the
8
+ * LICENSE file in the root directory of this source tree.
9
+ */var G=T.default,z=Symbol.for("react.element"),J=Symbol.for("react.fragment"),W=Object.prototype.hasOwnProperty,B=G.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner,Y={key:!0,ref:!0,__self:!0,__source:!0};function D(t,n,e){var a,i={},c=null,r=null;e!==void 0&&(c=""+e),n.key!==void 0&&(c=""+n.key),n.ref!==void 0&&(r=n.ref);for(a in n)W.call(n,a)&&!Y.hasOwnProperty(a)&&(i[a]=n[a]);if(t&&t.defaultProps)for(a in n=t.defaultProps,n)i[a]===void 0&&(i[a]=n[a]);return{$$typeof:z,type:t,key:c,ref:r,props:i,_owner:B.current}}b.Fragment=J,b.jsx=D,b.jsxs=D,R.exports=b;const s=R.exports.jsx,v=R.exports.jsxs,N=R.exports.Fragment;function Z(t){const{recommendationComponent:n,useRenderCampaigns:e}=f(),{renderCampaigns:a,pageTypeUpdated:i}=e("404");return C(async c=>{const r=await c.defaultSession().viewNotFound().setPlacements(t.placements||c.placements.getPlacements()).load();a(r,c)},[n,i]),s(N,{children:s("div",{className:"nosto_page_type",style:{display:"none"},children:"notfound"})})}function K(t){const{recommendationComponent:n,useRenderCampaigns:e}=f(),{renderCampaigns:a,pageTypeUpdated:i}=e("other");return C(async c=>{const r=await c.defaultSession().viewOther().setPlacements(t.placements||c.placements.getPlacements()).load();a(r,c)},[n,i]),s(N,{children:s("div",{className:"nosto_page_type",style:{display:"none"},children:"other"})})}function Q(t){const{recommendationComponent:n,useRenderCampaigns:e}=f(),{renderCampaigns:a,pageTypeUpdated:i}=e("checkout");return C(async c=>{const r=await c.defaultSession().viewCart().setPlacements(t.placements||c.placements.getPlacements()).load();a(r,c)},[n,i]),s(N,{children:s("div",{className:"nosto_page_type",style:{display:"none"},children:"cart"})})}function X(t){const{product:n,tagging:e,placements:a}=t,{recommendationComponent:i,useRenderCampaigns:c}=f(),{renderCampaigns:r,pageTypeUpdated:d}=c("product");return C(async o=>{const u=await o.defaultSession().viewProduct(n).setPlacements(a||o.placements.getPlacements()).load();r(u,o)},[n,i,d],{deep:!0}),v(N,{children:[s("div",{className:"nosto_page_type",style:{display:"none"},children:"product"}),v("div",{className:"nosto_product",style:{display:"none"},children:[(e==null?void 0:e.variationId)&&s("span",{className:"variation_id",children:e.variationId}),n&&s("span",{className:"product_id",children:n}),(e==null?void 0:e.name)&&s("span",{className:"name",children:e.name}),(e==null?void 0:e.url)&&s("span",{className:"url",children:e.url.toString()}),(e==null?void 0:e.imageUrl)&&s("span",{className:"image_url",children:e.imageUrl.toString()}),(e==null?void 0:e.availability)&&s("span",{className:"availability",children:e.availability}),(e==null?void 0:e.price)&&s("span",{className:"price",children:e.price}),(e==null?void 0:e.listPrice)&&s("span",{className:"list_price",children:e.listPrice}),(e==null?void 0:e.priceCurrencyCode)&&s("span",{className:"price_currency_code",children:e.priceCurrencyCode}),(e==null?void 0:e.brand)&&s("span",{className:"brand",children:e.brand}),(e==null?void 0:e.description)&&s("span",{className:"description",children:e.description}),(e==null?void 0:e.googleCategory)&&s("span",{className:"description",children:e.googleCategory}),(e==null?void 0:e.condition)&&s("span",{className:"condition",children:e.condition}),(e==null?void 0:e.gender)&&s("span",{className:"gender",children:e.gender}),(e==null?void 0:e.ageGroup)&&s("span",{className:"age_group",children:e.ageGroup}),(e==null?void 0:e.gtin)&&s("span",{className:"gtin",children:e.gtin}),(e==null?void 0:e.category)&&(e==null?void 0:e.category.map((o,u)=>s("span",{className:"category",children:o},u))),(e==null?void 0:e.tags1)&&e.tags1.map((o,u)=>s("span",{className:"tag1",children:o},u)),(e==null?void 0:e.tags2)&&e.tags2.map((o,u)=>s("span",{className:"tag2",children:o},u)),(e==null?void 0:e.tags3)&&e.tags3.map((o,u)=>s("span",{className:"tag3",children:o},u)),(e==null?void 0:e.ratingValue)&&s("span",{className:"rating_value",children:e.ratingValue}),(e==null?void 0:e.reviewCount)&&s("span",{className:"review_count",children:e.reviewCount}),(e==null?void 0:e.alternateImageUrls)&&e.alternateImageUrls.map((o,u)=>s("span",{className:"alternate_image_url",children:o.toString()},u)),(e==null?void 0:e.customFields)&&Object.keys(e.customFields).map((o,u)=>e.customFields&&e.customFields[o]&&s("span",{className:o,children:e.customFields[o]},u)),(e==null?void 0:e.skus)&&e.skus.map((o,u)=>v("span",{className:"nosto_sku",children:[(o==null?void 0:o.id)&&s("span",{className:"product_id",children:o.id}),(o==null?void 0:o.name)&&s("span",{className:"name",children:o.name}),(o==null?void 0:o.price)&&s("span",{className:"price",children:o.price}),(o==null?void 0:o.listPrice)&&s("span",{className:"list_price",children:o.listPrice}),(o==null?void 0:o.url)&&s("span",{className:"url",children:o.url.toString()}),(o==null?void 0:o.imageUrl)&&s("span",{className:"image_url",children:o.imageUrl.toString()}),(o==null?void 0:o.gtin)&&s("span",{className:"gtin",children:o.gtin}),(o==null?void 0:o.availability)&&s("span",{className:"availability",children:o.availability}),(o==null?void 0:o.customFields)&&Object.keys(o.customFields).map((P,E)=>o.customFields&&o.customFields[P]&&s("span",{className:P,children:o.customFields[P]},E))]},u))]})]})}function g(t){const{category:n,placements:e}=t,{recommendationComponent:a,useRenderCampaigns:i}=f(),{renderCampaigns:c,pageTypeUpdated:r}=i("home");return C(async d=>{const o=await d.defaultSession().viewCategory(n).setPlacements(e||d.placements.getPlacements()).load();c(o,d)},[n,a,r]),v(N,{children:[s("div",{className:"nosto_page_type",style:{display:"none"},children:"category"}),s("div",{className:"nosto_category",style:{display:"none"},children:n})]})}function k(t){const{query:n,placements:e}=t,{recommendationComponent:a,useRenderCampaigns:i}=f(),{renderCampaigns:c,pageTypeUpdated:r}=i("search");return C(async d=>{const o=await d.defaultSession().viewSearch(n).setPlacements(e||d.placements.getPlacements()).load();c(o,d)},[n,a,r]),v(N,{children:[s("div",{className:"nosto_page_type",style:{display:"none"},children:"search"}),s("div",{className:"nosto_search",style:{display:"none"},children:n})]})}function S(t){return!t||typeof t!="object"||ee(t)||te(t)?t:Array.isArray(t)?t.map(S):Object.keys(t).reduce((n,e)=>{const a=e[0].toLowerCase()+e.slice(1).replace(/([A-Z]+)/g,(i,c)=>"_"+c.toLowerCase());return n[a]=S(t[e]),n},{})}function ee(t){return Object.prototype.toString.call(t)==="[object Date]"}function te(t){return Object.prototype.toString.call(t)==="[object RegExp]"}function ne(t){const{order:n,placements:e}=t,{recommendationComponent:a,useRenderCampaigns:i}=f(),{renderCampaigns:c,pageTypeUpdated:r}=i("order");return C(async d=>{const o=await d.defaultSession().addOrder(S(n)).setPlacements(e||d.placements.getPlacements()).load();c(o,d)},[a,r]),v(N,{children:[s("div",{className:"nosto_page_type",style:{display:"none"},children:"order"}),s("div",{className:"nosto_order",style:{display:"none"},children:n.purchase.number})]})}function oe(t){const{recommendationComponent:n,useRenderCampaigns:e}=f(),{renderCampaigns:a,pageTypeUpdated:i}=e("home");return C(async c=>{const r=await c.defaultSession().viewFrontPage().setPlacements(t.placements||c.placements.getPlacements()).load();a(r,c)},[n,i]),s(N,{children:s("div",{className:"nosto_page_type",style:{display:"none"},children:"front"})})}function se(t){const{id:n,pageType:e}=t;return s("div",{className:"nosto_element",id:n},n+(e||""))}function re(t){const{account:n,multiCurrency:e=!1,host:a,children:i,recommendationComponent:c,shopifyMarkets:r}=t,[d,o]=T.default.useState(!1),u=T.default.useMemo(()=>d,[d]),P=e?t.currentVariation:"",E=p.isValidElement(c)?"JSON_ORIGINAL":"HTML";function ce(_){return T.default.cloneElement(c,{nostoRecommendation:_.nostoRecommendation})}const[A,ie]=p.useState("");function le(_=""){const w=p.useRef({});p.useEffect(()=>{A!==_&&ie(_)},[]);const L=_===A;function l(y,h){if(E=="HTML")h.placements.injectCampaigns(y.recommendations);else{const O=y.campaigns.recommendations;for(const j in O){const de=O[j],me="#"+j,V=()=>document.querySelector(me);V()&&(w.current[j]||(w.current[j]=H.createRoot(V())),w.current[j].render(s(ce,{nostoRecommendation:de})))}}}return{renderCampaigns:l,pageTypeUpdated:L}}return p.useEffect(()=>{var _,w,L;if(window.nostojs||(window.nostojs=l=>{(window.nostojs.q=window.nostojs.q||[]).push(l)},window.nostojs(l=>l.setAutoLoad(!1))),!document.querySelectorAll("[nosto-client-script]").length&&!r){const l=document.createElement("script");l.type="text/javascript",l.src="//"+(a||"connect.nosto.com")+"/include/"+n,l.async=!0,l.setAttribute("nosto-client-script",""),l.onload=()=>{var y;typeof jest!="undefined"&&((y=window.nosto)==null||y.reload({site:"localhost"})),o(!0)},document.body.appendChild(l)}if(r){const l=document.querySelector("[nosto-client-script]"),y=document.querySelector("#nosto-sandbox");if(!l||(l==null?void 0:l.getAttribute("nosto-language"))!==(r==null?void 0:r.language)||(l==null?void 0:l.getAttribute("nosto-market-id"))!==(r==null?void 0:r.marketId)){d&&o(!1),(_=l==null?void 0:l.parentNode)==null||_.removeChild(l),(w=y==null?void 0:y.parentNode)==null||w.removeChild(y);const h=document.createElement("script");h.type="text/javascript",h.src="//"+(a||"connect.nosto.com")+`/script/shopify/market/nosto.js?merchant=${n}&market=${r.marketId||""}&locale=${((L=r==null?void 0:r.language)==null?void 0:L.toLowerCase())||""}`,h.async=!0,h.setAttribute("nosto-client-script",""),h.setAttribute("nosto-language",(r==null?void 0:r.language)||""),h.setAttribute("nosto-market-id",String(r==null?void 0:r.marketId)),h.onload=()=>{var O;typeof jest!="undefined"&&((O=window.nosto)==null||O.reload({site:"localhost"})),o(!0)},document.body.appendChild(h)}}},[d,r]),s(F.Provider,{value:{account:n,clientScriptLoaded:u,currentVariation:P,responseMode:E,recommendationComponent:c,useRenderCampaigns:le,pageType:A},children:i})}function ae(t){const{cart:n,customer:e}=t,{clientScriptLoaded:a}=f();return x(()=>{const i=n?S(n):void 0,c=e?S(e):void 0;a&&window.nostojs(r=>{r.defaultSession().setResponseMode("HTML").setCart(i).setCustomer(c).viewOther().load()})},[a,n,e]),s(N,{})}m.Nosto404=Z,m.NostoCategory=g,m.NostoCheckout=Q,m.NostoContext=F,m.NostoHome=oe,m.NostoOrder=ne,m.NostoOther=K,m.NostoPlacement=se,m.NostoProduct=X,m.NostoProvider=re,m.NostoSearch=k,m.NostoSession=ae,m.useNostoContext=f,Object.defineProperties(m,{__esModule:{value:!0},[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": "0.4.1",
4
+ "version": "0.4.3",
5
5
  "author": "Mridang Agarwalla, Dominik Gilg",
6
6
  "license": "ISC",
7
7
  "repository": {
@@ -14,14 +14,14 @@
14
14
  ],
15
15
  "scripts": {
16
16
  "dev": "vite",
17
- "build": "tsc && vite build && typedoc src/index.client.ts",
17
+ "build": "tsc && npm run lint && vite build && typedoc src/index.ts",
18
18
  "preview": "vite preview",
19
19
  "prepare": "vite build",
20
20
  "typecheck": "tsc",
21
- "tslint": "tslint 'src/**/*.ts?(x)'",
21
+ "lint": "eslint 'src/**/*.{ts,tsx}' --cache",
22
22
  "clean": "rimraf dist",
23
- "prettier": "prettier '{src,spec}/*/**' --list-different",
24
- "prettier:fix": "prettier '{src,spec}/*/**' --write",
23
+ "prettier": "prettier '{src,spec}/**' --list-different",
24
+ "prettier:fix": "prettier '{src,spec}/**' --write",
25
25
  "test": "jest spec"
26
26
  },
27
27
  "files": [
@@ -36,7 +36,12 @@
36
36
  "@types/react": "^18.0.0",
37
37
  "@types/react-dom": "^18.0.0",
38
38
  "@types/user-event": "^4.1.1",
39
+ "@typescript-eslint/eslint-plugin": "^6.11.0",
40
+ "@typescript-eslint/parser": "^6.11.0",
39
41
  "@vitejs/plugin-react": "^1.3.0",
42
+ "eslint": "^8.53.0",
43
+ "eslint-plugin-react": "^7.33.2",
44
+ "eslint-plugin-promise": "^6.1.1",
40
45
  "jest": "^29.5.0",
41
46
  "jest-environment-jsdom": "^29.5.0",
42
47
  "prettier": "^2.0.5",
@@ -48,15 +53,15 @@
48
53
  "typescript": "^4.6.3",
49
54
  "vite": "^2.9.9"
50
55
  },
51
- "main": "./dist/index.umd.client.js",
52
- "module": "./dist/index.es.client.js",
56
+ "main": "./dist/index.umd.js",
57
+ "module": "./dist/index.es.js",
53
58
  "types": "./src/types.ts",
54
59
  "exports": {
55
60
  ".": {
56
- "import": "./dist/index.es.client.js",
57
- "require": "./dist/index.umd.client.js"
61
+ "import": "./dist/index.es.js",
62
+ "require": "./dist/index.umd.js"
58
63
  },
59
- "./shopify-hydrogen": "./dist/index.es.client.js"
64
+ "./shopify-hydrogen": "./dist/index.es.js"
60
65
  },
61
66
  "bugs": {
62
67
  "url": "https://github.com/Nosto/nosto-react/issues"
@@ -0,0 +1,47 @@
1
+ import { useNostoContext } from "./context"
2
+ import { useNostoApi } from "../utils/hooks"
3
+
4
+ /**
5
+ * You can personalise your cart and checkout pages by using the `Nosto404` component.
6
+ * The component does not require any props.
7
+ *
8
+ * By default, your account, when created, has three 404-page placements named `notfound-nosto-1`, `notfound-nosto-2` and `notfound-nosto-3`.
9
+ * You may omit these and use any identifier you need.
10
+ * The identifiers used here are simply provided to illustrate the example.
11
+ *
12
+ * @example
13
+ * ```
14
+ * <div className="notfound-page">
15
+ * <NostoPlacement id="notfound-nosto-1" />
16
+ * <NostoPlacement id="notfound-nosto-2" />
17
+ * <NostoPlacement id="notfound-nosto-3" />
18
+ * <Nosto404 />
19
+ * </div>
20
+ * ```
21
+ *
22
+ * @group Components
23
+ */
24
+ export default function Nosto404(props: { placements?: string[] }) {
25
+ const { recommendationComponent, useRenderCampaigns } = useNostoContext()
26
+
27
+ const { renderCampaigns, pageTypeUpdated } = useRenderCampaigns("404")
28
+
29
+ useNostoApi(
30
+ async (api) => {
31
+ const data = await api.defaultSession()
32
+ .viewNotFound()
33
+ .setPlacements(props.placements || api.placements.getPlacements())
34
+ .load()
35
+ renderCampaigns(data, api)
36
+ },
37
+ [recommendationComponent, pageTypeUpdated]
38
+ )
39
+
40
+ return (
41
+ <>
42
+ <div className="nosto_page_type" style={{ display: "none" }}>
43
+ notfound
44
+ </div>
45
+ </>
46
+ )
47
+ }