@usereactify/search 3.2.0 → 3.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -0
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/index.js +2 -0
- package/dist/hooks/reactivesearch/useReactiveDataSearchProps.js +7 -0
- package/dist/hooks/useAnalytics.d.ts +28 -0
- package/dist/hooks/useAnalytics.js +54 -0
- package/dist/hooks/useLiveConfig.d.ts +5 -0
- package/dist/hooks/useLiveConfig.js +70 -0
- package/dist/hooks/useSort.d.ts +1 -1
- package/dist/provider.d.ts +4 -2
- package/dist/provider.js +8 -2
- package/dist/result/ResultCard.js +13 -1
- package/dist/result/ResultList.d.ts +1 -0
- package/dist/result/ResultList.js +8 -1
- package/package.json +2 -1
package/README.md
CHANGED
package/dist/hooks/index.d.ts
CHANGED
|
@@ -5,8 +5,10 @@ export * from "./useConfig";
|
|
|
5
5
|
export * from "./useSearch";
|
|
6
6
|
export * from "./useFilters";
|
|
7
7
|
export * from "./useCuration";
|
|
8
|
+
export * from "./useAnalytics";
|
|
8
9
|
export * from "./useCollection";
|
|
9
10
|
export * from "./useFilterStack";
|
|
10
11
|
export * from "./useProductPrice";
|
|
11
12
|
export * from "./useFilterListProps";
|
|
12
13
|
export * from "./useFilterCollapsedState";
|
|
14
|
+
export * from "./useLiveConfig";
|
package/dist/hooks/index.js
CHANGED
|
@@ -17,8 +17,10 @@ __exportStar(require("./useConfig"), exports);
|
|
|
17
17
|
__exportStar(require("./useSearch"), exports);
|
|
18
18
|
__exportStar(require("./useFilters"), exports);
|
|
19
19
|
__exportStar(require("./useCuration"), exports);
|
|
20
|
+
__exportStar(require("./useAnalytics"), exports);
|
|
20
21
|
__exportStar(require("./useCollection"), exports);
|
|
21
22
|
__exportStar(require("./useFilterStack"), exports);
|
|
22
23
|
__exportStar(require("./useProductPrice"), exports);
|
|
23
24
|
__exportStar(require("./useFilterListProps"), exports);
|
|
24
25
|
__exportStar(require("./useFilterCollapsedState"), exports);
|
|
26
|
+
__exportStar(require("./useLiveConfig"), exports);
|
|
@@ -33,8 +33,15 @@ const useReactiveDataSearchProps = (props = {}) => {
|
|
|
33
33
|
return;
|
|
34
34
|
submitSearch();
|
|
35
35
|
}, [submitSearch]);
|
|
36
|
+
const { track } = (0, __1.useAnalytics)();
|
|
37
|
+
const trackQuery = react_1.default.useCallback(() => {
|
|
38
|
+
if (!searchQuery)
|
|
39
|
+
return;
|
|
40
|
+
track({ eventName: "search", payload: { searchTerm: searchQuery } });
|
|
41
|
+
}, [searchQuery, track]);
|
|
36
42
|
const { run: runDebouncedTriggerQuery, cancel: cancelDebouncedTriggerQuery } = (0, ahooks_1.useDebounceFn)((triggerQuery) => {
|
|
37
43
|
triggerQuery();
|
|
44
|
+
trackQuery();
|
|
38
45
|
}, {
|
|
39
46
|
wait: debounce !== null && debounce !== void 0 ? debounce : 300,
|
|
40
47
|
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export declare const useAnalytics: () => {
|
|
2
|
+
track: (event: TrackEvent) => Promise<import("axios").AxiosResponse<any, any> | undefined>;
|
|
3
|
+
};
|
|
4
|
+
export declare type TrackEvent = TrackEvent.SearchEvent | TrackEvent.ClickProductEvent;
|
|
5
|
+
export declare namespace TrackEvent {
|
|
6
|
+
interface SearchEvent {
|
|
7
|
+
eventName: "search";
|
|
8
|
+
payload: SearchEvent.Payload;
|
|
9
|
+
}
|
|
10
|
+
namespace SearchEvent {
|
|
11
|
+
interface Payload {
|
|
12
|
+
searchTerm: string;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
interface ClickProductEvent {
|
|
16
|
+
eventName: "clickProduct";
|
|
17
|
+
payload: ClickProductEvent.Payload;
|
|
18
|
+
}
|
|
19
|
+
namespace ClickProductEvent {
|
|
20
|
+
interface Payload {
|
|
21
|
+
elasticProduct: ElasticProduct;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
interface ElasticProduct {
|
|
25
|
+
id: number;
|
|
26
|
+
title: string;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
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
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.useAnalytics = void 0;
|
|
16
|
+
const axios_1 = __importDefault(require("axios"));
|
|
17
|
+
const provider_1 = require("../provider");
|
|
18
|
+
const useAnalytics = () => {
|
|
19
|
+
const { shopifyPermanentDomain } = (0, provider_1.useContext)();
|
|
20
|
+
const url = "https://analytics.search.reactify.app/record/";
|
|
21
|
+
const track = (event) => __awaiter(void 0, void 0, void 0, function* () {
|
|
22
|
+
if (!shopifyPermanentDomain) {
|
|
23
|
+
console.warn(new Error('Unable to send tracking event, missing value for "shopifyPermanentDomain".'));
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const events = getTrackEvents(event);
|
|
27
|
+
return axios_1.default.post(url, { events }, { headers: { "X-Reactify-Tenant": shopifyPermanentDomain } });
|
|
28
|
+
});
|
|
29
|
+
return { track };
|
|
30
|
+
};
|
|
31
|
+
exports.useAnalytics = useAnalytics;
|
|
32
|
+
function getTrackEvents(event) {
|
|
33
|
+
const { eventName } = event;
|
|
34
|
+
let events = [];
|
|
35
|
+
switch (eventName) {
|
|
36
|
+
case "search":
|
|
37
|
+
events = [
|
|
38
|
+
{
|
|
39
|
+
eventName,
|
|
40
|
+
payload: event.payload,
|
|
41
|
+
},
|
|
42
|
+
];
|
|
43
|
+
break;
|
|
44
|
+
case "clickProduct":
|
|
45
|
+
events = [
|
|
46
|
+
{
|
|
47
|
+
eventName,
|
|
48
|
+
payload: event.payload,
|
|
49
|
+
},
|
|
50
|
+
];
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
return events;
|
|
54
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use strict";
|
|
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
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.useLiveConfig = void 0;
|
|
16
|
+
const react_1 = __importDefault(require("react"));
|
|
17
|
+
const debug = require("debug")("reactify-search:useLiveConfig");
|
|
18
|
+
// 5 minute cache expiry
|
|
19
|
+
const CACHE_EXPIRY = 5 * 60 * 1000;
|
|
20
|
+
const useLiveConfig = (shopifyPermanentDomain) => {
|
|
21
|
+
// synchronously returns cached and non-expired config from session storage
|
|
22
|
+
const cachedConfig = react_1.default.useMemo(() => {
|
|
23
|
+
var _a;
|
|
24
|
+
// skip checking cache if url search param "nocache" is set
|
|
25
|
+
const skipCache = new URLSearchParams(window.location.href.split("?")[1]).get("nocache") !== null;
|
|
26
|
+
if (skipCache) {
|
|
27
|
+
debug("skipping cache");
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const sessionConfig = JSON.parse((_a = window.sessionStorage.getItem("reactify-search:config")) !== null && _a !== void 0 ? _a : "null");
|
|
31
|
+
if (sessionConfig) {
|
|
32
|
+
const sessionConfigTtl = sessionConfig.expiresAt - Date.now();
|
|
33
|
+
debug(`found ${sessionConfigTtl > 0 ? "cached" : "expired"} config`);
|
|
34
|
+
if (sessionConfigTtl > 0) {
|
|
35
|
+
debug(`config expires in ${(sessionConfigTtl / 1000).toFixed()} seconds`);
|
|
36
|
+
return sessionConfig.config;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return;
|
|
40
|
+
}, []);
|
|
41
|
+
const [config, setConfig] = react_1.default.useState(cachedConfig !== null && cachedConfig !== void 0 ? cachedConfig : {
|
|
42
|
+
fields: [],
|
|
43
|
+
filters: [],
|
|
44
|
+
sort: [],
|
|
45
|
+
curations: [],
|
|
46
|
+
redirects: [],
|
|
47
|
+
});
|
|
48
|
+
const [configLoading, setConfigLoading] = react_1.default.useState(cachedConfig ? false : true);
|
|
49
|
+
react_1.default.useEffect(() => {
|
|
50
|
+
// fetch remote config if no cached config is found
|
|
51
|
+
if (!cachedConfig) {
|
|
52
|
+
(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
53
|
+
debug("fetching fresh config");
|
|
54
|
+
setConfigLoading(true);
|
|
55
|
+
const json = yield fetch(`https://config.search.reactify.app/?shop=${shopifyPermanentDomain}`).then((response) => response.json());
|
|
56
|
+
setConfig(json.body);
|
|
57
|
+
setConfigLoading(false);
|
|
58
|
+
window.sessionStorage.setItem("reactify-search:config", JSON.stringify({
|
|
59
|
+
expiresAt: new Date().getTime() + CACHE_EXPIRY,
|
|
60
|
+
config: json.body,
|
|
61
|
+
}));
|
|
62
|
+
}))();
|
|
63
|
+
}
|
|
64
|
+
}, [shopifyPermanentDomain]);
|
|
65
|
+
return {
|
|
66
|
+
config,
|
|
67
|
+
configLoading,
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
exports.useLiveConfig = useLiveConfig;
|
package/dist/hooks/useSort.d.ts
CHANGED
package/dist/provider.d.ts
CHANGED
|
@@ -3,7 +3,9 @@ import type { Config, ConfigSort, ConfigFilter, ConfigCuration } from "./types/c
|
|
|
3
3
|
declare type Context = {
|
|
4
4
|
index: string;
|
|
5
5
|
config: Config;
|
|
6
|
-
|
|
6
|
+
configLoading: boolean;
|
|
7
|
+
shopifyPermanentDomain: string;
|
|
8
|
+
sortOption: ConfigSort | undefined;
|
|
7
9
|
sortOptions: ConfigSort[];
|
|
8
10
|
searchQuery?: string;
|
|
9
11
|
filterStack?: ConfigFilter;
|
|
@@ -23,7 +25,7 @@ declare type Context = {
|
|
|
23
25
|
declare const Context: React.Context<Context | undefined>;
|
|
24
26
|
declare type Props = {
|
|
25
27
|
index: string;
|
|
26
|
-
|
|
28
|
+
shopifyPermanentDomain: string;
|
|
27
29
|
filterStackId?: string;
|
|
28
30
|
collection?: Collection;
|
|
29
31
|
noReactiveBase?: boolean;
|
package/dist/provider.js
CHANGED
|
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.useContext = exports.Provider = void 0;
|
|
7
7
|
const react_1 = __importDefault(require("react"));
|
|
8
8
|
const UtilityAuthenticatedReactiveBase_1 = require("./utility/UtilityAuthenticatedReactiveBase");
|
|
9
|
+
const hooks_1 = require("./hooks");
|
|
9
10
|
const debug = require("debug")("reactify-search:Provider");
|
|
10
11
|
const Context = react_1.default.createContext(undefined);
|
|
11
12
|
const defaultCredentials = {
|
|
@@ -15,7 +16,7 @@ const defaultCredentials = {
|
|
|
15
16
|
};
|
|
16
17
|
const Provider = (props) => {
|
|
17
18
|
var _a, _b;
|
|
18
|
-
const { index,
|
|
19
|
+
const { index, shopifyPermanentDomain, children, collection, instantSearch, filterStackId, noReactiveBase, additionalComponentIds, } = props;
|
|
19
20
|
const credentials = (_a = props.credentials) !== null && _a !== void 0 ? _a : defaultCredentials;
|
|
20
21
|
const theme = (_b = props.theme) !== null && _b !== void 0 ? _b : {
|
|
21
22
|
typography: {
|
|
@@ -33,6 +34,7 @@ const Provider = (props) => {
|
|
|
33
34
|
react_1.default.useEffect(() => {
|
|
34
35
|
debug("props", props);
|
|
35
36
|
}, [props]);
|
|
37
|
+
const { config, configLoading } = (0, hooks_1.useLiveConfig)(props.shopifyPermanentDomain);
|
|
36
38
|
// @todo make this hackable with a prop
|
|
37
39
|
// https://gitlab.com/reactifyapps/reactify-search-frontend/-/issues/1
|
|
38
40
|
const searchQueryFromURL = react_1.default.useMemo(() => {
|
|
@@ -64,6 +66,8 @@ const Provider = (props) => {
|
|
|
64
66
|
const contextValue = react_1.default.useMemo(() => ({
|
|
65
67
|
index,
|
|
66
68
|
config,
|
|
69
|
+
configLoading,
|
|
70
|
+
shopifyPermanentDomain,
|
|
67
71
|
collection,
|
|
68
72
|
credentials,
|
|
69
73
|
searchQuery,
|
|
@@ -83,6 +87,7 @@ const Provider = (props) => {
|
|
|
83
87
|
index,
|
|
84
88
|
config,
|
|
85
89
|
curation,
|
|
90
|
+
shopifyPermanentDomain,
|
|
86
91
|
collection,
|
|
87
92
|
credentials,
|
|
88
93
|
searchQuery,
|
|
@@ -108,13 +113,14 @@ exports.Provider = Provider;
|
|
|
108
113
|
const useContext = () => react_1.default.useContext(Context);
|
|
109
114
|
exports.useContext = useContext;
|
|
110
115
|
const useSortState = (config, collection) => {
|
|
116
|
+
var _a;
|
|
111
117
|
const sortOptions = react_1.default.useMemo(() => {
|
|
112
118
|
const type = !!collection ? "collection" : "search";
|
|
113
119
|
return config.sort
|
|
114
120
|
.sort((a, b) => `${a.position}`.localeCompare(`${b.position}`))
|
|
115
121
|
.filter(({ visibility }) => ["all", type].includes(visibility));
|
|
116
122
|
}, [config, collection]);
|
|
117
|
-
const [sortOptionState, setSortOptionState] = react_1.default.useState(sortOptions[0].id);
|
|
123
|
+
const [sortOptionState, setSortOptionState] = react_1.default.useState((_a = sortOptions[0]) === null || _a === void 0 ? void 0 : _a.id);
|
|
118
124
|
const sortOption = react_1.default.useMemo(() => sortOptions.find(({ id }) => id === sortOptionState) || sortOptions[0], [sortOptions, sortOptionState]);
|
|
119
125
|
const setSortOption = react_1.default.useCallback((sortOptionId) => setSortOptionState(sortOptionId), []);
|
|
120
126
|
return react_1.default.useMemo(() => ({ sortOptions, sortOption, setSortOption }), [sortOption, sortOptions, setSortOption]);
|
|
@@ -24,8 +24,20 @@ const ResultCard = (_a) => {
|
|
|
24
24
|
if (render)
|
|
25
25
|
return render(Object.assign(Object.assign({}, props), productPrice));
|
|
26
26
|
const { formattedPrice, formattedCompareAtPrice, onSale } = productPrice;
|
|
27
|
+
const { track } = (0, hooks_1.useAnalytics)();
|
|
28
|
+
const handleClick = react_1.default.useCallback(() => {
|
|
29
|
+
track({
|
|
30
|
+
eventName: "clickProduct",
|
|
31
|
+
payload: {
|
|
32
|
+
elasticProduct: {
|
|
33
|
+
id: product.id,
|
|
34
|
+
title: product.title,
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
}, [track, product]);
|
|
27
39
|
return (react_1.default.createElement("article", null,
|
|
28
|
-
react_1.default.createElement("a", { href: `/products/${product.handle}` },
|
|
40
|
+
react_1.default.createElement("a", { onClick: handleClick, href: `/products/${product.handle}` },
|
|
29
41
|
product.image && react_1.default.createElement("img", { src: product.image, width: "100%" }),
|
|
30
42
|
product.title),
|
|
31
43
|
react_1.default.createElement("span", null, formattedPrice),
|
|
@@ -14,6 +14,7 @@ declare type Props = {
|
|
|
14
14
|
pageSize?: number;
|
|
15
15
|
gridColumns?: number;
|
|
16
16
|
listClassName?: string;
|
|
17
|
+
renderBooting?: () => JSX.Element | null;
|
|
17
18
|
renderLoading?: () => JSX.Element | null;
|
|
18
19
|
renderNoResults?: () => JSX.Element | null;
|
|
19
20
|
renderResultCard?: Parameters<typeof ResultCard>[0]["render"];
|
|
@@ -11,6 +11,7 @@ const ResultPagination_1 = require("./ResultPagination");
|
|
|
11
11
|
const ResultCardCallout_1 = require("./ResultCardCallout");
|
|
12
12
|
const ResultLoadMoreButton_1 = require("./ResultLoadMoreButton");
|
|
13
13
|
const ResultPaginationNextPrev_1 = require("./ResultPaginationNextPrev");
|
|
14
|
+
const provider_1 = require("../provider");
|
|
14
15
|
const hooks_1 = require("../hooks");
|
|
15
16
|
const elastic_1 = require("../types/elastic");
|
|
16
17
|
const ResultList = (props) => {
|
|
@@ -22,9 +23,10 @@ const ResultList = (props) => {
|
|
|
22
23
|
};
|
|
23
24
|
exports.ResultList = ResultList;
|
|
24
25
|
const ResultListInner = (props) => {
|
|
25
|
-
const { gridColumns, renderError, renderAfter, renderBefore, renderLoading, listClassName, renderResults, renderNoResults, renderResultCard, renderLoadMoreButton, renderResultCardCallout, reactivesearchResultProps, } = props;
|
|
26
|
+
const { gridColumns, renderError, renderAfter, renderBefore, renderBooting, renderLoading, listClassName, renderResults, renderNoResults, renderResultCard, renderLoadMoreButton, renderResultCardCallout, reactivesearchResultProps, } = props;
|
|
26
27
|
const filterStack = (0, hooks_1.useFilterStack)();
|
|
27
28
|
const { instantSearch } = (0, hooks_1.useSearch)();
|
|
29
|
+
const { configLoading } = (0, provider_1.useContext)();
|
|
28
30
|
const initialSearchHasRun = react_1.default.useMemo(() => "undefined" !==
|
|
29
31
|
typeof reactivesearchResultProps.resultStats.numberOfResults, [reactivesearchResultProps]);
|
|
30
32
|
const styleProp = react_1.default.useMemo(() => ({
|
|
@@ -32,6 +34,11 @@ const ResultListInner = (props) => {
|
|
|
32
34
|
gridTemplateColumns: `repeat(${gridColumns !== null && gridColumns !== void 0 ? gridColumns : 4}, minmax(0, 1fr))`,
|
|
33
35
|
}), [gridColumns]);
|
|
34
36
|
const resultProps = react_1.default.useMemo(() => (Object.assign(Object.assign({}, reactivesearchResultProps), { products: reactivesearchResultProps.data.filter((document) => elastic_1.ElasticDocumentType.Product === document.type || !document.type), callouts: reactivesearchResultProps.data.filter((document) => elastic_1.ElasticDocumentType.Callout === document.type) })), [reactivesearchResultProps]);
|
|
37
|
+
if (configLoading) {
|
|
38
|
+
if (renderBooting)
|
|
39
|
+
return renderBooting();
|
|
40
|
+
return react_1.default.createElement("div", null, "Loading...");
|
|
41
|
+
}
|
|
35
42
|
if (reactivesearchResultProps.error) {
|
|
36
43
|
if (renderError)
|
|
37
44
|
return renderError({ error: reactivesearchResultProps.error });
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@usereactify/search",
|
|
3
3
|
"description": "React UI library for Reactify Search",
|
|
4
|
-
"version": "3.
|
|
4
|
+
"version": "3.4.1",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"ahooks": "2.10.11",
|
|
33
|
+
"axios": "0.26.1",
|
|
33
34
|
"currency.js": "2.0.4",
|
|
34
35
|
"debug": "4.3.2"
|
|
35
36
|
},
|