@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.
Files changed (65) hide show
  1. package/build/asset-manifest.json +16 -12
  2. package/build/index.html +1 -1
  3. package/build/{precache-manifest.009e864ff0764cf3cf8a9b290c334099.js → precache-manifest.c92406fe2e3b0feaf429c551125e2d95.js} +26 -10
  4. package/build/service-worker.js +1 -1
  5. package/build/static/css/main.1b40c5ff.chunk.css +2 -0
  6. package/build/static/css/main.1b40c5ff.chunk.css.map +1 -0
  7. package/build/static/js/2.82ef1cd4.chunk.js +3 -0
  8. package/build/static/js/2.82ef1cd4.chunk.js.map +1 -0
  9. package/build/static/js/main.7cdac2fb.chunk.js +3 -0
  10. package/build/static/js/main.7cdac2fb.chunk.js.map +1 -0
  11. package/build/static/media/avatar.4c5346ed.svg +3 -0
  12. package/build/static/media/logout.07b9ef7f.svg +3 -0
  13. package/build/static/media/powered_by_nyris.e6766baf.svg +3 -0
  14. package/build/static/media/powered_by_nyris_colored.08d00bae.svg +9 -0
  15. package/package.json +3 -3
  16. package/public/index.html +4 -16
  17. package/src/Router.tsx +1 -0
  18. package/src/Store/Store.ts +2 -4
  19. package/src/Store/constants.ts +6 -0
  20. package/src/Store/search/Search.ts +10 -3
  21. package/src/Store/search/types.ts +1 -0
  22. package/src/common/assets/icons/avatar.svg +3 -0
  23. package/src/common/assets/icons/logout.svg +3 -0
  24. package/src/common/assets/images/powered_by_nyris.svg +3 -0
  25. package/src/common/assets/images/powered_by_nyris_colored.svg +9 -0
  26. package/src/components/AppMobile.tsx +1 -0
  27. package/src/components/AuthenticatedRoute.tsx +4 -1
  28. package/src/components/DragDropFile.tsx +3 -4
  29. package/src/components/FooterMobile.tsx +137 -24
  30. package/src/components/Header.tsx +120 -1
  31. package/src/components/HeaderMobile.tsx +230 -163
  32. package/src/components/ImagePreviewMobile.tsx +57 -8
  33. package/src/components/Layout.tsx +10 -53
  34. package/src/components/NoAccess.tsx +66 -0
  35. package/src/components/PoweredByNyris.tsx +49 -0
  36. package/src/components/ProductDetailView.tsx +16 -10
  37. package/src/components/ProductList/index.tsx +16 -93
  38. package/src/components/ProductList/useProductList.ts +114 -0
  39. package/src/components/Provider/InstantSearchProvider.tsx +66 -0
  40. package/src/components/appMobile.scss +2 -2
  41. package/src/components/common.scss +8 -1
  42. package/src/components/drawer/cameraCustom.tsx +3 -3
  43. package/src/components/input/inputSearch.tsx +86 -17
  44. package/src/components/pre-filter/index.tsx +58 -7
  45. package/src/components/results/ItemResult.tsx +6 -1
  46. package/src/index.tsx +1 -1
  47. package/src/page/landingPage/common.scss +4 -1
  48. package/src/page/result/index.tsx +154 -131
  49. package/src/services/Feedback.ts +1 -1
  50. package/src/services/image.ts +8 -5
  51. package/src/types.ts +13 -12
  52. package/build/static/css/main.86d40309.chunk.css +0 -2
  53. package/build/static/css/main.86d40309.chunk.css.map +0 -1
  54. package/build/static/js/2.1757789c.chunk.js +0 -3
  55. package/build/static/js/2.1757789c.chunk.js.map +0 -1
  56. package/build/static/js/main.1d184393.chunk.js +0 -3
  57. package/build/static/js/main.1d184393.chunk.js.map +0 -1
  58. package/src/Store/auth/Auth.ts +0 -33
  59. package/src/Store/auth/types.ts +0 -11
  60. package/src/Store/nyris/Nyris.ts +0 -67
  61. package/src/Store/nyris/types.ts +0 -11
  62. package/src/components/Feedback.tsx +0 -91
  63. /package/build/static/js/{2.1757789c.chunk.js.LICENSE.txt → 2.82ef1cd4.chunk.js.LICENSE.txt} +0 -0
  64. /package/build/static/js/{main.1d184393.chunk.js.LICENSE.txt → main.7cdac2fb.chunk.js.LICENSE.txt} +0 -0
  65. /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 { reset } from 'Store/search/Search';
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 onImageRemove = () => {
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
- // not an ideal solution: fixes text search not working after removing image
58
- setTimeout(() => {
59
- refine(searchQuery);
60
- }, 100);
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, useMemo, useState } from 'react';
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 { AlgoliaSettings, AppState } from '../types';
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 { valueTextSearch, loadingSearchAlgolia } = search;
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
- <InstantSearch
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
- </InstantSearch>
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
- const ctaLink = dataItem[settings.field?.ctaLinkField];
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 imgItem={dataImageCarousel} setSelectedImage={(url) => {
163
- setUrlImage(url ? url : urlImage);
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 { groupBy, uniqueId } from 'lodash';
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
- useEffect(() => {
39
- if (isSearchStalled) {
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 (!requestImage && !search.valueTextSearch.query && !isSearchStalled) {
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
- if (
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 itemShowDefault.map((hit: any, i: number) => {
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
- itemShowDefault,
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
+ };