@nosto/nosto-react 0.2.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +66 -86
- package/dist/index.es.client.js +299 -248
- package/dist/index.umd.client.js +2 -2
- package/package.json +29 -13
- package/src/components/Category/index.client.tsx +48 -19
- package/src/components/Checkout/index.client.tsx +42 -19
- package/src/components/Fohofo/index.client.tsx +42 -19
- package/src/components/Home/index.client.tsx +45 -19
- package/src/components/Order/index.client.tsx +44 -23
- package/src/components/Other/index.client.tsx +40 -18
- package/src/components/Placement/index.client.tsx +18 -7
- package/src/components/Product/index.client.tsx +60 -34
- package/src/components/Provider/context.client.ts +25 -8
- package/src/components/Provider/index.client.tsx +184 -32
- package/src/components/Search/index.client.tsx +47 -19
- package/src/components/Session/index.client.tsx +20 -15
- package/src/index.client.ts +7 -11
- package/src/types.ts +84 -2
- package/src/utils/compare.ts +30 -0
- package/src/utils/hooks.ts +21 -0
- package/src/utils/object.ts +20 -0
- package/src/utils/snakeize.ts +28 -0
- package/src/snakeize.d.ts +0 -1
- package/src/utils/stringinate.ts +0 -16
|
@@ -1,11 +1,22 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Nosto React has a special component called NostoPlacement.
|
|
5
|
+
* The component is a simply a hidden `<div>` placeholder into which Nosto injects recommendations or personalises the content between the tags.
|
|
6
|
+
*
|
|
7
|
+
* 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.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```
|
|
11
|
+
* <NostoPlacement id="frontpage-nosto-1" />
|
|
12
|
+
* ```
|
|
13
|
+
*
|
|
14
|
+
* @group Personalisation Components
|
|
15
|
+
*/
|
|
16
|
+
export default function NostoPlacement(props: {
|
|
4
17
|
id: string;
|
|
18
|
+
pageType?: string;
|
|
19
|
+
}): JSX.Element {
|
|
20
|
+
const { id, pageType } = props;
|
|
21
|
+
return <div className="nosto_element" id={id} key={id + (pageType || "")} />;
|
|
5
22
|
}
|
|
6
|
-
|
|
7
|
-
const NostoPlacement: React.FC<PlacementProps> = ({ id }) => {
|
|
8
|
-
return <div className="nosto_element" id={id} />;
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
export default NostoPlacement;
|
|
@@ -1,39 +1,67 @@
|
|
|
1
1
|
import { Product } from "../../types";
|
|
2
|
-
import React, { useEffect } from "react";
|
|
3
2
|
import { useNostoContext } from "../Provider/context.client";
|
|
3
|
+
import { useDeepCompareEffect } from "../../utils/hooks";
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
/**
|
|
6
|
+
* The NostoProduct component must be used to personalise the product page.
|
|
7
|
+
* The component requires that you provide it the identifier of the current product being viewed.
|
|
8
|
+
*
|
|
9
|
+
* By default, your account, when created, has three product-page placements named `productpage-nosto-1`, `productpage-nosto-2` and `productpage-nosto-3`.
|
|
10
|
+
* You may omit these and use any identifier you need.
|
|
11
|
+
* The identifiers used here are simply provided to illustrate the example.
|
|
12
|
+
*
|
|
13
|
+
* The `<NostoProduct \>` component needs to be added after the placements.
|
|
14
|
+
* Content and recommendations will be rendered through this component.
|
|
15
|
+
* Pass in the product ID via the product prop to pass this information back to Nosto.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```
|
|
19
|
+
* <div className="product-page">
|
|
20
|
+
* <NostoPlacement id="productpage-nosto-1" />
|
|
21
|
+
* <NostoPlacement id="productpage-nosto-2" />
|
|
22
|
+
* <NostoPlacement id="productpage-nosto-3" />
|
|
23
|
+
* <NostoProduct product={product.id} />
|
|
24
|
+
* </div>
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* @group Personalisation Components
|
|
28
|
+
*/
|
|
29
|
+
export default function NostoProduct(props: {
|
|
30
|
+
product: string;
|
|
31
|
+
tagging?: Product;
|
|
32
|
+
}): JSX.Element {
|
|
33
|
+
const { product, tagging } = props;
|
|
34
|
+
const {
|
|
35
|
+
clientScriptLoaded,
|
|
36
|
+
currentVariation,
|
|
37
|
+
responseMode,
|
|
38
|
+
recommendationComponent,
|
|
39
|
+
useRenderCampaigns,
|
|
40
|
+
} = useNostoContext();
|
|
11
41
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
} else {
|
|
28
|
-
// @ts-ignore
|
|
29
|
-
renderFunction(data.campaigns);
|
|
30
|
-
}
|
|
31
|
-
});
|
|
32
|
-
},
|
|
33
|
-
[clientScriptLoaded, currentVariation, product, renderFunction]
|
|
34
|
-
);
|
|
42
|
+
const { renderCampaigns, pageTypeUpdated } = useRenderCampaigns("product");
|
|
43
|
+
|
|
44
|
+
useDeepCompareEffect(() => {
|
|
45
|
+
if (clientScriptLoaded && pageTypeUpdated) {
|
|
46
|
+
window.nostojs((api) => {
|
|
47
|
+
api
|
|
48
|
+
.defaultSession()
|
|
49
|
+
.setResponseMode(responseMode)
|
|
50
|
+
.viewProduct(product)
|
|
51
|
+
.setPlacements(api.placements.getPlacements())
|
|
52
|
+
.load()
|
|
53
|
+
.then((data) => {
|
|
54
|
+
renderCampaigns(data, api);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
35
57
|
}
|
|
36
|
-
}
|
|
58
|
+
}, [
|
|
59
|
+
clientScriptLoaded,
|
|
60
|
+
currentVariation,
|
|
61
|
+
product,
|
|
62
|
+
recommendationComponent,
|
|
63
|
+
pageTypeUpdated,
|
|
64
|
+
]);
|
|
37
65
|
|
|
38
66
|
return (
|
|
39
67
|
<>
|
|
@@ -155,6 +183,4 @@ const NostoProduct: React.FC<{ product: string; tagging: Product }> = ({
|
|
|
155
183
|
</div>
|
|
156
184
|
</>
|
|
157
185
|
);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
export default NostoProduct;
|
|
186
|
+
}
|
|
@@ -1,23 +1,40 @@
|
|
|
1
1
|
import { createContext, useContext } from "react";
|
|
2
|
+
import { Recommendation } from "../../types";
|
|
2
3
|
|
|
3
|
-
|
|
4
|
+
/**
|
|
5
|
+
* @group Types
|
|
6
|
+
*/
|
|
7
|
+
export interface NostoContextType {
|
|
4
8
|
account: string;
|
|
5
9
|
clientScriptLoaded: boolean;
|
|
6
10
|
currentVariation: string;
|
|
7
11
|
renderFunction?: Function;
|
|
8
12
|
responseMode: string;
|
|
13
|
+
recommendationComponent?: React.ReactElement<{
|
|
14
|
+
nostoRecommendation: Recommendation;
|
|
15
|
+
}>;
|
|
16
|
+
useRenderCampaigns: Function;
|
|
17
|
+
pageType: string;
|
|
9
18
|
}
|
|
10
19
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
20
|
+
/**
|
|
21
|
+
* @group Essential Functions
|
|
22
|
+
*/
|
|
23
|
+
export const NostoContext = createContext<NostoContextType>({
|
|
24
|
+
account: "",
|
|
15
25
|
currentVariation: "",
|
|
16
|
-
|
|
26
|
+
pageType: "",
|
|
27
|
+
responseMode: "HTML",
|
|
28
|
+
clientScriptLoaded: false,
|
|
29
|
+
useRenderCampaigns: () => undefined,
|
|
17
30
|
});
|
|
18
31
|
|
|
19
|
-
|
|
20
|
-
|
|
32
|
+
/**
|
|
33
|
+
* A hook that allows you to access the NostoContext and retrieve Nosto-related data from it in React components.
|
|
34
|
+
*
|
|
35
|
+
* @group Essential Functions
|
|
36
|
+
*/
|
|
37
|
+
export function useNostoContext(): NostoContextType {
|
|
21
38
|
const context = useContext(NostoContext);
|
|
22
39
|
|
|
23
40
|
if (!context) {
|
|
@@ -1,23 +1,68 @@
|
|
|
1
|
-
import React, { useEffect } from "react";
|
|
1
|
+
import React, { useEffect, isValidElement, useState, useRef } from "react";
|
|
2
2
|
import { NostoContext } from "./context.client";
|
|
3
|
+
import { createRoot } from "react-dom/client";
|
|
4
|
+
import { Recommendation } from "../../types";
|
|
3
5
|
|
|
4
|
-
|
|
6
|
+
/**
|
|
7
|
+
* This widget is what we call the Nosto root widget, which is responsible for adding the actual Nosto script and the JS API stub.
|
|
8
|
+
* This widget wraps all other React Nosto widgets.
|
|
9
|
+
*
|
|
10
|
+
* ```
|
|
11
|
+
* <NostoProvider account="your-nosto-account-id" recommendationComponent={<NostoSlot />}>
|
|
12
|
+
* <App />
|
|
13
|
+
* </NostoProvider>
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* **Note:** the component also accepts a prop to configure the host `host="connect.nosto.com"`.
|
|
17
|
+
* In advanced use-cases, the need to configure the host may surface.
|
|
18
|
+
*
|
|
19
|
+
* In order to implement client-side rendering, the requires a designated component to render the recommendations provided by Nosto.
|
|
20
|
+
* This component should be capable of processing the JSON response received from our backend.
|
|
21
|
+
* Notice the `recommendationComponent` prop passed to `<NostoProvider>` above.
|
|
22
|
+
*
|
|
23
|
+
* 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.
|
|
24
|
+
*
|
|
25
|
+
* @group Essential Functions
|
|
26
|
+
*/
|
|
27
|
+
export default function NostoProvider(props: {
|
|
28
|
+
/**
|
|
29
|
+
* Indicates merchant id
|
|
30
|
+
*/
|
|
5
31
|
account: string;
|
|
6
|
-
|
|
7
|
-
|
|
32
|
+
/**
|
|
33
|
+
* Indicates currency
|
|
34
|
+
*/
|
|
35
|
+
currentVariation?: string;
|
|
36
|
+
/**
|
|
37
|
+
* Indicates an url of a server
|
|
38
|
+
*/
|
|
39
|
+
host?: string;
|
|
8
40
|
children: React.ReactElement;
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
41
|
+
/**
|
|
42
|
+
* Indicates if merchant uses multiple currencies
|
|
43
|
+
*/
|
|
44
|
+
multiCurrency?: boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Recommendation component which holds nostoRecommendation object
|
|
47
|
+
*/
|
|
48
|
+
recommendationComponent?: any;
|
|
49
|
+
/**
|
|
50
|
+
* Enables Shopify markets with language and market id
|
|
51
|
+
*/
|
|
52
|
+
shopifyMarkets?: {
|
|
53
|
+
language?: string;
|
|
54
|
+
marketId?: string | number;
|
|
55
|
+
}
|
|
56
|
+
}): JSX.Element {
|
|
57
|
+
let {
|
|
58
|
+
account,
|
|
59
|
+
currentVariation = "",
|
|
60
|
+
multiCurrency = false,
|
|
61
|
+
host,
|
|
62
|
+
children,
|
|
63
|
+
recommendationComponent,
|
|
64
|
+
shopifyMarkets
|
|
65
|
+
} = props;
|
|
21
66
|
const [clientScriptLoadedState, setClientScriptLoadedState] =
|
|
22
67
|
React.useState(false);
|
|
23
68
|
const clientScriptLoaded = React.useMemo(
|
|
@@ -25,32 +70,139 @@ const NostoProvider: React.FC<NostoProviderProps> = ({
|
|
|
25
70
|
[clientScriptLoadedState]
|
|
26
71
|
);
|
|
27
72
|
|
|
28
|
-
//Set responseMode for loading campaigns:
|
|
29
|
-
const responseMode =
|
|
30
|
-
typeof renderFunction == "function" ? "JSON_ORIGINAL" : "HTML";
|
|
31
|
-
|
|
32
73
|
//Pass currentVariation as empty string if multiCurrency is disabled
|
|
33
74
|
currentVariation = multiCurrency ? currentVariation : "";
|
|
34
75
|
|
|
76
|
+
// Set responseMode for loading campaigns:
|
|
77
|
+
const responseMode = isValidElement(recommendationComponent)
|
|
78
|
+
? "JSON_ORIGINAL"
|
|
79
|
+
: "HTML";
|
|
80
|
+
|
|
81
|
+
// RecommendationComponent for client-side rendering:
|
|
82
|
+
function RecommendationComponentWrapper(props: {
|
|
83
|
+
nostoRecommendation: Recommendation;
|
|
84
|
+
}) {
|
|
85
|
+
return React.cloneElement(recommendationComponent!, {
|
|
86
|
+
nostoRecommendation: props.nostoRecommendation,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// custom hook for rendering campaigns (CSR/SSR):
|
|
91
|
+
const [pageType, setPageType] = useState("");
|
|
92
|
+
const useRenderCampaigns: any = function (type: string = "") {
|
|
93
|
+
const placementRefs: any = useRef({});
|
|
94
|
+
useEffect(() => {
|
|
95
|
+
if (pageType != type) {
|
|
96
|
+
setPageType(type);
|
|
97
|
+
}
|
|
98
|
+
}, []);
|
|
99
|
+
|
|
100
|
+
const pageTypeUpdated = type == pageType;
|
|
101
|
+
|
|
102
|
+
function renderCampaigns(
|
|
103
|
+
data: {
|
|
104
|
+
recommendations: any;
|
|
105
|
+
campaigns: {
|
|
106
|
+
recommendations: {
|
|
107
|
+
[key: string]: any;
|
|
108
|
+
};
|
|
109
|
+
};
|
|
110
|
+
},
|
|
111
|
+
api: {
|
|
112
|
+
placements: {
|
|
113
|
+
injectCampaigns: (recommendations: any) => void;
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
) {
|
|
117
|
+
if (responseMode == "HTML") {
|
|
118
|
+
// inject content campaigns as usual:
|
|
119
|
+
api.placements.injectCampaigns(data.recommendations);
|
|
120
|
+
} else {
|
|
121
|
+
// render recommendation component into placements:
|
|
122
|
+
const recommendations = data.campaigns.recommendations;
|
|
123
|
+
for (const key in recommendations) {
|
|
124
|
+
let recommendation = recommendations[key];
|
|
125
|
+
let placementSelector = "#" + key;
|
|
126
|
+
let placement: Function = () =>
|
|
127
|
+
document.querySelector(placementSelector);
|
|
128
|
+
|
|
129
|
+
if (!!placement()) {
|
|
130
|
+
if (!placementRefs.current[key])
|
|
131
|
+
placementRefs.current[key] = createRoot(placement());
|
|
132
|
+
const root = placementRefs.current[key];
|
|
133
|
+
root.render(
|
|
134
|
+
<RecommendationComponentWrapper
|
|
135
|
+
nostoRecommendation={recommendation}
|
|
136
|
+
></RecommendationComponentWrapper>
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return { renderCampaigns, pageTypeUpdated };
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
|
|
35
146
|
useEffect(() => {
|
|
36
|
-
if (!
|
|
147
|
+
if (!window.nostojs) {
|
|
148
|
+
window.nostojs = (cb: Function) => {
|
|
149
|
+
(window.nostojs.q = window.nostojs.q || []).push(cb);
|
|
150
|
+
};
|
|
151
|
+
window.nostojs((api) => api.setAutoLoad(false));
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (!document.querySelectorAll("[nosto-client-script]").length && !shopifyMarkets) {
|
|
37
155
|
const script = document.createElement("script");
|
|
38
156
|
script.type = "text/javascript";
|
|
39
157
|
script.src = "//" + (host || "connect.nosto.com") + "/include/" + account;
|
|
40
158
|
script.async = true;
|
|
41
159
|
script.setAttribute("nosto-client-script", "");
|
|
160
|
+
|
|
42
161
|
script.onload = () => {
|
|
43
|
-
|
|
162
|
+
if (typeof jest !== "undefined") {
|
|
163
|
+
window.nosto?.reload({
|
|
164
|
+
site: "localhost",
|
|
165
|
+
});
|
|
166
|
+
}
|
|
44
167
|
setClientScriptLoadedState(true);
|
|
45
168
|
};
|
|
46
|
-
document.
|
|
169
|
+
document.body.appendChild(script);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
//Enable Shopify markets functionality:
|
|
173
|
+
if (!!shopifyMarkets) {
|
|
174
|
+
|
|
175
|
+
const existingScript = document.querySelector("[nosto-client-script]");
|
|
176
|
+
const nostoSandbox = document.querySelector('#nosto-sandbox');
|
|
177
|
+
|
|
178
|
+
if (!existingScript || existingScript?.getAttribute('nosto-language') != shopifyMarkets?.language || existingScript?.getAttribute('nosto-market-id') != shopifyMarkets?.marketId) {
|
|
179
|
+
if (clientScriptLoadedState) { setClientScriptLoadedState(false) };
|
|
180
|
+
|
|
181
|
+
existingScript?.parentNode?.removeChild(existingScript)
|
|
182
|
+
nostoSandbox?.parentNode?.removeChild(nostoSandbox)
|
|
183
|
+
|
|
184
|
+
const script = document.createElement("script");
|
|
185
|
+
script.type = "text/javascript";
|
|
186
|
+
script.src = "//" + (host || "connect.nosto.com") + `/script/shopify/market/nosto.js?merchant=${account}&market=${shopifyMarkets.marketId || ''}&locale=${shopifyMarkets?.language?.toLowerCase() || ''}`
|
|
187
|
+
script.async = true;
|
|
188
|
+
script.setAttribute("nosto-client-script", "");
|
|
189
|
+
script.setAttribute("nosto-language", shopifyMarkets?.language || '');
|
|
190
|
+
script.setAttribute("nosto-market-id", String(shopifyMarkets?.marketId));
|
|
191
|
+
|
|
192
|
+
script.onload = () => {
|
|
193
|
+
if (typeof jest !== "undefined") {
|
|
194
|
+
window.nosto?.reload({
|
|
195
|
+
site: "localhost",
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
setClientScriptLoadedState(true);
|
|
199
|
+
};
|
|
200
|
+
document.body.appendChild(script);
|
|
201
|
+
}
|
|
202
|
+
|
|
47
203
|
}
|
|
48
204
|
|
|
49
|
-
|
|
50
|
-
(window.nostojs.q = window.nostojs.q || []).push(cb);
|
|
51
|
-
// @ts-ignore
|
|
52
|
-
window.nostojs((api) => api.setAutoLoad(false));
|
|
53
|
-
}, []);
|
|
205
|
+
}, [clientScriptLoadedState, shopifyMarkets]);
|
|
54
206
|
|
|
55
207
|
return (
|
|
56
208
|
<NostoContext.Provider
|
|
@@ -58,13 +210,13 @@ const NostoProvider: React.FC<NostoProviderProps> = ({
|
|
|
58
210
|
account,
|
|
59
211
|
clientScriptLoaded,
|
|
60
212
|
currentVariation,
|
|
61
|
-
renderFunction,
|
|
62
213
|
responseMode,
|
|
214
|
+
recommendationComponent,
|
|
215
|
+
useRenderCampaigns,
|
|
216
|
+
pageType,
|
|
63
217
|
}}
|
|
64
218
|
>
|
|
65
219
|
{children}
|
|
66
220
|
</NostoContext.Provider>
|
|
67
221
|
);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export default NostoProvider;
|
|
222
|
+
}
|
|
@@ -1,14 +1,44 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { useEffect } from "react";
|
|
2
2
|
import { useNostoContext } from "../Provider/context.client";
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
/**
|
|
5
|
+
* You can personalise your search pages by using the NostoSearch component.
|
|
6
|
+
* The component requires that you provide it the current search term.
|
|
7
|
+
*
|
|
8
|
+
* By default, your account, when created, has two search-page placements named `searchpage-nosto-1` and `searchpage-nosto-2`.
|
|
9
|
+
* You may omit these and use any identifier you need. The identifiers used here are simply provided to illustrate the example.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```
|
|
13
|
+
* <div className="search-page">
|
|
14
|
+
* <NostoPlacement id="searchpage-nosto-1" />
|
|
15
|
+
* <NostoPlacement id="searchpage-nosto-2" />
|
|
16
|
+
* <NostoSearch query={"black shoes"} />
|
|
17
|
+
* </div>
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* **Note:** Do not encode the search term in any way.
|
|
21
|
+
* It should be provided an unencoded string.
|
|
22
|
+
* A query for `black shoes` must be provided as-is and not as `black+shoes`.
|
|
23
|
+
* Doing so will lead to invalid results.
|
|
24
|
+
*
|
|
25
|
+
* @group Personalisation Components
|
|
26
|
+
*/
|
|
27
|
+
export default function NostoSearch(props: { query: string }): JSX.Element {
|
|
28
|
+
const { query } = props;
|
|
29
|
+
const {
|
|
30
|
+
clientScriptLoaded,
|
|
31
|
+
currentVariation,
|
|
32
|
+
responseMode,
|
|
33
|
+
recommendationComponent,
|
|
34
|
+
useRenderCampaigns,
|
|
35
|
+
} = useNostoContext();
|
|
36
|
+
|
|
37
|
+
const { renderCampaigns, pageTypeUpdated } = useRenderCampaigns("search");
|
|
7
38
|
|
|
8
39
|
useEffect(() => {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
window.nostojs((api: any) => {
|
|
40
|
+
if (clientScriptLoaded && pageTypeUpdated) {
|
|
41
|
+
window.nostojs((api) => {
|
|
12
42
|
api
|
|
13
43
|
.defaultSession()
|
|
14
44
|
.setVariation(currentVariation)
|
|
@@ -16,18 +46,18 @@ const NostoSearch: React.FC<{ query: string }> = ({ query }) => {
|
|
|
16
46
|
.viewSearch(query)
|
|
17
47
|
.setPlacements(api.placements.getPlacements())
|
|
18
48
|
.load()
|
|
19
|
-
.then((data
|
|
20
|
-
|
|
21
|
-
// @ts-ignore
|
|
22
|
-
api.placements.injectCampaigns(data.recommendations);
|
|
23
|
-
} else {
|
|
24
|
-
// @ts-ignore
|
|
25
|
-
renderFunction(data.campaigns);
|
|
26
|
-
}
|
|
49
|
+
.then((data) => {
|
|
50
|
+
renderCampaigns(data, api);
|
|
27
51
|
});
|
|
28
52
|
});
|
|
29
53
|
}
|
|
30
|
-
}, [
|
|
54
|
+
}, [
|
|
55
|
+
clientScriptLoaded,
|
|
56
|
+
currentVariation,
|
|
57
|
+
query,
|
|
58
|
+
recommendationComponent,
|
|
59
|
+
pageTypeUpdated,
|
|
60
|
+
]);
|
|
31
61
|
|
|
32
62
|
return (
|
|
33
63
|
<>
|
|
@@ -39,6 +69,4 @@ const NostoSearch: React.FC<{ query: string }> = ({ query }) => {
|
|
|
39
69
|
</div>
|
|
40
70
|
</>
|
|
41
71
|
);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export default NostoSearch;
|
|
72
|
+
}
|
|
@@ -1,24 +1,31 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import snakeize from "snakeize";
|
|
3
1
|
import { useNostoContext } from "../Provider/context.client";
|
|
4
|
-
|
|
5
|
-
import useDeepCompareEffect from "use-deep-compare-effect";
|
|
6
2
|
import { Cart, Customer } from "../../types";
|
|
3
|
+
import { snakeize } from "../../utils/snakeize";
|
|
4
|
+
import { useDeepCompareEffect } from "../../utils/hooks";
|
|
7
5
|
|
|
8
|
-
|
|
6
|
+
/**
|
|
7
|
+
* 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.
|
|
8
|
+
* This makes it easier to add attribution.
|
|
9
|
+
*
|
|
10
|
+
* The `NostoSession` component makes it very easy to keep the session up to date so long as the cart and the customer are provided.
|
|
11
|
+
*
|
|
12
|
+
* 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`.
|
|
13
|
+
*
|
|
14
|
+
* @group Essential Functions
|
|
15
|
+
*/
|
|
16
|
+
export default function NostoSession(props: {
|
|
9
17
|
cart: Cart;
|
|
10
18
|
customer: Customer;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const NostoSession: React.FC<NostoSessionProps> = ({ cart, customer }) => {
|
|
19
|
+
}): JSX.Element {
|
|
20
|
+
const { cart, customer } = props;
|
|
14
21
|
const { clientScriptLoaded } = useNostoContext();
|
|
22
|
+
|
|
15
23
|
useDeepCompareEffect(() => {
|
|
16
|
-
const currentCart = cart ? snakeize(cart) : undefined;
|
|
24
|
+
const currentCart = cart ? snakeize(cart) : undefined;
|
|
17
25
|
const currentCustomer = customer ? snakeize(customer) : undefined;
|
|
18
26
|
|
|
19
|
-
// @ts-ignore
|
|
20
27
|
if (clientScriptLoaded) {
|
|
21
|
-
window.nostojs((api
|
|
28
|
+
window.nostojs((api) => {
|
|
22
29
|
api
|
|
23
30
|
.defaultSession()
|
|
24
31
|
.setResponseMode("HTML")
|
|
@@ -28,9 +35,7 @@ const NostoSession: React.FC<NostoSessionProps> = ({ cart, customer }) => {
|
|
|
28
35
|
.load();
|
|
29
36
|
});
|
|
30
37
|
}
|
|
31
|
-
}, [clientScriptLoaded, cart
|
|
38
|
+
}, [clientScriptLoaded, cart, customer]);
|
|
32
39
|
|
|
33
40
|
return <></>;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export default NostoSession;
|
|
41
|
+
}
|
package/src/index.client.ts
CHANGED
|
@@ -1,12 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
nostojs: any;
|
|
5
|
-
nosto: any;
|
|
6
|
-
}
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export * from "./types";
|
|
1
|
+
export type {
|
|
2
|
+
Buyer, Cart, Customer, Item, Product, Purchase, Recommendation, SKU
|
|
3
|
+
} from "./types";
|
|
10
4
|
// noinspection JSUnusedGlobalSymbols
|
|
11
5
|
export { default as Nosto404 } from "./components/Fohofo/index.client";
|
|
12
6
|
// noinspection JSUnusedGlobalSymbols
|
|
@@ -32,6 +26,8 @@ export {
|
|
|
32
26
|
NostoContext,
|
|
33
27
|
useNostoContext,
|
|
34
28
|
} from "./components/Provider/context.client";
|
|
29
|
+
export type {
|
|
30
|
+
NostoContextType,
|
|
31
|
+
} from "./components/Provider/context.client";
|
|
35
32
|
// noinspection JSUnusedGlobalSymbols
|
|
36
|
-
export { default as NostoSession } from "./components/Session/index.client";
|
|
37
|
-
//
|
|
33
|
+
export { default as NostoSession } from "./components/Session/index.client";
|