@tapcart/mobile-components 0.7.26 → 0.7.28
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/dist/components/hooks/use-debug-dependencies.d.ts +10 -0
- package/dist/components/hooks/use-debug-dependencies.d.ts.map +1 -0
- package/dist/components/hooks/use-debug-dependencies.js +17 -0
- package/dist/components/hooks/use-layout.d.ts +13 -0
- package/dist/components/hooks/use-layout.d.ts.map +1 -0
- package/dist/components/hooks/use-layout.js +23 -0
- package/dist/components/hooks/use-nosto-recommendation.d.ts +9 -1
- package/dist/components/hooks/use-nosto-recommendation.d.ts.map +1 -1
- package/dist/components/hooks/use-nosto-recommendation.js +138 -53
- package/dist/components/hooks/use-nosto-recommendation.queries.d.ts +48 -0
- package/dist/components/hooks/use-nosto-recommendation.queries.d.ts.map +1 -0
- package/dist/components/hooks/use-nosto-recommendation.queries.js +143 -0
- package/dist/components/hooks/use-nosto-recommendation.test.d.ts +2 -0
- package/dist/components/hooks/use-nosto-recommendation.test.d.ts.map +1 -0
- package/dist/components/hooks/use-nosto-recommendation.test.js +248 -0
- package/dist/components/hooks/use-products.d.ts +2 -0
- package/dist/components/hooks/use-products.d.ts.map +1 -1
- package/dist/components/hooks/use-products.js +40 -5
- package/dist/components/hooks/use-recommendations.d.ts.map +1 -1
- package/dist/components/hooks/use-recommendations.js +1 -2
- package/dist/components/ui/drawer.js +1 -1
- package/dist/styles.css +0 -3
- package/package.json +4 -2
- package/dist/components/hooks/use-tap.d.ts +0 -8
- package/dist/components/hooks/use-tap.d.ts.map +0 -1
- package/dist/components/hooks/use-tap.js +0 -100
- package/dist/jest.config.d.ts +0 -1
- package/dist/jest.config.d.ts.map +0 -1
- package/dist/jest.config.js +0 -1
- package/dist/lib/__tests__/utils.test.d.ts +0 -1
- package/dist/lib/__tests__/utils.test.d.ts.map +0 -1
- package/dist/lib/__tests__/utils.test.js +0 -1
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom hook to debug dependency changes.
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* useDebugDependencies([dependencyA, dependencyB]);
|
|
6
|
+
*
|
|
7
|
+
* @param {Array} deps - Array of dependencies to monitor for changes.
|
|
8
|
+
*/
|
|
9
|
+
export declare const useDebugDependencies: (deps: any[]) => void;
|
|
10
|
+
//# sourceMappingURL=use-debug-dependencies.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-debug-dependencies.d.ts","sourceRoot":"","sources":["../../../components/hooks/use-debug-dependencies.ts"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB,SAAU,GAAG,EAAE,SAU/C,CAAA"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { useRef, useEffect } from "react";
|
|
2
|
+
/**
|
|
3
|
+
* Custom hook to debug dependency changes.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* useDebugDependencies([dependencyA, dependencyB]);
|
|
7
|
+
*
|
|
8
|
+
* @param {Array} deps - Array of dependencies to monitor for changes.
|
|
9
|
+
*/
|
|
10
|
+
export const useDebugDependencies = (deps) => {
|
|
11
|
+
const prevDeps = useRef(deps);
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
const changedDeps = deps.map((dep, i) => dep !== prevDeps.current[i] ? dep : null);
|
|
14
|
+
console.log("Changed dependencies:", changedDeps);
|
|
15
|
+
prevDeps.current = deps;
|
|
16
|
+
}, [deps]);
|
|
17
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { PhoenixLayout } from "lib/tapcart/types";
|
|
2
|
+
type UseLayoutProps = {
|
|
3
|
+
appId: string;
|
|
4
|
+
layoutId: string;
|
|
5
|
+
};
|
|
6
|
+
type UseLayoutReturn = {
|
|
7
|
+
layout: PhoenixLayout | null;
|
|
8
|
+
error: any;
|
|
9
|
+
isLoading: boolean;
|
|
10
|
+
};
|
|
11
|
+
export declare function useLayout({ appId, layoutId, }: UseLayoutProps): UseLayoutReturn;
|
|
12
|
+
export {};
|
|
13
|
+
//# sourceMappingURL=use-layout.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-layout.d.ts","sourceRoot":"","sources":["../../../components/hooks/use-layout.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AAGjD,KAAK,cAAc,GAAG;IACpB,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;CACjB,CAAA;AAED,KAAK,eAAe,GAAG;IACrB,MAAM,EAAE,aAAa,GAAG,IAAI,CAAA;IAC5B,KAAK,EAAE,GAAG,CAAA;IACV,SAAS,EAAE,OAAO,CAAA;CACnB,CAAA;AAED,wBAAgB,SAAS,CAAC,EACxB,KAAK,EACL,QAAQ,GACT,EAAE,cAAc,GAAG,eAAe,CAYlC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
import useSWR from "swr";
|
|
12
|
+
import { fetchLayoutById } from "apps/tapcart-ssr-app/lib/tapcart/index";
|
|
13
|
+
export function useLayout({ appId, layoutId, }) {
|
|
14
|
+
const fetcher = (appId, layoutId) => __awaiter(this, void 0, void 0, function* () {
|
|
15
|
+
return fetchLayoutById(appId, layoutId);
|
|
16
|
+
});
|
|
17
|
+
const { data, error } = useSWR([appId, layoutId], fetcher);
|
|
18
|
+
return {
|
|
19
|
+
layout: data || null,
|
|
20
|
+
error,
|
|
21
|
+
isLoading: !data && !error,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import { Integrations } from "app-studio-types";
|
|
2
|
-
export declare
|
|
2
|
+
export declare enum NostoRecommendationsType {
|
|
3
|
+
BOUGHT_TOGETHER = "BOUGHT_TOGETHER",
|
|
4
|
+
VIEWED_TOGETHER = "VIEWED_TOGETHER",
|
|
5
|
+
VIEWED_TOGETHER_SMART = "VIEWED_TOGETHER_SMART",
|
|
6
|
+
DEFAULT_SUM = "DEFAULT_SUM",
|
|
7
|
+
BEST_SELLERS = "BEST_SELLERS",
|
|
8
|
+
VIEWED_CATEGORY = "VIEWED_CATEGORY"
|
|
9
|
+
}
|
|
10
|
+
export declare function useNostoRecommendations(integrations: Integrations, baseURL: string, slotId?: string, recommendationsType?: NostoRecommendationsType | "", productIds?: string[], skip?: boolean): {
|
|
3
11
|
recommendations: import("app-studio-types").Product[];
|
|
4
12
|
isLoading: boolean;
|
|
5
13
|
error: any;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-nosto-recommendation.d.ts","sourceRoot":"","sources":["../../../components/hooks/use-nosto-recommendation.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;
|
|
1
|
+
{"version":3,"file":"use-nosto-recommendation.d.ts","sourceRoot":"","sources":["../../../components/hooks/use-nosto-recommendation.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAe/C,oBAAY,wBAAwB;IAClC,eAAe,oBAAoB;IACnC,eAAe,oBAAoB;IACnC,qBAAqB,0BAA0B;IAC/C,WAAW,gBAAgB;IAC3B,YAAY,iBAAiB;IAC7B,eAAe,oBAAoB;CACpC;AAyRD,wBAAgB,uBAAuB,CACrC,YAAY,EAAE,YAAY,EAC1B,OAAO,EAAE,MAAM,EACf,MAAM,SAAK,EACX,mBAAmB,CAAC,EAAE,wBAAwB,GAAG,EAAE,EACnD,UAAU,CAAC,EAAE,MAAM,EAAE,EACrB,IAAI,CAAC,EAAE,OAAO;;;;;EAiCf"}
|
|
@@ -10,74 +10,147 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
10
10
|
};
|
|
11
11
|
import { useState, useEffect } from "react";
|
|
12
12
|
import { useProducts } from "./use-products";
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
import { bestSellersRecommendationsQuery, categoryPageRecommendationsQuery, getFrontPageRecommendationsQuery, getProductPageRecommendationsQuery, getRelatedProductsRecommendationsQuery, newSessionQuery, } from "./use-nosto-recommendation.queries";
|
|
14
|
+
export var NostoRecommendationsType;
|
|
15
|
+
(function (NostoRecommendationsType) {
|
|
16
|
+
NostoRecommendationsType["BOUGHT_TOGETHER"] = "BOUGHT_TOGETHER";
|
|
17
|
+
NostoRecommendationsType["VIEWED_TOGETHER"] = "VIEWED_TOGETHER";
|
|
18
|
+
NostoRecommendationsType["VIEWED_TOGETHER_SMART"] = "VIEWED_TOGETHER_SMART";
|
|
19
|
+
NostoRecommendationsType["DEFAULT_SUM"] = "DEFAULT_SUM";
|
|
20
|
+
NostoRecommendationsType["BEST_SELLERS"] = "BEST_SELLERS";
|
|
21
|
+
NostoRecommendationsType["VIEWED_CATEGORY"] = "VIEWED_CATEGORY";
|
|
22
|
+
})(NostoRecommendationsType || (NostoRecommendationsType = {}));
|
|
23
|
+
const fetchNosto = (nostoKey, graphqlBody, mode) => __awaiter(void 0, void 0, void 0, function* () {
|
|
17
24
|
const url = "https://api.nosto.com/v1/graphql";
|
|
18
25
|
const headers = new Headers({
|
|
19
26
|
"Content-Type": "application/json",
|
|
20
|
-
Authorization: "Basic " + btoa(":" +
|
|
21
|
-
});
|
|
22
|
-
// Fetch sessionId
|
|
23
|
-
const sessionBody = JSON.stringify({
|
|
24
|
-
query: `mutation { newSession }`,
|
|
27
|
+
Authorization: "Basic " + btoa(":" + nostoKey),
|
|
25
28
|
});
|
|
26
|
-
const
|
|
29
|
+
const response = yield fetch(url, {
|
|
27
30
|
method: "POST",
|
|
28
31
|
headers: headers,
|
|
29
|
-
|
|
32
|
+
mode: mode,
|
|
33
|
+
body: graphqlBody,
|
|
30
34
|
});
|
|
31
|
-
const
|
|
35
|
+
const json = yield response.json();
|
|
36
|
+
return json;
|
|
37
|
+
});
|
|
38
|
+
const getProductIdsFromPageResponse = (pageResponse) => {
|
|
39
|
+
return ((pageResponse === null || pageResponse === void 0 ? void 0 : pageResponse.flatMap((page) => page.primary).filter((item) => item && item.productId).map((item) => item.productId)) || []);
|
|
40
|
+
};
|
|
41
|
+
const fetchFrontPageRecommendations = (nostoKey, slotId) => __awaiter(void 0, void 0, void 0, function* () {
|
|
42
|
+
var _a, _b, _c;
|
|
43
|
+
// Fetch sessionId
|
|
44
|
+
const sessionData = yield fetchNosto(nostoKey, newSessionQuery);
|
|
32
45
|
const sessionId = sessionData.data.newSession;
|
|
33
46
|
// Main recommendation query
|
|
34
|
-
const body =
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
47
|
+
const body = getFrontPageRecommendationsQuery(sessionId, slotId);
|
|
48
|
+
const response = yield fetchNosto(nostoKey, body, "cors");
|
|
49
|
+
return getProductIdsFromPageResponse((_c = (_b = (_a = response.data) === null || _a === void 0 ? void 0 : _a.updateSession) === null || _b === void 0 ? void 0 : _b.pages) === null || _c === void 0 ? void 0 : _c.forFrontPage);
|
|
50
|
+
});
|
|
51
|
+
const fetchProductPageRecommendations = (nostoKey, productId, slotId) => __awaiter(void 0, void 0, void 0, function* () {
|
|
52
|
+
var _d, _e, _f;
|
|
53
|
+
const sessionData = yield fetchNosto(nostoKey, newSessionQuery);
|
|
54
|
+
const sessionId = sessionData.data.newSession;
|
|
55
|
+
const body = getProductPageRecommendationsQuery(sessionId, productId, slotId);
|
|
56
|
+
const response = yield fetchNosto(nostoKey, body, "cors");
|
|
57
|
+
return getProductIdsFromPageResponse((_f = (_e = (_d = response.data) === null || _d === void 0 ? void 0 : _d.updateSession) === null || _e === void 0 ? void 0 : _e.pages) === null || _f === void 0 ? void 0 : _f.forProductPage);
|
|
58
|
+
});
|
|
59
|
+
const fetchCategoryPageRecommendations = (nostoKey, sourceCollectionTitle) => __awaiter(void 0, void 0, void 0, function* () {
|
|
60
|
+
var _g, _h, _j;
|
|
61
|
+
const sessionData = yield fetchNosto(nostoKey, newSessionQuery);
|
|
62
|
+
const sessionId = sessionData.data.newSession;
|
|
63
|
+
const body = categoryPageRecommendationsQuery(sessionId, sourceCollectionTitle);
|
|
64
|
+
const response = yield fetchNosto(nostoKey, body, "cors");
|
|
65
|
+
return getProductIdsFromPageResponse((_j = (_h = (_g = response.data) === null || _g === void 0 ? void 0 : _g.updateSession) === null || _h === void 0 ? void 0 : _h.pages) === null || _j === void 0 ? void 0 : _j.forCategoryPage);
|
|
66
|
+
});
|
|
67
|
+
const fetchBestSellersRecommendations = (nostoKey) => __awaiter(void 0, void 0, void 0, function* () {
|
|
68
|
+
var _k, _l, _m, _o;
|
|
69
|
+
const body = bestSellersRecommendationsQuery;
|
|
70
|
+
const response = yield fetchNosto(nostoKey, body, "cors");
|
|
71
|
+
const productIds = ((_o = (_m = (_l = (_k = response === null || response === void 0 ? void 0 : response.data) === null || _k === void 0 ? void 0 : _k.recos) === null || _l === void 0 ? void 0 : _l.toplist) === null || _m === void 0 ? void 0 : _m.primary) === null || _o === void 0 ? void 0 : _o.map((item) => item.productId)) || [];
|
|
72
|
+
return productIds;
|
|
73
|
+
});
|
|
74
|
+
const fetchRelatedProductsRecommendations = (nostoKey, recommendationsType, productIds) => __awaiter(void 0, void 0, void 0, function* () {
|
|
75
|
+
var _p, _q, _r, _s;
|
|
76
|
+
const body = getRelatedProductsRecommendationsQuery(recommendationsType, productIds);
|
|
77
|
+
const response = yield fetchNosto(nostoKey, body, "cors");
|
|
78
|
+
const recommendedProductIds = ((_s = (_r = (_q = (_p = response === null || response === void 0 ? void 0 : response.data) === null || _p === void 0 ? void 0 : _p.recos) === null || _q === void 0 ? void 0 : _q.related) === null || _r === void 0 ? void 0 : _r.primary) === null || _s === void 0 ? void 0 : _s.map((item) => item.productId)) || [];
|
|
79
|
+
return recommendedProductIds;
|
|
80
|
+
});
|
|
81
|
+
const fetchPDPFallbackRecommendations = (nostoKey, productId, recommendationsType = NostoRecommendationsType.BOUGHT_TOGETHER) => __awaiter(void 0, void 0, void 0, function* () {
|
|
82
|
+
switch (recommendationsType) {
|
|
83
|
+
case NostoRecommendationsType.VIEWED_CATEGORY:
|
|
84
|
+
return fetchCategoryPageRecommendations(nostoKey, productId);
|
|
85
|
+
case NostoRecommendationsType.BEST_SELLERS:
|
|
86
|
+
return fetchBestSellersRecommendations(nostoKey);
|
|
87
|
+
default:
|
|
88
|
+
return fetchRelatedProductsRecommendations(nostoKey, recommendationsType, [productId]);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
const fetchHomeRecommendations = (nostoKey, slotId) => __awaiter(void 0, void 0, void 0, function* () {
|
|
92
|
+
let productIdRecommendations = [];
|
|
93
|
+
if (slotId) {
|
|
94
|
+
try {
|
|
95
|
+
productIdRecommendations = yield fetchFrontPageRecommendations(nostoKey, slotId);
|
|
96
|
+
if (productIdRecommendations.length === 0) {
|
|
97
|
+
productIdRecommendations = yield fetchBestSellersRecommendations(nostoKey);
|
|
55
98
|
}
|
|
56
|
-
}
|
|
57
99
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
});
|
|
67
|
-
const json = yield response.json();
|
|
68
|
-
return json;
|
|
100
|
+
catch (error) {
|
|
101
|
+
productIdRecommendations = yield fetchBestSellersRecommendations(nostoKey);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
productIdRecommendations = yield fetchBestSellersRecommendations(nostoKey);
|
|
106
|
+
}
|
|
107
|
+
return productIdRecommendations;
|
|
69
108
|
});
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
109
|
+
const fetchPDPRecommendations = (nostoKey, productId, slotId, recommendationsType = NostoRecommendationsType.BOUGHT_TOGETHER) => __awaiter(void 0, void 0, void 0, function* () {
|
|
110
|
+
let productIdRecommendations = [];
|
|
111
|
+
if (slotId) {
|
|
112
|
+
try {
|
|
113
|
+
productIdRecommendations = yield fetchProductPageRecommendations(nostoKey, productId, slotId);
|
|
114
|
+
if (productIdRecommendations.length === 0) {
|
|
115
|
+
productIdRecommendations = yield fetchPDPFallbackRecommendations(nostoKey, productId, recommendationsType);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
productIdRecommendations = yield fetchPDPFallbackRecommendations(nostoKey, productId, recommendationsType);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
productIdRecommendations = yield fetchPDPFallbackRecommendations(nostoKey, productId, recommendationsType);
|
|
124
|
+
}
|
|
125
|
+
return productIdRecommendations;
|
|
126
|
+
});
|
|
127
|
+
const getProductIdRecommendations = (nostoKey, slotId = "", recommendationsType, productIds, skip) => __awaiter(void 0, void 0, void 0, function* () {
|
|
128
|
+
if (skip) {
|
|
129
|
+
return [];
|
|
130
|
+
}
|
|
131
|
+
const isPDP = productIds && productIds.length > 0;
|
|
132
|
+
if (isPDP) {
|
|
133
|
+
return fetchPDPRecommendations(nostoKey, productIds[0], slotId, recommendationsType);
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
return fetchHomeRecommendations(nostoKey, slotId);
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
function useNostoRecommendationsProductIds(nostoKey, slotId = "", recommendationsType, productIds, skip) {
|
|
140
|
+
const [recommendedProductIds, setRecommendedProductIds] = useState([]);
|
|
73
141
|
const [isLoading, setIsLoading] = useState(true);
|
|
74
142
|
const [error, setError] = useState(null);
|
|
75
143
|
useEffect(() => {
|
|
76
144
|
function fetchNostoData() {
|
|
77
145
|
return __awaiter(this, void 0, void 0, function* () {
|
|
78
146
|
try {
|
|
79
|
-
|
|
80
|
-
|
|
147
|
+
if (!nostoKey) {
|
|
148
|
+
setRecommendedProductIds([]);
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
const productIdRecommendations = yield getProductIdRecommendations(nostoKey, slotId, recommendationsType, productIds, skip);
|
|
152
|
+
setRecommendedProductIds(productIdRecommendations);
|
|
153
|
+
}
|
|
81
154
|
}
|
|
82
155
|
catch (err) {
|
|
83
156
|
console.error("Error fetching recommendations:", err);
|
|
@@ -89,10 +162,22 @@ export function useNostoRecommendations(integrations, baseURL, slotId = null) {
|
|
|
89
162
|
});
|
|
90
163
|
}
|
|
91
164
|
fetchNostoData();
|
|
92
|
-
}, [
|
|
93
|
-
|
|
165
|
+
}, [nostoKey, slotId, recommendationsType, productIds, skip]);
|
|
166
|
+
return {
|
|
167
|
+
recommendedProductIds,
|
|
168
|
+
isLoading,
|
|
169
|
+
error,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
export function useNostoRecommendations(integrations, baseURL, slotId = "", recommendationsType, productIds, skip) {
|
|
173
|
+
var _a;
|
|
174
|
+
if (recommendationsType === "") {
|
|
175
|
+
recommendationsType = NostoRecommendationsType.BOUGHT_TOGETHER;
|
|
176
|
+
}
|
|
177
|
+
const nostoKey = (_a = integrations.find((integration) => integration.name === "nosto")) === null || _a === void 0 ? void 0 : _a.key;
|
|
178
|
+
const { recommendedProductIds, isLoading, error } = useNostoRecommendationsProductIds(nostoKey, slotId, recommendationsType, productIds, skip);
|
|
94
179
|
const { products, error: productsError, isLoading: productsLoading, } = useProducts({
|
|
95
|
-
productIds,
|
|
180
|
+
productIds: recommendedProductIds,
|
|
96
181
|
baseURL,
|
|
97
182
|
productHandles: [],
|
|
98
183
|
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { NostoRecommendationsType } from "./use-nosto-recommendation";
|
|
2
|
+
type NostoPageResponse<T extends string> = {
|
|
3
|
+
data?: {
|
|
4
|
+
updateSession?: {
|
|
5
|
+
pages?: {
|
|
6
|
+
[K in T]: Array<{
|
|
7
|
+
primary: Array<{
|
|
8
|
+
productId: string;
|
|
9
|
+
}>;
|
|
10
|
+
}>;
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
export type NostoFrontPageResponse = NostoPageResponse<"forFrontPage">;
|
|
16
|
+
export type NostoProductPageResponse = NostoPageResponse<"forProductPage">;
|
|
17
|
+
export type NostoCategoryPageResponse = NostoPageResponse<"forCategoryPage">;
|
|
18
|
+
export type NostoBestSellersResponse = {
|
|
19
|
+
data?: {
|
|
20
|
+
recos?: {
|
|
21
|
+
toplist?: {
|
|
22
|
+
primary: Array<{
|
|
23
|
+
name: string;
|
|
24
|
+
productId: string;
|
|
25
|
+
}>;
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
export type NostoRelatedProductsResponse = {
|
|
31
|
+
data?: {
|
|
32
|
+
recos?: {
|
|
33
|
+
related?: {
|
|
34
|
+
primary: Array<{
|
|
35
|
+
productId: string;
|
|
36
|
+
}>;
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
export declare const getFrontPageRecommendationsQuery: (sessionId: string, slotId?: string) => string;
|
|
42
|
+
export declare const getProductPageRecommendationsQuery: (sessionId: string, productId: string, slotId?: string) => string;
|
|
43
|
+
export declare const categoryPageRecommendationsQuery: (sessionId: string, sourceCollectionTitle: string) => string;
|
|
44
|
+
export declare const bestSellersRecommendationsQuery: string;
|
|
45
|
+
export declare const getRelatedProductsRecommendationsQuery: (recommendationsType: NostoRecommendationsType, productIds: string[]) => string;
|
|
46
|
+
export declare const newSessionQuery: string;
|
|
47
|
+
export {};
|
|
48
|
+
//# sourceMappingURL=use-nosto-recommendation.queries.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-nosto-recommendation.queries.d.ts","sourceRoot":"","sources":["../../../components/hooks/use-nosto-recommendation.queries.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAA;AAErE,KAAK,iBAAiB,CAAC,CAAC,SAAS,MAAM,IAAI;IACzC,IAAI,CAAC,EAAE;QACL,aAAa,CAAC,EAAE;YACd,KAAK,CAAC,EAAE;iBACL,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC;oBACd,OAAO,EAAE,KAAK,CAAC;wBAAE,SAAS,EAAE,MAAM,CAAA;qBAAE,CAAC,CAAA;iBACtC,CAAC;aACH,CAAA;SACF,CAAA;KACF,CAAA;CACF,CAAA;AAED,MAAM,MAAM,sBAAsB,GAAG,iBAAiB,CAAC,cAAc,CAAC,CAAA;AACtE,MAAM,MAAM,wBAAwB,GAAG,iBAAiB,CAAC,gBAAgB,CAAC,CAAA;AAC1E,MAAM,MAAM,yBAAyB,GAAG,iBAAiB,CAAC,iBAAiB,CAAC,CAAA;AAE5E,MAAM,MAAM,wBAAwB,GAAG;IACrC,IAAI,CAAC,EAAE;QACL,KAAK,CAAC,EAAE;YACN,OAAO,CAAC,EAAE;gBACR,OAAO,EAAE,KAAK,CAAC;oBAAE,IAAI,EAAE,MAAM,CAAC;oBAAC,SAAS,EAAE,MAAM,CAAA;iBAAE,CAAC,CAAA;aACpD,CAAA;SACF,CAAA;KACF,CAAA;CACF,CAAA;AACD,MAAM,MAAM,4BAA4B,GAAG;IACzC,IAAI,CAAC,EAAE;QACL,KAAK,CAAC,EAAE;YACN,OAAO,CAAC,EAAE;gBACR,OAAO,EAAE,KAAK,CAAC;oBAAE,SAAS,EAAE,MAAM,CAAA;iBAAE,CAAC,CAAA;aACtC,CAAA;SACF,CAAA;KACF,CAAA;CACF,CAAA;AAGD,eAAO,MAAM,gCAAgC,cAAe,MAAM,WAAW,MAAM,WAiC3E,CAAA;AAER,eAAO,MAAM,kCAAkC,cAAe,MAAM,aAAa,MAAM,WAAW,MAAM,WAqChG,CAAA;AAER,eAAO,MAAM,gCAAgC,cAAe,MAAM,yBAAyB,MAAM,WAqCzF,CAAA;AAGR,eAAO,MAAM,+BAA+B,QAe1C,CAAA;AAEF,eAAO,MAAM,sCAAsC,wBAAyB,wBAAwB,cAAc,MAAM,EAAE,WAqBlH,CAAA;AAGR,eAAO,MAAM,eAAe,QAE1B,CAAA"}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
export const getFrontPageRecommendationsQuery = (sessionId, slotId) => JSON.stringify({
|
|
2
|
+
query: `
|
|
3
|
+
mutation GetFrontPageRecommendations($sessionId: String!${slotId ? `, $slotId: String` : ""} ) {
|
|
4
|
+
updateSession(
|
|
5
|
+
by: BY_CID
|
|
6
|
+
id: $sessionId
|
|
7
|
+
params: {
|
|
8
|
+
event: { type: VIEWED_PAGE, target: "home" }
|
|
9
|
+
}
|
|
10
|
+
) {
|
|
11
|
+
pages {
|
|
12
|
+
forFrontPage(
|
|
13
|
+
params: {
|
|
14
|
+
isPreview: false
|
|
15
|
+
imageVersion: VERSION_8_400_400
|
|
16
|
+
${slotId ? `slotIds: [$slotId]` : ""}
|
|
17
|
+
}
|
|
18
|
+
) {
|
|
19
|
+
primary {
|
|
20
|
+
productId
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
`,
|
|
27
|
+
variables: {
|
|
28
|
+
sessionId,
|
|
29
|
+
slotId,
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
export const getProductPageRecommendationsQuery = (sessionId, productId, slotId) => JSON.stringify({
|
|
33
|
+
query: `
|
|
34
|
+
mutation GetProductPageRecommendations($sessionId: String!, $productId: String!${slotId ? `, $slotId: String` : ""} ) {
|
|
35
|
+
updateSession(
|
|
36
|
+
by: BY_CID
|
|
37
|
+
id: $sessionId
|
|
38
|
+
params: {
|
|
39
|
+
event: { type: VIEWED_PRODUCT, target: $productId }
|
|
40
|
+
}
|
|
41
|
+
) {
|
|
42
|
+
id
|
|
43
|
+
pages {
|
|
44
|
+
forProductPage(
|
|
45
|
+
product: $productId
|
|
46
|
+
params: {
|
|
47
|
+
isPreview: false
|
|
48
|
+
${slotId ? `slotIds: [$slotId]` : ""}
|
|
49
|
+
}
|
|
50
|
+
) {
|
|
51
|
+
resultId
|
|
52
|
+
primary {
|
|
53
|
+
name
|
|
54
|
+
productId
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
`,
|
|
61
|
+
variables: {
|
|
62
|
+
sessionId,
|
|
63
|
+
productId,
|
|
64
|
+
slotId,
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
export const categoryPageRecommendationsQuery = (sessionId, sourceCollectionTitle) => JSON.stringify({
|
|
68
|
+
query: `
|
|
69
|
+
mutation CategoryPageRecommendations($sessionId: String!, $sourceCollectionTitle: String!) {
|
|
70
|
+
updateSession(
|
|
71
|
+
by: BY_CID
|
|
72
|
+
id: $sessionId
|
|
73
|
+
params: {
|
|
74
|
+
event: {
|
|
75
|
+
type: VIEWED_CATEGORY
|
|
76
|
+
target: $sourceCollectionTitle
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
) {
|
|
80
|
+
id
|
|
81
|
+
pages {
|
|
82
|
+
forCategoryPage(
|
|
83
|
+
params: {
|
|
84
|
+
isPreview: false
|
|
85
|
+
imageVersion: VERSION_8_400_400
|
|
86
|
+
}
|
|
87
|
+
category: $sourceCollectionTitle
|
|
88
|
+
) {
|
|
89
|
+
resultId
|
|
90
|
+
primary {
|
|
91
|
+
name
|
|
92
|
+
productId
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
`,
|
|
99
|
+
variables: {
|
|
100
|
+
sessionId,
|
|
101
|
+
sourceCollectionTitle,
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
export const bestSellersRecommendationsQuery = JSON.stringify({
|
|
105
|
+
query: `query GetBestSellersRecommendations {
|
|
106
|
+
recos(preview: false, image: VERSION_7_200_200) {
|
|
107
|
+
toplist(hours: 168, sort: BUYS, params: {
|
|
108
|
+
minProducts: 1
|
|
109
|
+
maxProducts: 10
|
|
110
|
+
}) {
|
|
111
|
+
resultId
|
|
112
|
+
primary {
|
|
113
|
+
name
|
|
114
|
+
productId
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}`,
|
|
119
|
+
});
|
|
120
|
+
export const getRelatedProductsRecommendationsQuery = (recommendationsType, productIds) => JSON.stringify({
|
|
121
|
+
query: `
|
|
122
|
+
query GetRelatedProductsRecommendations($relationship: RelationModeEntity!, $productIds: [String!]!) {
|
|
123
|
+
recos(preview: false, image: VERSION_7_200_200) {
|
|
124
|
+
related(
|
|
125
|
+
relationship: $relationship
|
|
126
|
+
productIds: $productIds
|
|
127
|
+
params: { minProducts: 1, maxProducts: 10 }
|
|
128
|
+
) {
|
|
129
|
+
primary {
|
|
130
|
+
productId
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
`,
|
|
136
|
+
variables: {
|
|
137
|
+
relationship: recommendationsType,
|
|
138
|
+
productIds: productIds,
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
export const newSessionQuery = JSON.stringify({
|
|
142
|
+
query: `mutation { newSession }`,
|
|
143
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-nosto-recommendation.test.d.ts","sourceRoot":"","sources":["../../../components/hooks/use-nosto-recommendation.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import { renderHook } from "@testing-library/react-hooks";
|
|
11
|
+
import { NostoRecommendationsType, useNostoRecommendations, } from "./use-nosto-recommendation";
|
|
12
|
+
import { useProducts } from "./use-products";
|
|
13
|
+
jest.mock("./use-products", () => ({
|
|
14
|
+
useProducts: jest.fn(),
|
|
15
|
+
}));
|
|
16
|
+
// Mock fetch for GraphQL queries
|
|
17
|
+
global.fetch = jest.fn((url, options) => {
|
|
18
|
+
const body = JSON.parse(options === null || options === void 0 ? void 0 : options.body);
|
|
19
|
+
if (body.query.includes("newSession")) {
|
|
20
|
+
return Promise.resolve({
|
|
21
|
+
json: () => Promise.resolve({ data: { newSession: "mockSessionId" } }),
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
if (body.query.includes("GetFrontPageRecommendations")) {
|
|
25
|
+
return Promise.resolve({
|
|
26
|
+
json: () => Promise.resolve({
|
|
27
|
+
data: {
|
|
28
|
+
updateSession: {
|
|
29
|
+
pages: {
|
|
30
|
+
forFrontPage: [{ primary: [{ productId: "front-page-id-1" }] }],
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
}),
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
if (body.query.includes("GetBestSellersRecommendations")) {
|
|
38
|
+
return Promise.resolve({
|
|
39
|
+
json: () => Promise.resolve({
|
|
40
|
+
data: {
|
|
41
|
+
recos: {
|
|
42
|
+
toplist: {
|
|
43
|
+
primary: [{ name: "Product 1", productId: "best-seller-1" }],
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
}),
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
if (body.query.includes("GetRelatedProductsRecommendations")) {
|
|
51
|
+
return Promise.resolve({
|
|
52
|
+
json: () => Promise.resolve({
|
|
53
|
+
data: {
|
|
54
|
+
recos: {
|
|
55
|
+
related: {
|
|
56
|
+
primary: [{ productId: "related-1" }],
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
}),
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
if (body.query.includes("GetProductPageRecommendations")) {
|
|
64
|
+
return Promise.resolve({
|
|
65
|
+
json: () => Promise.resolve({
|
|
66
|
+
data: {
|
|
67
|
+
updateSession: {
|
|
68
|
+
pages: {
|
|
69
|
+
forProductPage: [
|
|
70
|
+
{ primary: [{ productId: "product-page-id-1" }] },
|
|
71
|
+
],
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
}),
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
if (body.query.includes("CategoryPageRecommendations")) {
|
|
79
|
+
return Promise.resolve({
|
|
80
|
+
json: () => Promise.resolve({
|
|
81
|
+
data: {
|
|
82
|
+
updateSession: {
|
|
83
|
+
pages: {
|
|
84
|
+
forCategoryPage: [
|
|
85
|
+
{ primary: [{ productId: "category-page-id-1" }] },
|
|
86
|
+
],
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
}),
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
return Promise.reject(new Error("Unknown query"));
|
|
94
|
+
});
|
|
95
|
+
describe("useNostoRecommendations", () => {
|
|
96
|
+
const mockIntegrations = [
|
|
97
|
+
{
|
|
98
|
+
name: "nosto",
|
|
99
|
+
key: "mockNostoKey",
|
|
100
|
+
enabled: true,
|
|
101
|
+
},
|
|
102
|
+
];
|
|
103
|
+
const mockBaseURL = "http://mockbaseurl.com";
|
|
104
|
+
beforeEach(() => {
|
|
105
|
+
jest.clearAllMocks();
|
|
106
|
+
useProducts.mockReturnValue({
|
|
107
|
+
products: [{ id: "1", name: "Product 1" }],
|
|
108
|
+
isLoading: false,
|
|
109
|
+
error: null,
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
it("should return front page recommendations if productIds is empty and slotId is provided", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
113
|
+
const slotId = "mockSlotId";
|
|
114
|
+
const { result, waitFor } = renderHook(() => useNostoRecommendations(mockIntegrations, mockBaseURL, slotId, undefined, undefined, undefined));
|
|
115
|
+
yield waitFor(() => result.current.isLoading === false);
|
|
116
|
+
expect(result.current.isLoading).toBe(false);
|
|
117
|
+
expect(result.current.error).toBe(null);
|
|
118
|
+
expect(useProducts).toHaveBeenCalledWith({
|
|
119
|
+
productIds: ["front-page-id-1"],
|
|
120
|
+
baseURL: mockBaseURL,
|
|
121
|
+
productHandles: [],
|
|
122
|
+
});
|
|
123
|
+
}));
|
|
124
|
+
it("should return best sellers recommendations if productIds is empty and slotId is not provided", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
125
|
+
const slotId = "";
|
|
126
|
+
const { result, waitFor } = renderHook(() => useNostoRecommendations(mockIntegrations, mockBaseURL, slotId, undefined, undefined));
|
|
127
|
+
yield waitFor(() => result.current.isLoading === false);
|
|
128
|
+
expect(result.current.error).toBe(null);
|
|
129
|
+
expect(useProducts).toHaveBeenCalledWith({
|
|
130
|
+
productIds: ["best-seller-1"],
|
|
131
|
+
baseURL: mockBaseURL,
|
|
132
|
+
productHandles: [],
|
|
133
|
+
});
|
|
134
|
+
}));
|
|
135
|
+
it("should return best sellers recommendations if productIds is not empty, Best Sellers is the recommendation type and slotId is not provided", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
136
|
+
const slotId = "";
|
|
137
|
+
const productIds = ["mockProductId"];
|
|
138
|
+
const { result, waitFor } = renderHook(() => useNostoRecommendations(mockIntegrations, mockBaseURL, slotId, NostoRecommendationsType.BEST_SELLERS, productIds));
|
|
139
|
+
yield waitFor(() => result.current.isLoading === false);
|
|
140
|
+
expect(result.current.error).toBe(null);
|
|
141
|
+
expect(useProducts).toHaveBeenCalledWith({
|
|
142
|
+
productIds: ["best-seller-1"],
|
|
143
|
+
baseURL: mockBaseURL,
|
|
144
|
+
productHandles: [],
|
|
145
|
+
});
|
|
146
|
+
}));
|
|
147
|
+
it("should return default front page recommendations if slotId is provided but layoutType is not", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
148
|
+
const slotId = "mockSlotId";
|
|
149
|
+
const { result, waitFor } = renderHook(() => useNostoRecommendations(mockIntegrations, mockBaseURL, slotId));
|
|
150
|
+
yield waitFor(() => result.current.isLoading === false);
|
|
151
|
+
expect(result.current.error).toBe(null);
|
|
152
|
+
expect(useProducts).toHaveBeenCalledWith({
|
|
153
|
+
productIds: ["front-page-id-1"],
|
|
154
|
+
baseURL: mockBaseURL,
|
|
155
|
+
productHandles: [],
|
|
156
|
+
});
|
|
157
|
+
}));
|
|
158
|
+
it("should return the correct recommendations for best sellers", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
159
|
+
const { result, waitFor } = renderHook(() => useNostoRecommendations(mockIntegrations, mockBaseURL, "", NostoRecommendationsType.BEST_SELLERS, undefined, undefined));
|
|
160
|
+
yield waitFor(() => result.current.isLoading === false);
|
|
161
|
+
expect(result.current.error).toBe(null);
|
|
162
|
+
expect(useProducts).toHaveBeenCalledWith({
|
|
163
|
+
productIds: ["best-seller-1"],
|
|
164
|
+
baseURL: mockBaseURL,
|
|
165
|
+
productHandles: [],
|
|
166
|
+
});
|
|
167
|
+
}));
|
|
168
|
+
it("should return the related recommendations when only productIds and a recommendation type are provided on a non-home layout", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
169
|
+
const productIds = ["mockProductId"];
|
|
170
|
+
const { result, waitFor } = renderHook(() => useNostoRecommendations(mockIntegrations, mockBaseURL, "", NostoRecommendationsType.VIEWED_TOGETHER, productIds, undefined));
|
|
171
|
+
yield waitFor(() => result.current.isLoading === false);
|
|
172
|
+
expect(result.current.error).toBe(null);
|
|
173
|
+
expect(useProducts).toHaveBeenCalledWith({
|
|
174
|
+
productIds: ["related-1"],
|
|
175
|
+
baseURL: mockBaseURL,
|
|
176
|
+
productHandles: [],
|
|
177
|
+
});
|
|
178
|
+
}));
|
|
179
|
+
it("should return the correct recommendations when productIds and slotIds are provided", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
180
|
+
const slotId = "mockSlotId";
|
|
181
|
+
const productIds = ["mockProductId"];
|
|
182
|
+
const { result, waitFor } = renderHook(() => useNostoRecommendations(mockIntegrations, mockBaseURL, slotId, NostoRecommendationsType.VIEWED_TOGETHER, productIds, undefined));
|
|
183
|
+
yield waitFor(() => result.current.isLoading === false);
|
|
184
|
+
expect(result.current.error).toBe(null);
|
|
185
|
+
expect(useProducts).toHaveBeenCalledWith({
|
|
186
|
+
productIds: ["product-page-id-1"],
|
|
187
|
+
baseURL: mockBaseURL,
|
|
188
|
+
productHandles: [],
|
|
189
|
+
});
|
|
190
|
+
}));
|
|
191
|
+
it("should return the related products fallback recommendations when productIds are provided and no slotId and a type other than viewed_category and best_sellers is provided", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
192
|
+
const productIds = ["mockProductId"];
|
|
193
|
+
const { result, waitFor } = renderHook(() => useNostoRecommendations(mockIntegrations, mockBaseURL, "", NostoRecommendationsType.VIEWED_TOGETHER, productIds, undefined));
|
|
194
|
+
yield waitFor(() => result.current.isLoading === false);
|
|
195
|
+
expect(result.current.error).toBe(null);
|
|
196
|
+
expect(useProducts).toHaveBeenCalledWith({
|
|
197
|
+
productIds: ["related-1"],
|
|
198
|
+
baseURL: mockBaseURL,
|
|
199
|
+
productHandles: [],
|
|
200
|
+
});
|
|
201
|
+
}));
|
|
202
|
+
it("should return the category fallback recommendations when no slotId, productIds are provided and a recommendation type of viewed_category is provided", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
203
|
+
const productIds = ["mockProductId"];
|
|
204
|
+
const { result, waitFor } = renderHook(() => useNostoRecommendations(mockIntegrations, mockBaseURL, "", NostoRecommendationsType.VIEWED_CATEGORY, productIds, undefined));
|
|
205
|
+
yield waitFor(() => result.current.isLoading === false);
|
|
206
|
+
expect(result.current.error).toBe(null);
|
|
207
|
+
expect(useProducts).toHaveBeenCalledWith({
|
|
208
|
+
productIds: ["category-page-id-1"],
|
|
209
|
+
baseURL: mockBaseURL,
|
|
210
|
+
productHandles: [],
|
|
211
|
+
});
|
|
212
|
+
}));
|
|
213
|
+
it("should handle loading state", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
214
|
+
const { result } = renderHook(() => useNostoRecommendations(mockIntegrations, mockBaseURL, ""));
|
|
215
|
+
expect(result.current.isLoading).toBe(true);
|
|
216
|
+
}));
|
|
217
|
+
it("should handle error state", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
218
|
+
const mockError = () => "Failed to fetch products";
|
|
219
|
+
useProducts.mockReturnValue({
|
|
220
|
+
products: [],
|
|
221
|
+
isLoading: false,
|
|
222
|
+
error: mockError,
|
|
223
|
+
});
|
|
224
|
+
const { result, waitForNextUpdate } = renderHook(() => useNostoRecommendations(mockIntegrations, mockBaseURL));
|
|
225
|
+
yield waitForNextUpdate();
|
|
226
|
+
expect(result.current.error).toBe(mockError);
|
|
227
|
+
}));
|
|
228
|
+
it("should default to best sellers if productIds are empty but a recommendation type other than Best Sellers is used", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
229
|
+
const productIds = [];
|
|
230
|
+
const { result, waitFor } = renderHook(() => useNostoRecommendations(mockIntegrations, mockBaseURL, "", NostoRecommendationsType.VIEWED_TOGETHER, productIds, undefined));
|
|
231
|
+
yield waitFor(() => result.current.isLoading === false);
|
|
232
|
+
expect(result.current.error).toBe(null);
|
|
233
|
+
expect(useProducts).toHaveBeenCalledWith({
|
|
234
|
+
productIds: ["best-seller-1"],
|
|
235
|
+
baseURL: mockBaseURL,
|
|
236
|
+
productHandles: [],
|
|
237
|
+
});
|
|
238
|
+
}));
|
|
239
|
+
it("should return an empty array if skip is true", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
240
|
+
const { result, waitFor } = renderHook(() => useNostoRecommendations(mockIntegrations, mockBaseURL, "", NostoRecommendationsType.BEST_SELLERS, undefined, true));
|
|
241
|
+
yield waitFor(() => result.current.isLoading === false);
|
|
242
|
+
expect(useProducts).toHaveBeenCalledWith({
|
|
243
|
+
productIds: [],
|
|
244
|
+
baseURL: mockBaseURL,
|
|
245
|
+
productHandles: [],
|
|
246
|
+
});
|
|
247
|
+
}));
|
|
248
|
+
});
|
|
@@ -12,6 +12,8 @@ type UseProductsProps = {
|
|
|
12
12
|
queryVariables?: Record<string, any>;
|
|
13
13
|
fetcher?: (url: string, body?: Record<string, any> | null) => Promise<any>;
|
|
14
14
|
metafields?: MetafieldInput[];
|
|
15
|
+
mock?: boolean;
|
|
16
|
+
onlyAvailableProducts?: boolean;
|
|
15
17
|
};
|
|
16
18
|
type UseProductsReturn = {
|
|
17
19
|
products: Product[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-products.d.ts","sourceRoot":"","sources":["../../../components/hooks/use-products.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAA;
|
|
1
|
+
{"version":3,"file":"use-products.d.ts","sourceRoot":"","sources":["../../../components/hooks/use-products.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAA;AAI1C,KAAK,GAAG,GAAG,MAAM,CAAA;AACjB,KAAK,cAAc,GAAG;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAA;AACxD,KAAK,gBAAgB,GAAG;IACtB,UAAU,EAAE,MAAM,EAAE,CAAA;IACpB,cAAc,EAAE,MAAM,EAAE,CAAA;IACxB,OAAO,EAAE,GAAG,CAAA;IACZ,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IACpC,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,KAAK,OAAO,CAAC,GAAG,CAAC,CAAA;IAC1E,UAAU,CAAC,EAAE,cAAc,EAAE,CAAA;IAC7B,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,qBAAqB,CAAC,EAAE,OAAO,CAAA;CAChC,CAAA;AACD,KAAK,iBAAiB,GAAG;IACvB,QAAQ,EAAE,OAAO,EAAE,CAAA;IACnB,KAAK,EAAE,GAAG,CAAA;IACV,SAAS,EAAE,OAAO,CAAA;CACnB,CAAA;AA8BD,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,GAAG,IAAI,GAAG,iBAAiB,CAwF7E"}
|
|
@@ -1,11 +1,43 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import useSWR from "swr";
|
|
3
3
|
import { getProductGidsFromIds } from "../../lib/utils";
|
|
4
|
+
import { useCollection } from "./use-collection";
|
|
5
|
+
const isUseProductsProps = (props) => {
|
|
6
|
+
return Boolean(props);
|
|
7
|
+
};
|
|
8
|
+
const formatProductData = ({ data, onlyAvailableProducts = false, }) => {
|
|
9
|
+
const isProductsObject = (value) => {
|
|
10
|
+
return value && "products" in value;
|
|
11
|
+
};
|
|
12
|
+
const products = isProductsObject(data) ? data.products : data || [];
|
|
13
|
+
return onlyAvailableProducts
|
|
14
|
+
? products === null || products === void 0 ? void 0 : products.filter((p) => {
|
|
15
|
+
if (!onlyAvailableProducts)
|
|
16
|
+
return true;
|
|
17
|
+
return p.availableForSale;
|
|
18
|
+
})
|
|
19
|
+
: products;
|
|
20
|
+
};
|
|
4
21
|
export function useProducts(props) {
|
|
22
|
+
var _a, _b, _c;
|
|
5
23
|
let url = null;
|
|
6
24
|
let body = null;
|
|
7
|
-
|
|
8
|
-
|
|
25
|
+
const shouldMockProducts = Boolean(props === null || props === void 0 ? void 0 : props.mock);
|
|
26
|
+
const { collections } = useCollection({
|
|
27
|
+
apiUrl: (props === null || props === void 0 ? void 0 : props.baseURL) || "",
|
|
28
|
+
appId: ((_a = props === null || props === void 0 ? void 0 : props.queryVariables) === null || _a === void 0 ? void 0 : _a.appId) || "",
|
|
29
|
+
language: ((_b = props === null || props === void 0 ? void 0 : props.queryVariables) === null || _b === void 0 ? void 0 : _b.language) || "en",
|
|
30
|
+
collectionId: undefined,
|
|
31
|
+
getCollections: shouldMockProducts,
|
|
32
|
+
limit: 1,
|
|
33
|
+
});
|
|
34
|
+
if (isUseProductsProps(props)) {
|
|
35
|
+
let { baseURL, productIds, productHandles, metafields, collection, queryVariables, } = props;
|
|
36
|
+
if (shouldMockProducts) {
|
|
37
|
+
const collectionToFetchID = (_c = collections[0]) === null || _c === void 0 ? void 0 : _c.id;
|
|
38
|
+
queryVariables = Object.assign(Object.assign({}, queryVariables), { collectionId: collectionToFetchID });
|
|
39
|
+
collection = collectionToFetchID;
|
|
40
|
+
}
|
|
9
41
|
let queryParams = new URLSearchParams();
|
|
10
42
|
if ((productIds === null || productIds === void 0 ? void 0 : productIds.length) > 0) {
|
|
11
43
|
queryParams.set("ids", getProductGidsFromIds(productIds).join(","));
|
|
@@ -39,10 +71,13 @@ export function useProducts(props) {
|
|
|
39
71
|
body: body ? JSON.stringify(body) : undefined,
|
|
40
72
|
}).then((res) => res.json());
|
|
41
73
|
const fetcher = (props === null || props === void 0 ? void 0 : props.fetcher) || defaultFetcher;
|
|
42
|
-
const { data, error } = useSWR(url ? [url, body] : null, ([url, body]) => fetcher(url, body));
|
|
74
|
+
const { data, error, isLoading } = useSWR(url ? [url, body] : null, ([url, body]) => fetcher(url, body));
|
|
43
75
|
return {
|
|
44
|
-
products: (
|
|
76
|
+
products: formatProductData({
|
|
77
|
+
data,
|
|
78
|
+
onlyAvailableProducts: (props === null || props === void 0 ? void 0 : props.mock) && (props === null || props === void 0 ? void 0 : props.onlyAvailableProducts),
|
|
79
|
+
}),
|
|
45
80
|
error,
|
|
46
|
-
isLoading
|
|
81
|
+
isLoading,
|
|
47
82
|
};
|
|
48
83
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-recommendations.d.ts","sourceRoot":"","sources":["../../../components/hooks/use-recommendations.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAA;AAKtD,UAAU,sBAAsB;IAC9B,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IACnC,MAAM,EAAE,MAAM,CAAA;CACf;AAED,UAAU,uBAAuB;IAC/B,QAAQ,EAAE,OAAO,EAAE,CAAA;IACnB,WAAW,EAAE,UAAU,EAAE,CAAA;IACzB,QAAQ,EAAE,MAAM,EAAE,CAAA;IAClB,SAAS,EAAE,OAAO,CAAA;IAClB,KAAK,EAAE,GAAG,CAAA;CACX;AAOD,QAAA,MAAM,kBAAkB,+BAGrB,sBAAsB,KAAG,
|
|
1
|
+
{"version":3,"file":"use-recommendations.d.ts","sourceRoot":"","sources":["../../../components/hooks/use-recommendations.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAA;AAKtD,UAAU,sBAAsB;IAC9B,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IACnC,MAAM,EAAE,MAAM,CAAA;CACf;AAED,UAAU,uBAAuB;IAC/B,QAAQ,EAAE,OAAO,EAAE,CAAA;IACnB,WAAW,EAAE,UAAU,EAAE,CAAA;IACzB,QAAQ,EAAE,MAAM,EAAE,CAAA;IAClB,SAAS,EAAE,OAAO,CAAA;IAClB,KAAK,EAAE,GAAG,CAAA;CACX;AAOD,QAAA,MAAM,kBAAkB,+BAGrB,sBAAsB,KAAG,uBAgC3B,CAAA;AAED,OAAO,EAAE,kBAAkB,EAAE,CAAA"}
|
|
@@ -8,7 +8,6 @@ const constructURL = (apiURL) => {
|
|
|
8
8
|
const useRecommendations = ({ queryVariables, apiURL }) => {
|
|
9
9
|
const searchParams = useSearchParams();
|
|
10
10
|
const recommendation = (searchParams === null || searchParams === void 0 ? void 0 : searchParams.get('recommendation')) || "*";
|
|
11
|
-
const isBestSeller = recommendation === "*";
|
|
12
11
|
const fetcher = (body) => fetch(constructURL(apiURL), {
|
|
13
12
|
method: "POST",
|
|
14
13
|
body: JSON.stringify(body),
|
|
@@ -24,7 +23,7 @@ const useRecommendations = ({ queryVariables, apiURL }) => {
|
|
|
24
23
|
return {
|
|
25
24
|
products: data === null || data === void 0 ? void 0 : data.products,
|
|
26
25
|
collections: data === null || data === void 0 ? void 0 : data.collections,
|
|
27
|
-
searches:
|
|
26
|
+
searches: data === null || data === void 0 ? void 0 : data.searches,
|
|
28
27
|
isLoading: isLoading,
|
|
29
28
|
error: error
|
|
30
29
|
};
|
|
@@ -25,7 +25,7 @@ const DrawerPortal = ({ children, containerRef }) => {
|
|
|
25
25
|
}
|
|
26
26
|
// If no ref is passed, attempt to look for the dashboard specific block container.
|
|
27
27
|
// This will only be valid within the CMS Dashboard.
|
|
28
|
-
const dashboardContainer = document.querySelector("#_tcDashboard-BlockPhonePreview");
|
|
28
|
+
const dashboardContainer = typeof document !== "undefined" && document.querySelector("#_tcDashboard-BlockPhonePreview");
|
|
29
29
|
if (dashboardContainer) {
|
|
30
30
|
return ReactDOM.createPortal(children, dashboardContainer);
|
|
31
31
|
}
|
package/dist/styles.css
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tapcart/mobile-components",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.28",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"style": "dist/styles.css",
|
|
@@ -39,12 +39,14 @@
|
|
|
39
39
|
"eslint": "^7.32.0",
|
|
40
40
|
"eslint-config-custom": "workspace:*",
|
|
41
41
|
"jest": "^29.7.0",
|
|
42
|
+
"jest-environment-jsdom": "^29.7.0",
|
|
42
43
|
"postcss": "^8.4.24",
|
|
43
44
|
"tailwindcss": "^3.3.2",
|
|
44
45
|
"ts-jest": "^29.2.5",
|
|
45
46
|
"tsc-alias": "^1.8.10",
|
|
46
47
|
"tsconfig": "workspace:*",
|
|
47
|
-
"typescript": "^4.5.2"
|
|
48
|
+
"typescript": "^4.5.2",
|
|
49
|
+
"@testing-library/react-hooks": "^8.0.1"
|
|
48
50
|
},
|
|
49
51
|
"dependencies": {
|
|
50
52
|
"@radix-ui/react-accordion": "^1.1.2",
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
declare const useTap: (tapThreshold?: number) => {
|
|
3
|
-
onTap: (handler: (event: any) => void) => (event: any) => void;
|
|
4
|
-
isPressed: boolean;
|
|
5
|
-
ref: React.MutableRefObject<null>;
|
|
6
|
-
};
|
|
7
|
-
export { useTap };
|
|
8
|
-
//# sourceMappingURL=use-tap.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"use-tap.d.ts","sourceRoot":"","sources":["../../../components/hooks/use-tap.ts"],"names":[],"mappings":"AACA,OAAO,KAAmD,MAAM,OAAO,CAAA;AAuFvE,QAAA,MAAM,MAAM;6BAuBkC,GAAG,KAAK,IAAI,aACvC,GAAG;;;CAerB,CAAA;AAED,OAAO,EAAE,MAAM,EAAE,CAAA"}
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import { useState, useEffect, useCallback, useRef } from "react";
|
|
3
|
-
// Shared manager for all instances of the hook
|
|
4
|
-
const tapManager = (() => {
|
|
5
|
-
const elements = new Map();
|
|
6
|
-
let isListening = false;
|
|
7
|
-
const startListening = () => {
|
|
8
|
-
if (isListening)
|
|
9
|
-
return;
|
|
10
|
-
const handleTouchStart = (e) => {
|
|
11
|
-
const touch = e.touches[0];
|
|
12
|
-
elements.forEach((data, el) => {
|
|
13
|
-
if (el.contains(touch.target)) {
|
|
14
|
-
data.touchStarted = true;
|
|
15
|
-
data.touchMoved = false;
|
|
16
|
-
data.startPosition = { x: touch.clientX, y: touch.clientY };
|
|
17
|
-
// Don't set isPressed here, wait to determine if it's a tap or drag
|
|
18
|
-
}
|
|
19
|
-
});
|
|
20
|
-
};
|
|
21
|
-
const handleTouchMove = (e) => {
|
|
22
|
-
const touch = e.touches[0];
|
|
23
|
-
elements.forEach((data, el) => {
|
|
24
|
-
if (data.touchStarted) {
|
|
25
|
-
const deltaX = Math.abs(touch.clientX - data.startPosition.x);
|
|
26
|
-
const deltaY = Math.abs(touch.clientY - data.startPosition.y);
|
|
27
|
-
if (deltaX > data.tapThreshold || deltaY > data.tapThreshold) {
|
|
28
|
-
data.touchMoved = true;
|
|
29
|
-
data.setIsPressed(false);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
});
|
|
33
|
-
};
|
|
34
|
-
const handleTouchEnd = () => {
|
|
35
|
-
elements.forEach((data) => {
|
|
36
|
-
if (data.touchStarted) {
|
|
37
|
-
data.touchStarted = false;
|
|
38
|
-
if (!data.touchMoved) {
|
|
39
|
-
// It's a tap, set isPressed briefly
|
|
40
|
-
data.setIsPressed(true);
|
|
41
|
-
setTimeout(() => data.setIsPressed(false), 100);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
});
|
|
45
|
-
};
|
|
46
|
-
document.addEventListener("touchstart", (e) => handleTouchStart(e), { passive: true });
|
|
47
|
-
document.addEventListener("touchmove", (e) => handleTouchMove(e), { passive: true });
|
|
48
|
-
document.addEventListener("touchend", () => handleTouchEnd(), {
|
|
49
|
-
passive: true,
|
|
50
|
-
});
|
|
51
|
-
isListening = true;
|
|
52
|
-
};
|
|
53
|
-
return {
|
|
54
|
-
register: (el, data) => {
|
|
55
|
-
elements.set(el, data);
|
|
56
|
-
startListening();
|
|
57
|
-
},
|
|
58
|
-
unregister: (el) => {
|
|
59
|
-
elements.delete(el);
|
|
60
|
-
},
|
|
61
|
-
elements,
|
|
62
|
-
};
|
|
63
|
-
})();
|
|
64
|
-
const useTap = (tapThreshold = 10) => {
|
|
65
|
-
const [isPressed, setIsPressed] = useState(false);
|
|
66
|
-
const elementRef = useRef(null);
|
|
67
|
-
useEffect(() => {
|
|
68
|
-
const element = elementRef.current;
|
|
69
|
-
if (!element)
|
|
70
|
-
return;
|
|
71
|
-
const data = {
|
|
72
|
-
touchStarted: false,
|
|
73
|
-
touchMoved: false,
|
|
74
|
-
startPosition: { x: 0, y: 0 },
|
|
75
|
-
setIsPressed,
|
|
76
|
-
tapThreshold,
|
|
77
|
-
};
|
|
78
|
-
tapManager.register(element, data);
|
|
79
|
-
return () => {
|
|
80
|
-
tapManager.unregister(element);
|
|
81
|
-
};
|
|
82
|
-
}, [tapThreshold]);
|
|
83
|
-
const onTap = useCallback((handler) => {
|
|
84
|
-
return (event) => {
|
|
85
|
-
const data = tapManager.elements.get(elementRef.current);
|
|
86
|
-
if (!data)
|
|
87
|
-
return;
|
|
88
|
-
if (event.type === "touchend" && !data.touchMoved) {
|
|
89
|
-
handler(event);
|
|
90
|
-
}
|
|
91
|
-
else if (event.type === "click" && !data.touchStarted) {
|
|
92
|
-
handler(event);
|
|
93
|
-
setIsPressed(true);
|
|
94
|
-
setTimeout(() => setIsPressed(false), 100);
|
|
95
|
-
}
|
|
96
|
-
};
|
|
97
|
-
}, []);
|
|
98
|
-
return { onTap, isPressed, ref: elementRef };
|
|
99
|
-
};
|
|
100
|
-
export { useTap };
|
package/dist/jest.config.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
//# sourceMappingURL=jest.config.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"jest.config.d.ts","sourceRoot":"","sources":["../jest.config.ts"],"names":[],"mappings":""}
|
package/dist/jest.config.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
//# sourceMappingURL=utils.test.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"utils.test.d.ts","sourceRoot":"","sources":["../../../lib/__tests__/utils.test.ts"],"names":[],"mappings":""}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
"use strict";
|