@nyris/nyris-webapp 0.3.41 → 0.3.43
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/_redirects +1 -0
- package/build/asset-manifest.json +16 -12
- package/build/index.html +1 -1
- package/build/{precache-manifest.d9bae9c81b3a390a89a437be45e3a94b.js → precache-manifest.fdacc61375beca4170228eb2951d87b0.js} +27 -11
- 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.1e5f374f.chunk.js.LICENSE.txt → 2.82ef1cd4.chunk.js.LICENSE.txt} +2 -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 +4 -3
- package/public/_redirects +1 -0
- package/src/App.tsx +0 -17
- package/src/Router.tsx +13 -36
- package/src/Store/Store.ts +2 -4
- package/src/Store/constants.ts +7 -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 +38 -0
- package/src/components/CadenasWebViewer.tsx +5 -2
- 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 +16 -56
- package/src/components/NoAccess.tsx +66 -0
- package/src/components/PoweredByNyris.tsx +49 -0
- package/src/components/ProductDetailView.tsx +17 -7
- package/src/components/ProductList/index.tsx +16 -93
- package/src/components/ProductList/useProductList.ts +114 -0
- package/src/components/Provider/AuthProvider.tsx +25 -0
- package/src/components/Provider/InstantSearchProvider.tsx +66 -0
- package/src/components/appMobile.scss +2 -2
- package/src/components/carousel/ImagePreviewCarousel.tsx +6 -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/components/rfq/RfqBanner.tsx +0 -1
- package/src/index.tsx +9 -6
- package/src/page/Login.tsx +21 -0
- package/src/page/Logout.tsx +23 -0
- 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 +35 -25
- 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.1e5f374f.chunk.js +0 -3
- package/build/static/js/2.1e5f374f.chunk.js.map +0 -1
- package/build/static/js/main.909efae2.chunk.js +0 -3
- package/build/static/js/main.909efae2.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/{main.909efae2.chunk.js.LICENSE.txt → main.7cdac2fb.chunk.js.LICENSE.txt} +0 -0
|
@@ -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';
|
|
@@ -26,11 +19,13 @@ import Loading from './Loading';
|
|
|
26
19
|
import i18n from 'i18next';
|
|
27
20
|
import { initReactI18next } from 'react-i18next';
|
|
28
21
|
import { translations } from 'translations';
|
|
22
|
+
import { useAuth0 } from '@auth0/auth0-react';
|
|
23
|
+
import InstantSearchProvider from './Provider/InstantSearchProvider';
|
|
24
|
+
import PoweredByNyris from './PoweredByNyris';
|
|
29
25
|
|
|
30
26
|
declare var psol: any;
|
|
31
27
|
|
|
32
28
|
jQuery(document).ready(function () {
|
|
33
|
-
psol.core.setApiKey('66c56a38010f4a81a82f6ed51c903399');
|
|
34
29
|
psol.core.setUserInfo({
|
|
35
30
|
server_type: 'oem_apps_cadenas_webcomponentsdemo',
|
|
36
31
|
title: 'Herr',
|
|
@@ -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();
|
|
@@ -69,7 +63,9 @@ function Layout({ children }: ReactNode): JSX.Element {
|
|
|
69
63
|
(isMobile && history.location?.pathname === '/result') ||
|
|
70
64
|
history.location?.pathname === '/';
|
|
71
65
|
const language = useAppSelector(state => state.settings.language);
|
|
72
|
-
|
|
66
|
+
const { isAuthenticated } = useAuth0();
|
|
67
|
+
const { auth0, showPoweredByNyris } = settings;
|
|
68
|
+
const showApp = !auth0.enabled || (auth0.enabled && isAuthenticated);
|
|
73
69
|
i18n.changeLanguage(language);
|
|
74
70
|
|
|
75
71
|
useEffect(() => {
|
|
@@ -101,35 +97,6 @@ function Layout({ children }: ReactNode): JSX.Element {
|
|
|
101
97
|
HeaderApp = Header;
|
|
102
98
|
}
|
|
103
99
|
|
|
104
|
-
const conditionalQuery = useMemo(() => {
|
|
105
|
-
const searchClient = algoliasearch(appId, apiKey);
|
|
106
|
-
searchClient.initIndex(indexName);
|
|
107
|
-
return {
|
|
108
|
-
...searchClient,
|
|
109
|
-
search(requests: MultipleQueriesQuery[]) {
|
|
110
|
-
if (
|
|
111
|
-
requests.every(
|
|
112
|
-
(request: MultipleQueriesQuery) =>
|
|
113
|
-
!request.params?.query &&
|
|
114
|
-
(!request.params?.filters ||
|
|
115
|
-
request.params?.filters.endsWith('<score=1>')),
|
|
116
|
-
)
|
|
117
|
-
) {
|
|
118
|
-
// Here we have to do something else
|
|
119
|
-
return Promise.resolve({
|
|
120
|
-
results: requests.map(() => ({
|
|
121
|
-
hits: [],
|
|
122
|
-
nbHits: 0,
|
|
123
|
-
nbPages: 0,
|
|
124
|
-
processingTimeMS: 0,
|
|
125
|
-
})),
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
|
-
return searchClient.search(requests);
|
|
129
|
-
},
|
|
130
|
-
};
|
|
131
|
-
}, [apiKey, appId, indexName]);
|
|
132
|
-
|
|
133
100
|
// First we get the viewport height and we multiple it by 1% to get a value for a vh unit
|
|
134
101
|
let vh = window.innerHeight * 0.01;
|
|
135
102
|
// Then we set the value in the --vh custom property to the root of the document
|
|
@@ -155,18 +122,9 @@ function Layout({ children }: ReactNode): JSX.Element {
|
|
|
155
122
|
<Loading />
|
|
156
123
|
</Box>
|
|
157
124
|
)}
|
|
158
|
-
<
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
searchState={valueTextSearch}
|
|
162
|
-
onSearchStateChange={state => {
|
|
163
|
-
if (state.page && state.query !== undefined) {
|
|
164
|
-
dispatch(changeValueTextSearch(state));
|
|
165
|
-
}
|
|
166
|
-
}}
|
|
167
|
-
>
|
|
168
|
-
{isMobile && <AppMobile>{children}</AppMobile>}
|
|
169
|
-
{!isMobile && (
|
|
125
|
+
<InstantSearchProvider>
|
|
126
|
+
{isMobile && showApp && <AppMobile>{children}</AppMobile>}
|
|
127
|
+
{!isMobile && showApp && (
|
|
170
128
|
<div className={`layout-main-${classNameBoxVersion}`}>
|
|
171
129
|
<div
|
|
172
130
|
className={
|
|
@@ -197,9 +155,11 @@ function Layout({ children }: ReactNode): JSX.Element {
|
|
|
197
155
|
<FooterApp />
|
|
198
156
|
</div>
|
|
199
157
|
)}
|
|
158
|
+
{showPoweredByNyris && <PoweredByNyris />}
|
|
200
159
|
</div>
|
|
201
160
|
)}
|
|
202
|
-
|
|
161
|
+
{!showApp && <> {children}</>}
|
|
162
|
+
</InstantSearchProvider>
|
|
203
163
|
</div>
|
|
204
164
|
);
|
|
205
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,11 +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
|
|
168
|
+
<ImagePreviewCarousel
|
|
169
|
+
imgItem={dataImageCarousel}
|
|
170
|
+
setSelectedImage={url => {
|
|
171
|
+
setUrlImage(url ? url : urlImage);
|
|
172
|
+
}}
|
|
173
|
+
/>
|
|
163
174
|
)}
|
|
164
175
|
{dataImageCarousel.length > 0 && (
|
|
165
176
|
<Button
|
|
@@ -175,6 +186,7 @@ function ProductDetailView(props: Props) {
|
|
|
175
186
|
justifyContent: 'center',
|
|
176
187
|
alignItems: 'center',
|
|
177
188
|
cursor: 'pointer',
|
|
189
|
+
bottom: isMobile ? '25px' : '4px',
|
|
178
190
|
}}
|
|
179
191
|
onClick={() => {
|
|
180
192
|
if (urlImage.length > 1) {
|
|
@@ -212,6 +224,7 @@ function ProductDetailView(props: Props) {
|
|
|
212
224
|
style={{
|
|
213
225
|
position: 'absolute',
|
|
214
226
|
left: '16px',
|
|
227
|
+
bottom: isMobile ? '25px' : '10px',
|
|
215
228
|
}}
|
|
216
229
|
>
|
|
217
230
|
{!is3dView &&
|
|
@@ -423,10 +436,7 @@ function ProductDetailView(props: Props) {
|
|
|
423
436
|
}}
|
|
424
437
|
onClick={() => {
|
|
425
438
|
if (ctaLink) {
|
|
426
|
-
window.open(
|
|
427
|
-
`${dataItem[settings.field.ctaLinkField]}`,
|
|
428
|
-
'_blank',
|
|
429
|
-
);
|
|
439
|
+
window.open(`${ctaLink}`, '_blank');
|
|
430
440
|
}
|
|
431
441
|
}}
|
|
432
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
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Auth0Provider } from '@auth0/auth0-react';
|
|
3
|
+
import { useAppSelector } from 'Store/Store';
|
|
4
|
+
|
|
5
|
+
const AuthProvider = ({ children }: any) => {
|
|
6
|
+
const settings = useAppSelector(state => state.settings);
|
|
7
|
+
|
|
8
|
+
if (!settings.auth0.enabled) {
|
|
9
|
+
return <>{children}</>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<Auth0Provider
|
|
14
|
+
domain={settings.auth0.domain || ''}
|
|
15
|
+
clientId={settings.auth0.clientId || ''}
|
|
16
|
+
authorizationParams={{
|
|
17
|
+
redirect_uri: window.location.origin,
|
|
18
|
+
}}
|
|
19
|
+
>
|
|
20
|
+
{children}
|
|
21
|
+
</Auth0Provider>
|
|
22
|
+
);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export default AuthProvider;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import React, { memo, useMemo } from 'react';
|
|
2
|
+
import { MultipleQueriesQuery } from '@algolia/client-search';
|
|
3
|
+
import algoliasearch from 'algoliasearch/lite';
|
|
4
|
+
import { ReactNode } from 'components/common';
|
|
5
|
+
import { InstantSearch } from 'react-instantsearch-dom';
|
|
6
|
+
import { changeValueTextSearch } from 'Store/search/Search';
|
|
7
|
+
import { useAppDispatch, useAppSelector } from 'Store/Store';
|
|
8
|
+
import { AlgoliaSettings } from 'types';
|
|
9
|
+
|
|
10
|
+
function InstantSearchProvider({ children }: ReactNode): JSX.Element {
|
|
11
|
+
const dispatch = useAppDispatch();
|
|
12
|
+
const { settings, search } = useAppSelector(state => state);
|
|
13
|
+
const { valueTextSearch } = search;
|
|
14
|
+
const { apiKey, appId, indexName } = settings.algolia as AlgoliaSettings;
|
|
15
|
+
const isAlgoliaEnabled = settings.algolia?.enabled;
|
|
16
|
+
|
|
17
|
+
const conditionalQuery = useMemo(() => {
|
|
18
|
+
const searchClient = algoliasearch(appId, apiKey);
|
|
19
|
+
searchClient.initIndex(indexName);
|
|
20
|
+
return {
|
|
21
|
+
...searchClient,
|
|
22
|
+
search(requests: MultipleQueriesQuery[]) {
|
|
23
|
+
if (
|
|
24
|
+
requests.every(
|
|
25
|
+
(request: MultipleQueriesQuery) =>
|
|
26
|
+
!request.params?.query &&
|
|
27
|
+
(!request.params?.filters ||
|
|
28
|
+
request.params?.filters.endsWith('<score=1>')),
|
|
29
|
+
)
|
|
30
|
+
) {
|
|
31
|
+
// Here we have to do something else
|
|
32
|
+
return Promise.resolve({
|
|
33
|
+
results: requests.map(() => ({
|
|
34
|
+
hits: [],
|
|
35
|
+
nbHits: 0,
|
|
36
|
+
nbPages: 0,
|
|
37
|
+
processingTimeMS: 0,
|
|
38
|
+
})),
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
if (isAlgoliaEnabled) {
|
|
42
|
+
return searchClient.search(requests);
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}, [apiKey, appId, indexName, isAlgoliaEnabled]);
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<div style={{ position: 'relative' }}>
|
|
50
|
+
<InstantSearch
|
|
51
|
+
indexName={indexName}
|
|
52
|
+
searchClient={conditionalQuery}
|
|
53
|
+
searchState={isAlgoliaEnabled ? valueTextSearch : {}}
|
|
54
|
+
onSearchStateChange={state => {
|
|
55
|
+
if (state.page && state.query !== undefined && isAlgoliaEnabled) {
|
|
56
|
+
dispatch(changeValueTextSearch(state));
|
|
57
|
+
}
|
|
58
|
+
}}
|
|
59
|
+
>
|
|
60
|
+
{children}
|
|
61
|
+
</InstantSearch>
|
|
62
|
+
</div>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export default memo(InstantSearchProvider);
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
|
|
42
42
|
background: #FFFFFF;
|
|
43
43
|
border-bottom: 1px solid #E9E9EC;
|
|
44
|
-
box-shadow: 0px 0px 16px rgba(170, 171, 181, 0.
|
|
44
|
+
box-shadow: 0px 0px 16px 0px rgba(170, 171, 181, 0.50);
|
|
45
45
|
border-radius: 32px;
|
|
46
46
|
|
|
47
47
|
width: 100%;
|
|
@@ -308,7 +308,7 @@
|
|
|
308
308
|
|
|
309
309
|
background: #FFFFFF;
|
|
310
310
|
border-bottom: 1px solid #E9E9EC;
|
|
311
|
-
box-shadow: 0px 0px 16px rgba(170, 171, 181, 0.
|
|
311
|
+
box-shadow: 0px 0px 16px 0px rgba(170, 171, 181, 0.50);
|
|
312
312
|
border-radius: 32px;
|
|
313
313
|
|
|
314
314
|
width: 100%;
|