@nyris/nyris-webapp 0.3.42 → 0.3.44
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/build/asset-manifest.json +16 -12
- package/build/index.html +1 -1
- package/build/{precache-manifest.009e864ff0764cf3cf8a9b290c334099.js → precache-manifest.c92406fe2e3b0feaf429c551125e2d95.js} +26 -10
- package/build/service-worker.js +1 -1
- package/build/static/css/main.1b40c5ff.chunk.css +2 -0
- package/build/static/css/main.1b40c5ff.chunk.css.map +1 -0
- package/build/static/js/2.82ef1cd4.chunk.js +3 -0
- package/build/static/js/2.82ef1cd4.chunk.js.map +1 -0
- package/build/static/js/main.7cdac2fb.chunk.js +3 -0
- package/build/static/js/main.7cdac2fb.chunk.js.map +1 -0
- package/build/static/media/avatar.4c5346ed.svg +3 -0
- package/build/static/media/logout.07b9ef7f.svg +3 -0
- package/build/static/media/powered_by_nyris.e6766baf.svg +3 -0
- package/build/static/media/powered_by_nyris_colored.08d00bae.svg +9 -0
- package/package.json +3 -3
- package/public/index.html +4 -16
- package/src/Router.tsx +1 -0
- package/src/Store/Store.ts +2 -4
- package/src/Store/constants.ts +6 -0
- package/src/Store/search/Search.ts +10 -3
- package/src/Store/search/types.ts +1 -0
- package/src/common/assets/icons/avatar.svg +3 -0
- package/src/common/assets/icons/logout.svg +3 -0
- package/src/common/assets/images/powered_by_nyris.svg +3 -0
- package/src/common/assets/images/powered_by_nyris_colored.svg +9 -0
- package/src/components/AppMobile.tsx +1 -0
- package/src/components/AuthenticatedRoute.tsx +4 -1
- package/src/components/DragDropFile.tsx +3 -4
- package/src/components/FooterMobile.tsx +137 -24
- package/src/components/Header.tsx +120 -1
- package/src/components/HeaderMobile.tsx +230 -163
- package/src/components/ImagePreviewMobile.tsx +57 -8
- package/src/components/Layout.tsx +10 -53
- package/src/components/NoAccess.tsx +66 -0
- package/src/components/PoweredByNyris.tsx +49 -0
- package/src/components/ProductDetailView.tsx +16 -10
- package/src/components/ProductList/index.tsx +16 -93
- package/src/components/ProductList/useProductList.ts +114 -0
- package/src/components/Provider/InstantSearchProvider.tsx +66 -0
- package/src/components/appMobile.scss +2 -2
- package/src/components/common.scss +8 -1
- package/src/components/drawer/cameraCustom.tsx +3 -3
- package/src/components/input/inputSearch.tsx +86 -17
- package/src/components/pre-filter/index.tsx +58 -7
- package/src/components/results/ItemResult.tsx +6 -1
- package/src/index.tsx +1 -1
- package/src/page/landingPage/common.scss +4 -1
- package/src/page/result/index.tsx +154 -131
- package/src/services/Feedback.ts +1 -1
- package/src/services/image.ts +8 -5
- package/src/types.ts +13 -12
- package/build/static/css/main.86d40309.chunk.css +0 -2
- package/build/static/css/main.86d40309.chunk.css.map +0 -1
- package/build/static/js/2.1757789c.chunk.js +0 -3
- package/build/static/js/2.1757789c.chunk.js.map +0 -1
- package/build/static/js/main.1d184393.chunk.js +0 -3
- package/build/static/js/main.1d184393.chunk.js.map +0 -1
- package/src/Store/auth/Auth.ts +0 -33
- package/src/Store/auth/types.ts +0 -11
- package/src/Store/nyris/Nyris.ts +0 -67
- package/src/Store/nyris/types.ts +0 -11
- package/src/components/Feedback.tsx +0 -91
- /package/build/static/js/{2.1757789c.chunk.js.LICENSE.txt → 2.82ef1cd4.chunk.js.LICENSE.txt} +0 -0
- /package/build/static/js/{main.1d184393.chunk.js.LICENSE.txt → main.7cdac2fb.chunk.js.LICENSE.txt} +0 -0
- /package/src/components/{AuthProvider.tsx → Provider/AuthProvider.tsx} +0 -0
|
@@ -5,14 +5,20 @@ import { Preview } from '@nyris/nyris-react-components';
|
|
|
5
5
|
import { DEFAULT_REGION } from '../constants';
|
|
6
6
|
import { ReactComponent as IconInfo } from 'common/assets/icons/info-tooltip.svg';
|
|
7
7
|
import { useTranslation } from 'react-i18next';
|
|
8
|
-
import { useAppDispatch } from 'Store/Store';
|
|
8
|
+
import { useAppDispatch, useAppSelector } from 'Store/Store';
|
|
9
9
|
import { ReactComponent as ArrowUp } from 'common/assets/icons/arrow_up.svg';
|
|
10
10
|
import { ReactComponent as ArrowDown } from 'common/assets/icons/arrow_down.svg';
|
|
11
11
|
import { ReactComponent as Trash } from 'common/assets/icons/trash.svg';
|
|
12
12
|
import { useQuery } from 'hooks/useQuery';
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
reset,
|
|
15
|
+
setSearchResults,
|
|
16
|
+
updateStatusLoading,
|
|
17
|
+
} from 'Store/search/Search';
|
|
14
18
|
import { useHistory } from 'react-router-dom';
|
|
15
19
|
import { connectSearchBox } from 'react-instantsearch-dom';
|
|
20
|
+
import { find } from 'services/image';
|
|
21
|
+
import { isEmpty } from 'lodash';
|
|
16
22
|
|
|
17
23
|
function ImagePreviewMobileComponent({
|
|
18
24
|
requestImage,
|
|
@@ -36,6 +42,9 @@ function ImagePreviewMobileComponent({
|
|
|
36
42
|
const { refine }: any = rest;
|
|
37
43
|
const [editActive, setEditActive] = useState(false);
|
|
38
44
|
const [showShrinkAnimation, setShrinkAnimation] = useState(false);
|
|
45
|
+
const settings = useAppSelector(state => state.settings);
|
|
46
|
+
const { preFilter } = useAppSelector(state => state.search);
|
|
47
|
+
const isAlgoliaEnabled = settings.algolia?.enabled;
|
|
39
48
|
const query = useQuery();
|
|
40
49
|
const dispatch = useAppDispatch();
|
|
41
50
|
const history = useHistory();
|
|
@@ -45,19 +54,59 @@ function ImagePreviewMobileComponent({
|
|
|
45
54
|
setShrinkAnimation(true);
|
|
46
55
|
};
|
|
47
56
|
|
|
48
|
-
const
|
|
49
|
-
const searchQuery = query.get('query') || '';
|
|
57
|
+
const searchQuery = query.get('query') || '';
|
|
50
58
|
|
|
59
|
+
const onImageRemove = () => {
|
|
51
60
|
if (!searchQuery) {
|
|
52
61
|
dispatch(reset(''));
|
|
53
62
|
history.push('/');
|
|
54
63
|
}
|
|
55
64
|
dispatch(reset(''));
|
|
65
|
+
if (isAlgoliaEnabled) {
|
|
66
|
+
// not an ideal solution: fixes text search not working after removing image
|
|
67
|
+
setTimeout(() => {
|
|
68
|
+
refine(searchQuery);
|
|
69
|
+
}, 100);
|
|
70
|
+
}
|
|
71
|
+
if (!isAlgoliaEnabled) {
|
|
72
|
+
let payload: any;
|
|
73
|
+
let filters: any[] = [];
|
|
74
|
+
const preFilterValues = [
|
|
75
|
+
{
|
|
76
|
+
key: settings.visualSearchFilterKey,
|
|
77
|
+
values: Object.keys(preFilter) as string[],
|
|
78
|
+
},
|
|
79
|
+
];
|
|
80
|
+
if (searchQuery || requestImage) {
|
|
81
|
+
dispatch(updateStatusLoading(true));
|
|
82
|
+
find({
|
|
83
|
+
settings,
|
|
84
|
+
filters: !isEmpty(preFilter) ? preFilterValues : undefined,
|
|
85
|
+
text: searchQuery,
|
|
86
|
+
})
|
|
87
|
+
.then((res: any) => {
|
|
88
|
+
res?.results.map((item: any) => {
|
|
89
|
+
filters.push({
|
|
90
|
+
sku: item.sku,
|
|
91
|
+
score: item.score,
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
payload = {
|
|
95
|
+
...res,
|
|
96
|
+
filters,
|
|
97
|
+
};
|
|
56
98
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
99
|
+
dispatch(setSearchResults(payload));
|
|
100
|
+
dispatch(updateStatusLoading(false));
|
|
101
|
+
})
|
|
102
|
+
.catch((e: any) => {
|
|
103
|
+
console.log('error input search', e);
|
|
104
|
+
dispatch(updateStatusLoading(false));
|
|
105
|
+
});
|
|
106
|
+
} else {
|
|
107
|
+
dispatch(setSearchResults([]));
|
|
108
|
+
}
|
|
109
|
+
}
|
|
61
110
|
};
|
|
62
111
|
|
|
63
112
|
return (
|
|
@@ -1,18 +1,11 @@
|
|
|
1
1
|
import { Box } from '@material-ui/core';
|
|
2
|
-
import { MultipleQueriesQuery } from '@algolia/client-search';
|
|
3
|
-
import algoliasearch from 'algoliasearch/lite';
|
|
4
2
|
import { ReactNode } from 'components/common';
|
|
5
|
-
import React, { memo, useEffect,
|
|
6
|
-
import { InstantSearch } from 'react-instantsearch-dom';
|
|
3
|
+
import React, { memo, useEffect, useState } from 'react';
|
|
7
4
|
import { useMediaQuery } from 'react-responsive';
|
|
8
5
|
import { useHistory } from 'react-router-dom';
|
|
9
|
-
import {
|
|
10
|
-
changeValueTextSearch,
|
|
11
|
-
onResetRequestImage,
|
|
12
|
-
setUpdateSession,
|
|
13
|
-
} from 'Store/search/Search';
|
|
6
|
+
import { onResetRequestImage, setUpdateSession } from 'Store/search/Search';
|
|
14
7
|
import { useAppDispatch, useAppSelector } from 'Store/Store';
|
|
15
|
-
import {
|
|
8
|
+
import { AppState } from '../types';
|
|
16
9
|
import './appMobile.scss';
|
|
17
10
|
import './common.scss';
|
|
18
11
|
import FooterMobile from './FooterMobile';
|
|
@@ -27,6 +20,8 @@ import i18n from 'i18next';
|
|
|
27
20
|
import { initReactI18next } from 'react-i18next';
|
|
28
21
|
import { translations } from 'translations';
|
|
29
22
|
import { useAuth0 } from '@auth0/auth0-react';
|
|
23
|
+
import InstantSearchProvider from './Provider/InstantSearchProvider';
|
|
24
|
+
import PoweredByNyris from './PoweredByNyris';
|
|
30
25
|
|
|
31
26
|
declare var psol: any;
|
|
32
27
|
|
|
@@ -60,8 +55,7 @@ i18n.use(initReactI18next).init({
|
|
|
60
55
|
function Layout({ children }: ReactNode): JSX.Element {
|
|
61
56
|
const dispatch = useAppDispatch();
|
|
62
57
|
const { settings, search } = useAppSelector<AppState>((state: any) => state);
|
|
63
|
-
const {
|
|
64
|
-
const { apiKey, appId, indexName } = settings.algolia as AlgoliaSettings;
|
|
58
|
+
const { loadingSearchAlgolia } = search;
|
|
65
59
|
const isMobile = useMediaQuery({ query: '(max-width: 776px)' });
|
|
66
60
|
const [isOpenFilter, setOpenFilter] = useState<boolean>(false);
|
|
67
61
|
const history = useHistory();
|
|
@@ -70,7 +64,7 @@ function Layout({ children }: ReactNode): JSX.Element {
|
|
|
70
64
|
history.location?.pathname === '/';
|
|
71
65
|
const language = useAppSelector(state => state.settings.language);
|
|
72
66
|
const { isAuthenticated } = useAuth0();
|
|
73
|
-
const { auth0 } = settings;
|
|
67
|
+
const { auth0, showPoweredByNyris } = settings;
|
|
74
68
|
const showApp = !auth0.enabled || (auth0.enabled && isAuthenticated);
|
|
75
69
|
i18n.changeLanguage(language);
|
|
76
70
|
|
|
@@ -103,35 +97,6 @@ function Layout({ children }: ReactNode): JSX.Element {
|
|
|
103
97
|
HeaderApp = Header;
|
|
104
98
|
}
|
|
105
99
|
|
|
106
|
-
const conditionalQuery = useMemo(() => {
|
|
107
|
-
const searchClient = algoliasearch(appId, apiKey);
|
|
108
|
-
searchClient.initIndex(indexName);
|
|
109
|
-
return {
|
|
110
|
-
...searchClient,
|
|
111
|
-
search(requests: MultipleQueriesQuery[]) {
|
|
112
|
-
if (
|
|
113
|
-
requests.every(
|
|
114
|
-
(request: MultipleQueriesQuery) =>
|
|
115
|
-
!request.params?.query &&
|
|
116
|
-
(!request.params?.filters ||
|
|
117
|
-
request.params?.filters.endsWith('<score=1>')),
|
|
118
|
-
)
|
|
119
|
-
) {
|
|
120
|
-
// Here we have to do something else
|
|
121
|
-
return Promise.resolve({
|
|
122
|
-
results: requests.map(() => ({
|
|
123
|
-
hits: [],
|
|
124
|
-
nbHits: 0,
|
|
125
|
-
nbPages: 0,
|
|
126
|
-
processingTimeMS: 0,
|
|
127
|
-
})),
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
|
-
return searchClient.search(requests);
|
|
131
|
-
},
|
|
132
|
-
};
|
|
133
|
-
}, [apiKey, appId, indexName]);
|
|
134
|
-
|
|
135
100
|
// First we get the viewport height and we multiple it by 1% to get a value for a vh unit
|
|
136
101
|
let vh = window.innerHeight * 0.01;
|
|
137
102
|
// Then we set the value in the --vh custom property to the root of the document
|
|
@@ -157,16 +122,7 @@ function Layout({ children }: ReactNode): JSX.Element {
|
|
|
157
122
|
<Loading />
|
|
158
123
|
</Box>
|
|
159
124
|
)}
|
|
160
|
-
<
|
|
161
|
-
indexName={indexName}
|
|
162
|
-
searchClient={conditionalQuery}
|
|
163
|
-
searchState={valueTextSearch}
|
|
164
|
-
onSearchStateChange={state => {
|
|
165
|
-
if (state.page && state.query !== undefined) {
|
|
166
|
-
dispatch(changeValueTextSearch(state));
|
|
167
|
-
}
|
|
168
|
-
}}
|
|
169
|
-
>
|
|
125
|
+
<InstantSearchProvider>
|
|
170
126
|
{isMobile && showApp && <AppMobile>{children}</AppMobile>}
|
|
171
127
|
{!isMobile && showApp && (
|
|
172
128
|
<div className={`layout-main-${classNameBoxVersion}`}>
|
|
@@ -199,10 +155,11 @@ function Layout({ children }: ReactNode): JSX.Element {
|
|
|
199
155
|
<FooterApp />
|
|
200
156
|
</div>
|
|
201
157
|
)}
|
|
158
|
+
{showPoweredByNyris && <PoweredByNyris />}
|
|
202
159
|
</div>
|
|
203
160
|
)}
|
|
204
161
|
{!showApp && <> {children}</>}
|
|
205
|
-
</
|
|
162
|
+
</InstantSearchProvider>
|
|
206
163
|
</div>
|
|
207
164
|
);
|
|
208
165
|
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { useAppSelector } from 'Store/Store';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
|
|
4
|
+
import { useMediaQuery } from 'react-responsive';
|
|
5
|
+
|
|
6
|
+
function NoAccess() {
|
|
7
|
+
const isMobile = useMediaQuery({ query: '(max-width: 776px)' });
|
|
8
|
+
const { supportEmail } = useAppSelector(state => state.settings.auth0);
|
|
9
|
+
|
|
10
|
+
return (
|
|
11
|
+
<div
|
|
12
|
+
style={{
|
|
13
|
+
backgroundColor: 'white',
|
|
14
|
+
height: '100%',
|
|
15
|
+
paddingLeft: isMobile ? '32px' : '64px',
|
|
16
|
+
paddingTop: isMobile ? '24px' : '32px',
|
|
17
|
+
display: 'flex',
|
|
18
|
+
flexDirection: 'column',
|
|
19
|
+
gap: '16px',
|
|
20
|
+
}}
|
|
21
|
+
>
|
|
22
|
+
<div
|
|
23
|
+
style={{
|
|
24
|
+
fontSize: '20px',
|
|
25
|
+
fontWeight: 'bold',
|
|
26
|
+
color: '#2B2C46',
|
|
27
|
+
}}
|
|
28
|
+
>
|
|
29
|
+
Email verification is required
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
<div
|
|
33
|
+
style={{
|
|
34
|
+
width: '200px',
|
|
35
|
+
height: '4px',
|
|
36
|
+
background: '#3E36DC',
|
|
37
|
+
borderRadius: '4px',
|
|
38
|
+
}}
|
|
39
|
+
/>
|
|
40
|
+
|
|
41
|
+
<p style={{ color: '#2B2C46', fontSize: '13px' }}>
|
|
42
|
+
Please verify your email for access to the Search Suite. If you haven't
|
|
43
|
+
received your verification email yet, contact support.
|
|
44
|
+
</p>
|
|
45
|
+
<a
|
|
46
|
+
className="contact-support"
|
|
47
|
+
style={{
|
|
48
|
+
backgroundColor: '#2B2C46',
|
|
49
|
+
padding: '8px 16px 8px 16px',
|
|
50
|
+
color: '#fff',
|
|
51
|
+
border: 'none',
|
|
52
|
+
cursor: 'pointer',
|
|
53
|
+
fontSize: '14px',
|
|
54
|
+
width: 'fit-content',
|
|
55
|
+
}}
|
|
56
|
+
href={`mailto:${
|
|
57
|
+
supportEmail || 'support@nyris.io'
|
|
58
|
+
}?subject=Resend Email Verification&body=`}
|
|
59
|
+
>
|
|
60
|
+
Contact Support
|
|
61
|
+
</a>
|
|
62
|
+
</div>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export default NoAccess;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { ReactComponent as PoweredByNyrisImage } from 'common/assets/images/powered_by_nyris.svg';
|
|
3
|
+
import { ReactComponent as PoweredByNyrisImageColored } from 'common/assets/images/powered_by_nyris_colored.svg';
|
|
4
|
+
|
|
5
|
+
function PoweredByNyris() {
|
|
6
|
+
const [isHovered, setHovered] = useState(false);
|
|
7
|
+
|
|
8
|
+
const handleMouseOver = () => {
|
|
9
|
+
setHovered(true);
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const handleMouseOut = () => {
|
|
13
|
+
setHovered(false);
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<div
|
|
18
|
+
className="powered-by-nyris"
|
|
19
|
+
style={{
|
|
20
|
+
display: 'flex',
|
|
21
|
+
padding: '9px 0px',
|
|
22
|
+
justifyContent: 'center',
|
|
23
|
+
borderTop: '1px solid #E0E0E0',
|
|
24
|
+
}}
|
|
25
|
+
onMouseOver={handleMouseOver}
|
|
26
|
+
onMouseOut={handleMouseOut}
|
|
27
|
+
>
|
|
28
|
+
{isHovered && (
|
|
29
|
+
<PoweredByNyrisImageColored
|
|
30
|
+
style={{ cursor: 'pointer' }}
|
|
31
|
+
onClick={() => {
|
|
32
|
+
window.open('https://www.nyris.io', '_blank');
|
|
33
|
+
}}
|
|
34
|
+
/>
|
|
35
|
+
)}
|
|
36
|
+
{!isHovered && (
|
|
37
|
+
<PoweredByNyrisImage
|
|
38
|
+
style={{ cursor: 'pointer' }}
|
|
39
|
+
onClick={() => {
|
|
40
|
+
window.open('https://www.nyris.io', '_blank');
|
|
41
|
+
}}
|
|
42
|
+
color="#2B2C46"
|
|
43
|
+
/>
|
|
44
|
+
)}
|
|
45
|
+
</div>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export default PoweredByNyris;
|
|
@@ -20,6 +20,7 @@ import { useTranslation } from 'react-i18next';
|
|
|
20
20
|
import ProductAttribute from './ProductAttribute';
|
|
21
21
|
import CadenasWebViewer from './CadenasWebViewer';
|
|
22
22
|
import { makeStyles } from '@material-ui/core/styles';
|
|
23
|
+
import { get } from 'lodash';
|
|
23
24
|
|
|
24
25
|
const useStyles = makeStyles(theme => ({
|
|
25
26
|
buttonStyle3D: {
|
|
@@ -58,7 +59,7 @@ function ProductDetailView(props: Props) {
|
|
|
58
59
|
const isMobile = useMediaQuery({ query: '(max-width: 776px)' });
|
|
59
60
|
const { settings } = useAppSelector<AppState>((state: any) => state);
|
|
60
61
|
const brand = dataItem[settings.field.productTag];
|
|
61
|
-
|
|
62
|
+
|
|
62
63
|
const [collapDescription, setCollapDescription] = useState(false);
|
|
63
64
|
const [feedback, setFeedback] = useState('none');
|
|
64
65
|
const [is3dView, setIs3dView] = useState(show3dView);
|
|
@@ -70,6 +71,11 @@ function ProductDetailView(props: Props) {
|
|
|
70
71
|
const { t } = useTranslation();
|
|
71
72
|
const classes = useStyles(props?.show3dView);
|
|
72
73
|
|
|
74
|
+
const ctaLink = get(
|
|
75
|
+
dataItem,
|
|
76
|
+
settings.field?.ctaLinkField ? settings.field?.ctaLinkField : 'links.main',
|
|
77
|
+
);
|
|
78
|
+
|
|
73
79
|
useEffect(() => {
|
|
74
80
|
if (dataItem) {
|
|
75
81
|
checkDataItemResult(dataItem);
|
|
@@ -155,13 +161,16 @@ function ProductDetailView(props: Props) {
|
|
|
155
161
|
height: is3dView ? '0px' : !isMobile ? '60%' : '368px',
|
|
156
162
|
opacity: is3dView ? 0 : 1,
|
|
157
163
|
transition: !is3dView ? 'opacity 3s ease' : '',
|
|
158
|
-
paddingTop: '16px',
|
|
164
|
+
paddingTop: !is3dView ? '16px' : '0px',
|
|
159
165
|
}}
|
|
160
166
|
>
|
|
161
167
|
{dataImageCarousel.length > 0 && (
|
|
162
|
-
<ImagePreviewCarousel
|
|
163
|
-
|
|
164
|
-
|
|
168
|
+
<ImagePreviewCarousel
|
|
169
|
+
imgItem={dataImageCarousel}
|
|
170
|
+
setSelectedImage={url => {
|
|
171
|
+
setUrlImage(url ? url : urlImage);
|
|
172
|
+
}}
|
|
173
|
+
/>
|
|
165
174
|
)}
|
|
166
175
|
{dataImageCarousel.length > 0 && (
|
|
167
176
|
<Button
|
|
@@ -215,7 +224,7 @@ function ProductDetailView(props: Props) {
|
|
|
215
224
|
style={{
|
|
216
225
|
position: 'absolute',
|
|
217
226
|
left: '16px',
|
|
218
|
-
bottom: isMobile ? '25px' : '10px'
|
|
227
|
+
bottom: isMobile ? '25px' : '10px',
|
|
219
228
|
}}
|
|
220
229
|
>
|
|
221
230
|
{!is3dView &&
|
|
@@ -427,10 +436,7 @@ function ProductDetailView(props: Props) {
|
|
|
427
436
|
}}
|
|
428
437
|
onClick={() => {
|
|
429
438
|
if (ctaLink) {
|
|
430
|
-
window.open(
|
|
431
|
-
`${dataItem[settings.field.ctaLinkField]}`,
|
|
432
|
-
'_blank',
|
|
433
|
-
);
|
|
439
|
+
window.open(`${ctaLink}`, '_blank');
|
|
434
440
|
}
|
|
435
441
|
}}
|
|
436
442
|
>
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { Box } from '@material-ui/core';
|
|
2
2
|
import ItemResult from 'components/results/ItemResult';
|
|
3
|
-
import {
|
|
4
|
-
import React, { memo, useEffect, useMemo, useState } from 'react';
|
|
3
|
+
import React, { memo, useMemo } from 'react';
|
|
5
4
|
import { useTranslation } from 'react-i18next';
|
|
6
5
|
import { connectStateResults } from 'react-instantsearch-dom';
|
|
7
6
|
import { useMediaQuery } from 'react-responsive';
|
|
8
7
|
import { useAppSelector } from 'Store/Store';
|
|
9
8
|
import { AppState } from 'types';
|
|
9
|
+
import { useProductList } from './useProductList';
|
|
10
10
|
|
|
11
11
|
interface Props {
|
|
12
12
|
allSearchResults: any;
|
|
@@ -29,106 +29,29 @@ function ProductListComponent({
|
|
|
29
29
|
}: any): JSX.Element {
|
|
30
30
|
const { search, settings } = useAppSelector<AppState>((state: any) => state);
|
|
31
31
|
const { loadingSearchAlgolia } = search;
|
|
32
|
-
const [hitGroups, setHitGroups] = useState<any>({});
|
|
33
|
-
const [itemShowDefault, setItemShowDefault] = useState<any[]>([]);
|
|
34
|
-
const [algoliaRequest, setAlgoliaRequest] = useState(false);
|
|
35
32
|
const isMobile = useMediaQuery({ query: '(max-width: 776px)' });
|
|
36
33
|
const { t } = useTranslation();
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
setAlgoliaRequest(true);
|
|
41
|
-
}
|
|
42
|
-
}, [isSearchStalled]);
|
|
43
|
-
|
|
44
|
-
useEffect(() => {
|
|
45
|
-
if (!allSearchResults?.hits?.length) {
|
|
46
|
-
setItemShowDefault([]);
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
setAlgoliaRequest(false);
|
|
50
|
-
const listHistDefaultGroups = settings.showGroup
|
|
51
|
-
? setListHitDefault(allSearchResults?.hits)
|
|
52
|
-
: allSearchResults?.hits;
|
|
53
|
-
setItemShowDefault(listHistDefaultGroups);
|
|
54
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
55
|
-
}, [allSearchResults?.hits, search?.valueTextSearch]);
|
|
56
|
-
|
|
57
|
-
const setListHitDefault = (hits: any) => {
|
|
58
|
-
let newArrayShowGroup: any = [];
|
|
59
|
-
let newArrayShowItem: any = [];
|
|
60
|
-
|
|
61
|
-
const groupHits = hits.map((hit: { group_id: string }) => {
|
|
62
|
-
if (!hit.group_id) {
|
|
63
|
-
return { ...hit, group_id: uniqueId('random-group-id') };
|
|
64
|
-
}
|
|
65
|
-
return hit;
|
|
34
|
+
const { productList, handlerCloseGroup, handlerGroupItem, algoliaRequest } =
|
|
35
|
+
useProductList({
|
|
36
|
+
allSearchResults,
|
|
66
37
|
});
|
|
67
38
|
|
|
68
|
-
const groups = groupBy(groupHits, 'group_id');
|
|
69
|
-
setHitGroups(groups);
|
|
70
|
-
newArrayShowGroup = Object.values(groups);
|
|
71
|
-
if (newArrayShowGroup.length === 0) {
|
|
72
|
-
return hits;
|
|
73
|
-
}
|
|
74
|
-
newArrayShowGroup.forEach((item: any) => {
|
|
75
|
-
let payload: any;
|
|
76
|
-
if (item.length >= 2) {
|
|
77
|
-
payload = {
|
|
78
|
-
...item[0],
|
|
79
|
-
isGroup: true,
|
|
80
|
-
collap: true,
|
|
81
|
-
};
|
|
82
|
-
newArrayShowItem.push(payload);
|
|
83
|
-
} else {
|
|
84
|
-
payload = {
|
|
85
|
-
...item[0],
|
|
86
|
-
isGroup: false,
|
|
87
|
-
collap: null,
|
|
88
|
-
};
|
|
89
|
-
newArrayShowItem.push(payload);
|
|
90
|
-
}
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
return newArrayShowItem;
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
const handlerGroupItem = (hit: any, index: number) => {
|
|
97
|
-
const group_id = hit.group_id;
|
|
98
|
-
let newItemList = [...itemShowDefault];
|
|
99
|
-
const firstArr = newItemList.slice(0, index + 1);
|
|
100
|
-
firstArr.filter(item => item.group_id === group_id)[0].collap = false;
|
|
101
|
-
let secondArr = newItemList.slice(index + 1, newItemList.length);
|
|
102
|
-
let otherItemsInGroup = [...hitGroups[group_id]];
|
|
103
|
-
otherItemsInGroup.shift();
|
|
104
|
-
secondArr = otherItemsInGroup.concat(secondArr);
|
|
105
|
-
setItemShowDefault(firstArr.concat(secondArr));
|
|
106
|
-
};
|
|
107
|
-
const handlerCloseGroup = (hit: any, index: number) => {
|
|
108
|
-
const group_id = hit.group_id;
|
|
109
|
-
let newItemList = [...itemShowDefault];
|
|
110
|
-
const firstArr = newItemList.slice(0, index + 1);
|
|
111
|
-
firstArr.filter(item => item.group_id === group_id)[0].collap = true;
|
|
112
|
-
let secondArr = newItemList.slice(index + 1, newItemList.length);
|
|
113
|
-
secondArr = secondArr.filter(item => {
|
|
114
|
-
return item.group_id !== group_id;
|
|
115
|
-
});
|
|
116
|
-
setItemShowDefault(firstArr.concat(secondArr));
|
|
117
|
-
};
|
|
118
|
-
|
|
119
39
|
const renderItem = useMemo(() => {
|
|
120
|
-
if (
|
|
40
|
+
if (
|
|
41
|
+
!requestImage &&
|
|
42
|
+
!search.valueTextSearch.query &&
|
|
43
|
+
!searchQuery &&
|
|
44
|
+
!isSearchStalled
|
|
45
|
+
) {
|
|
121
46
|
return (
|
|
122
47
|
<Box style={{ marginTop: '50px', width: '100%', textAlign: 'center' }}>
|
|
123
48
|
{t('Please upload an image or enter a keyword to search.')}
|
|
124
49
|
</Box>
|
|
125
50
|
);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
itemShowDefault.length === 0 &&
|
|
51
|
+
} else if (
|
|
52
|
+
productList.length === 0 &&
|
|
129
53
|
!loadingSearchAlgolia &&
|
|
130
|
-
!isSearchStalled
|
|
131
|
-
(algoliaRequest || requestImage)
|
|
54
|
+
!isSearchStalled
|
|
132
55
|
) {
|
|
133
56
|
return (
|
|
134
57
|
<Box style={{ marginTop: '50px', width: '100%', textAlign: 'center' }}>
|
|
@@ -136,7 +59,7 @@ function ProductListComponent({
|
|
|
136
59
|
</Box>
|
|
137
60
|
);
|
|
138
61
|
}
|
|
139
|
-
return
|
|
62
|
+
return productList.map((hit: any, i: number) => {
|
|
140
63
|
return (
|
|
141
64
|
<Box key={i} style={{ height: 'fit-content' }}>
|
|
142
65
|
<ItemResult
|
|
@@ -166,7 +89,7 @@ function ProductListComponent({
|
|
|
166
89
|
});
|
|
167
90
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
168
91
|
}, [
|
|
169
|
-
|
|
92
|
+
productList,
|
|
170
93
|
searchQuery,
|
|
171
94
|
requestImage,
|
|
172
95
|
search.valueTextSearch,
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { useAppSelector } from 'Store/Store';
|
|
2
|
+
import { groupBy, uniqueId } from 'lodash';
|
|
3
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
4
|
+
|
|
5
|
+
export const useProductList = ({ allSearchResults, isSearchStalled }: any) => {
|
|
6
|
+
const { search, settings } = useAppSelector(state => state);
|
|
7
|
+
const { valueTextSearch, results } = search || {};
|
|
8
|
+
const { showGroup, algolia } = settings || {};
|
|
9
|
+
const [itemShowDefault, setItemShowDefault] = useState<any[]>([]);
|
|
10
|
+
const [algoliaRequest, setAlgoliaRequest] = useState(false);
|
|
11
|
+
const [hitGroups, setHitGroups] = useState<any>({});
|
|
12
|
+
|
|
13
|
+
const setListHitDefault = (hits: any) => {
|
|
14
|
+
let newArrayShowGroup: any = [];
|
|
15
|
+
let newArrayShowItem: any = [];
|
|
16
|
+
|
|
17
|
+
const groupHits = hits.map((hit: { group_id: string }) => {
|
|
18
|
+
if (!hit.group_id) {
|
|
19
|
+
return { ...hit, group_id: uniqueId('random-group-id') };
|
|
20
|
+
}
|
|
21
|
+
return hit;
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const groups = groupBy(groupHits, 'group_id');
|
|
25
|
+
setHitGroups(groups);
|
|
26
|
+
newArrayShowGroup = Object.values(groups);
|
|
27
|
+
if (newArrayShowGroup?.length === 0) {
|
|
28
|
+
return hits;
|
|
29
|
+
}
|
|
30
|
+
newArrayShowGroup.forEach((item: any) => {
|
|
31
|
+
let payload: any;
|
|
32
|
+
if (item?.length >= 2) {
|
|
33
|
+
payload = {
|
|
34
|
+
...item[0],
|
|
35
|
+
isGroup: true,
|
|
36
|
+
collap: true,
|
|
37
|
+
};
|
|
38
|
+
newArrayShowItem.push(payload);
|
|
39
|
+
} else {
|
|
40
|
+
payload = {
|
|
41
|
+
...item[0],
|
|
42
|
+
isGroup: false,
|
|
43
|
+
collap: null,
|
|
44
|
+
};
|
|
45
|
+
newArrayShowItem.push(payload);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
return newArrayShowItem;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const handlerGroupItem = (hit: any, index: number) => {
|
|
53
|
+
const group_id = hit.group_id;
|
|
54
|
+
let newItemList = [...itemShowDefault];
|
|
55
|
+
const firstArr = newItemList.slice(0, index + 1);
|
|
56
|
+
firstArr.filter(item => item.group_id === group_id)[0].collap = false;
|
|
57
|
+
let secondArr = newItemList.slice(index + 1, newItemList.length);
|
|
58
|
+
let otherItemsInGroup = [...hitGroups[group_id]];
|
|
59
|
+
otherItemsInGroup.shift();
|
|
60
|
+
secondArr = otherItemsInGroup.concat(secondArr);
|
|
61
|
+
setItemShowDefault(firstArr.concat(secondArr));
|
|
62
|
+
};
|
|
63
|
+
const handlerCloseGroup = (hit: any, index: number) => {
|
|
64
|
+
const group_id = hit.group_id;
|
|
65
|
+
let newItemList = [...itemShowDefault];
|
|
66
|
+
const firstArr = newItemList.slice(0, index + 1);
|
|
67
|
+
firstArr.filter(item => item.group_id === group_id)[0].collap = true;
|
|
68
|
+
let secondArr = newItemList.slice(index + 1, newItemList.length);
|
|
69
|
+
secondArr = secondArr.filter(item => {
|
|
70
|
+
return item.group_id !== group_id;
|
|
71
|
+
});
|
|
72
|
+
setItemShowDefault(firstArr.concat(secondArr));
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
useEffect(() => {
|
|
76
|
+
if (!allSearchResults?.hits?.length) {
|
|
77
|
+
setItemShowDefault([]);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
setAlgoliaRequest(false);
|
|
81
|
+
const listHistDefaultGroups = showGroup
|
|
82
|
+
? setListHitDefault(allSearchResults?.hits)
|
|
83
|
+
: allSearchResults?.hits;
|
|
84
|
+
setItemShowDefault(listHistDefaultGroups);
|
|
85
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
86
|
+
}, [allSearchResults?.hits, valueTextSearch]);
|
|
87
|
+
|
|
88
|
+
useEffect(() => {
|
|
89
|
+
if (isSearchStalled) {
|
|
90
|
+
setAlgoliaRequest(true);
|
|
91
|
+
}
|
|
92
|
+
}, [isSearchStalled]);
|
|
93
|
+
|
|
94
|
+
const productList = useMemo(() => {
|
|
95
|
+
return results?.map((item: any) => {
|
|
96
|
+
return {
|
|
97
|
+
...item,
|
|
98
|
+
main_image_link: item.image || item.images ? item.images[0] : '',
|
|
99
|
+
};
|
|
100
|
+
});
|
|
101
|
+
}, [results]);
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
productList: algolia?.enabled ? itemShowDefault : productList || [],
|
|
105
|
+
handlerGroupItem,
|
|
106
|
+
handlerCloseGroup,
|
|
107
|
+
algoliaRequest,
|
|
108
|
+
isLoading: false,
|
|
109
|
+
hasError: false,
|
|
110
|
+
errorMessage: '',
|
|
111
|
+
loadProductList: () => {},
|
|
112
|
+
reset: () => {},
|
|
113
|
+
};
|
|
114
|
+
};
|