@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.
- package/README.md +5 -2
- package/dist/{index.es.client.js → index.es.js} +123 -164
- package/dist/index.umd.js +9 -0
- package/package.json +15 -10
- package/src/components/Nosto404.tsx +47 -0
- package/src/components/{Category/index.client.tsx → NostoCategory.tsx} +18 -39
- package/src/components/NostoCheckout.tsx +47 -0
- package/src/components/{Home/index.client.tsx → NostoHome.tsx} +17 -36
- package/src/components/NostoOrder.tsx +55 -0
- package/src/components/NostoOther.tsx +46 -0
- package/src/components/{Placement/index.client.tsx → NostoPlacement.tsx} +5 -8
- package/src/components/{Product/index.client.tsx → NostoProduct.tsx} +37 -81
- package/src/components/NostoProvider.tsx +220 -0
- package/src/components/{Search/index.client.tsx → NostoSearch.tsx} +18 -39
- package/src/components/{Session/index.client.tsx → NostoSession.tsx} +14 -17
- package/src/components/context.ts +55 -0
- package/src/components/index.ts +14 -0
- package/src/index.ts +3 -0
- package/src/types.ts +112 -97
- package/src/utils/compare.ts +9 -9
- package/src/utils/hooks.ts +28 -8
- package/src/utils/object.ts +10 -11
- package/src/utils/snakeize.ts +11 -11
- package/dist/index.umd.client.js +0 -9
- package/src/components/Checkout/index.client.tsx +0 -66
- package/src/components/Fohofo/index.client.tsx +0 -66
- package/src/components/Order/index.client.tsx +0 -72
- package/src/components/Other/index.client.tsx +0 -64
- package/src/components/Provider/context.client.ts +0 -45
- package/src/components/Provider/index.client.tsx +0 -222
- 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.
|
|
46
|
-
- node:
|
|
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: () =>
|
|
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
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
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
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
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
|
-
|
|
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
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
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
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
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
|
-
|
|
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
|
-
|
|
500
|
+
function useRenderCampaigns(type = "") {
|
|
542
501
|
const placementRefs = useRef({});
|
|
543
502
|
useEffect(() => {
|
|
544
|
-
if (pageType
|
|
503
|
+
if (pageType !== type) {
|
|
545
504
|
setPageType(type);
|
|
546
505
|
}
|
|
547
506
|
}, []);
|
|
548
|
-
const pageTypeUpdated = type
|
|
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
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
if (
|
|
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 (
|
|
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"))
|
|
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.
|
|
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.
|
|
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
|
-
"
|
|
21
|
+
"lint": "eslint 'src/**/*.{ts,tsx}' --cache",
|
|
22
22
|
"clean": "rimraf dist",
|
|
23
|
-
"prettier": "prettier '{src,spec}
|
|
24
|
-
"prettier:fix": "prettier '{src,spec}
|
|
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.
|
|
52
|
-
"module": "./dist/index.es.
|
|
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.
|
|
57
|
-
"require": "./dist/index.umd.
|
|
61
|
+
"import": "./dist/index.es.js",
|
|
62
|
+
"require": "./dist/index.umd.js"
|
|
58
63
|
},
|
|
59
|
-
"./shopify-hydrogen": "./dist/index.es.
|
|
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
|
+
}
|