@nyris/nyris-webapp 0.3.91 → 0.3.93

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 (107) hide show
  1. package/build/asset-manifest.json +6 -6
  2. package/build/data/related-parts.json +83 -0
  3. package/build/index.html +1 -1
  4. package/build/js/settings.example.js +3 -0
  5. package/build/static/css/main.fed38059.css +4 -0
  6. package/build/static/css/main.fed38059.css.map +1 -0
  7. package/build/static/js/main.c9bbbcb9.js +3 -0
  8. package/build/static/js/{main.f2255597.js.map → main.c9bbbcb9.js.map} +1 -1
  9. package/package.json +4 -3
  10. package/public/data/related-parts.json +83 -0
  11. package/public/js/settings.example.js +3 -0
  12. package/src/App.test.tsx +0 -1
  13. package/src/App.tsx +0 -1
  14. package/src/assets/arrow_down_expanded.svg +3 -0
  15. package/src/assets/arrow_enter.svg +3 -0
  16. package/src/assets/camera.svg +3 -0
  17. package/src/assets/close.svg +3 -0
  18. package/src/assets/enter.svg +3 -0
  19. package/src/assets/refresh.svg +3 -0
  20. package/src/assets/vizo_avatar.svg +16 -0
  21. package/src/components/Cadenas/CadenasWebViewer.tsx +1 -1
  22. package/src/components/Cart.tsx +48 -36
  23. package/src/components/ChatAssistant/ChatAssistant.tsx +289 -0
  24. package/src/components/ChatAssistant/MobileChatAssistant.tsx +291 -0
  25. package/src/components/ChatAssistant/OptionChip.tsx +78 -0
  26. package/src/components/ChatAssistant/index.ts +3 -0
  27. package/src/components/ChatAssistant/useChatAssistantLogic.ts +745 -0
  28. package/src/components/CurrentRefinements.tsx +2 -2
  29. package/src/components/CustomCameraDrawer.tsx +56 -13
  30. package/src/components/DragDropFile.tsx +5 -5
  31. package/src/components/ExperienceVisualSearch/ExperienceVisualSearch.tsx +1 -1
  32. package/src/components/Header.tsx +116 -96
  33. package/src/components/Hint.tsx +1 -2
  34. package/src/components/HitsPerPage.tsx +9 -3
  35. package/src/components/IdentifiedAttributes.tsx +159 -0
  36. package/src/components/ImagePreview.tsx +32 -17
  37. package/src/components/ImageUpload.tsx +16 -8
  38. package/src/components/Inquiry/InquiryBanner.tsx +1 -1
  39. package/src/components/Inquiry/InquiryModal.tsx +35 -29
  40. package/src/components/ItemSpecification.tsx +58 -126
  41. package/src/components/LocationInfoPopup.tsx +33 -33
  42. package/src/components/MatchNotificationBanner.tsx +90 -36
  43. package/src/components/PostFilter/PostFilter.tsx +1 -1
  44. package/src/components/PostFilter/PostFilterComponent.tsx +0 -1
  45. package/src/components/PostFilter/PostFilterFindApi.tsx +0 -1
  46. package/src/components/PoweredBy.tsx +1 -1
  47. package/src/components/PreFilter/PreFilter.tsx +14 -3
  48. package/src/components/PreFilter/PreFilterModal.tsx +0 -1
  49. package/src/components/Product/Product.tsx +15 -11
  50. package/src/components/Product/ProductAttribute.tsx +4 -5
  51. package/src/components/Product/ProductDetailViewModal.tsx +2 -4
  52. package/src/components/Product/ProductList.tsx +26 -13
  53. package/src/components/Rfq/RfqModal.tsx +1 -1
  54. package/src/components/SidePanel.tsx +128 -46
  55. package/src/components/SmartFilter.tsx +320 -0
  56. package/src/components/TextSearch.tsx +134 -70
  57. package/src/components/UploadDisclaimer.tsx +1 -1
  58. package/src/hooks/useBadResultsRecovery.ts +407 -0
  59. package/src/hooks/useEffectiveGroundingResults.ts +54 -0
  60. package/src/hooks/useGoodResultsChat.ts +651 -0
  61. package/src/hooks/useGroundedSearch.ts +88 -0
  62. package/src/hooks/useImageSearch.ts +139 -187
  63. package/src/hooks/useResultEvaluator.ts +417 -0
  64. package/src/index.css +1 -1
  65. package/src/index.tsx +0 -1
  66. package/src/layouts/AppLayout.tsx +53 -2
  67. package/src/pages/Home.tsx +11 -52
  68. package/src/pages/Login.tsx +1 -2
  69. package/src/pages/Logout.tsx +1 -1
  70. package/src/pages/Result.tsx +254 -200
  71. package/src/providers/AuthProvider.tsx +0 -1
  72. package/src/services/Feedback.ts +1 -1
  73. package/src/services/visualSearch.ts +0 -21
  74. package/src/services/vizo.ts +192 -4
  75. package/src/stores/chat/chatStore.ts +150 -0
  76. package/src/stores/chat/conversationStore.ts +300 -0
  77. package/src/stores/request/Misc/misc.slice.ts +2 -2
  78. package/src/stores/request/filter/filter.slice.ts +8 -8
  79. package/src/stores/request/query/query.slice.ts +2 -2
  80. package/src/stores/request/requestImage/requestImage.slice.ts +6 -6
  81. package/src/stores/request/specifications/specifications.slice.ts +10 -7
  82. package/src/stores/result/detectedRegions/detectedRegions.slice.ts +1 -1
  83. package/src/stores/result/prodcuts/products.initialState.ts +12 -0
  84. package/src/stores/result/prodcuts/products.slice.ts +28 -8
  85. package/src/stores/result/session/session.slice.ts +2 -2
  86. package/src/stores/smartFilters/smartFiltersStore.ts +270 -0
  87. package/src/stores/types.ts +41 -0
  88. package/src/stores/ui/ai/ai.initialState.ts +5 -0
  89. package/src/stores/ui/ai/ai.slice.ts +15 -0
  90. package/src/stores/ui/banner/banner.initialState.ts +6 -0
  91. package/src/stores/ui/banner/banner.slice.ts +14 -0
  92. package/src/stores/ui/feedback/feedback.slice.ts +1 -1
  93. package/src/stores/ui/loading/loading.slice.ts +4 -4
  94. package/src/stores/ui/uiStore.ts +7 -1
  95. package/src/styles/product.scss +0 -2
  96. package/src/types.ts +4 -8
  97. package/src/utils/cropImageToBase64.ts +32 -0
  98. package/src/utils/fetchProductImage.ts +109 -0
  99. package/src/utils/imageConverters.ts +124 -0
  100. package/src/utils/relatedParts.ts +35 -0
  101. package/src/utils/specificationFilter.ts +1 -5
  102. package/tailwind.config.js +3 -2
  103. package/build/static/css/main.734b52e1.css +0 -4
  104. package/build/static/css/main.734b52e1.css.map +0 -1
  105. package/build/static/js/main.f2255597.js +0 -3
  106. package/src/utils/addAssets.ts +0 -40
  107. /package/build/static/js/{main.f2255597.js.LICENSE.txt → main.c9bbbcb9.js.LICENSE.txt} +0 -0
@@ -30,7 +30,7 @@ function CurrentRefinements({ className }: { className?: string }) {
30
30
  key={value}
31
31
  className="h-6 pl-2 pr-0.5 py-0.5 bg-[#f3f3f5] rounded-[1px] border border-solid border-[#e0e0e0] justify-start items-center gap-2 inline-flex"
32
32
  >
33
- <div className="text-[#2b2c46] text-xs font-normal ">{value}</div>
33
+ <div className="text-[#3B3E5F] text-xs font-normal ">{value}</div>
34
34
  <div className="w-5 h-5 rounded-sm justify-center items-center flex cursor-pointer">
35
35
  <Icon
36
36
  name="close"
@@ -60,7 +60,7 @@ function CurrentRefinements({ className }: { className?: string }) {
60
60
  key={refinement.label}
61
61
  className="h-6 pl-2 pr-0.5 py-0.5 bg-[#f3f3f5] rounded-[1px] border border-solid border-[#e0e0e0] justify-start items-center gap-2 inline-flex"
62
62
  >
63
- <div className="text-[#2b2c46] text-xs font-normal ">
63
+ <div className="text-[#3B3E5F] text-xs font-normal ">
64
64
  {refinement.label}
65
65
  </div>
66
66
  <div className="w-5 h-5 rounded-sm justify-center items-center flex cursor-pointer">
@@ -25,12 +25,18 @@ interface Props {
25
25
  show: boolean;
26
26
  onClose: any;
27
27
  newSearch?: boolean;
28
+ handleImageUpload?: (image: any) => void;
28
29
  }
29
30
 
30
31
  const FACING_MODE_USER = 'environment';
31
32
 
32
33
  function CustomCamera(props: Props) {
33
- const { show: isToggle, onClose: onToggleModal, newSearch } = props;
34
+ const {
35
+ show: isToggle,
36
+ onClose: onToggleModal,
37
+ newSearch,
38
+ handleImageUpload,
39
+ } = props;
34
40
  const settings = window.settings;
35
41
  const webcamRef: any = useRef(null);
36
42
  const navigate = useNavigate();
@@ -44,12 +50,16 @@ function CustomCamera(props: Props) {
44
50
  const specifications = useRequestStore(state => state.specifications);
45
51
  const setSpecifications = useRequestStore(state => state.setSpecifications);
46
52
  const setRequestImages = useRequestStore(state => state.setRequestImages);
47
- const setNameplateNotificationText = useRequestStore(state => state.setNameplateNotificationText);
53
+ const setNameplateNotificationText = useRequestStore(
54
+ state => state.setNameplateNotificationText,
55
+ );
48
56
  const setAlgoliaFilter = useRequestStore(state => state.setAlgoliaFilter);
49
57
  const setPreFilter = useRequestStore(state => state.setPreFilter);
50
58
  const setShowLoading = useRequestStore(state => state.setShowLoading);
51
59
  const setNameplateImage = useRequestStore(state => state.setNameplateImage);
52
- const setShowNotMatchedError = useRequestStore(state => state.setShowNotMatchedError);
60
+ const setShowNotMatchedError = useRequestStore(
61
+ state => state.setShowNotMatchedError,
62
+ );
53
63
  const [capturedImages, setCapturedImages] = useState<HTMLCanvasElement[]>([]);
54
64
  const [currentIndex, setCurrentIndex] = useState(0);
55
65
  const [imageCaptureHelpModal, setImageCaptureHelpModal] = useState(false);
@@ -60,6 +70,12 @@ function CustomCamera(props: Props) {
60
70
  };
61
71
 
62
72
  const handlerFindImage = async (image: any) => {
73
+ if (handleImageUpload) {
74
+ handleImageUpload(image);
75
+ handleClose();
76
+ return;
77
+ }
78
+
63
79
  if (location.pathname !== '/result') {
64
80
  navigate('/result');
65
81
  }
@@ -77,35 +93,62 @@ function CustomCamera(props: Props) {
77
93
  settings,
78
94
  newSearch,
79
95
  showFeedback: true,
80
- }).then((singleImageResp) => {
81
- const specificationPrefilter = singleImageResp.image_analysis?.specification?.prefilter_value || null;
82
- const hasPrefilter = preFilterList.filter((filter: any) => filter.values.includes(specificationPrefilter));
96
+ }).then(singleImageResp => {
97
+ const specificationPrefilter =
98
+ singleImageResp.image_analysis?.specification?.prefilter_value || null;
99
+
100
+ const hasPrefilter = preFilterList.filter((filter: any) =>
101
+ filter.values.includes(specificationPrefilter),
102
+ );
83
103
  if (specificationPrefilter) {
84
104
  setRequestImages([]);
85
105
  setShowNotMatchedError(false);
86
106
  if (hasPrefilter.length) {
87
- setSpecifications(clone(singleImageResp.image_analysis.specification));
107
+ setSpecifications(
108
+ clone(singleImageResp.image_analysis.specification),
109
+ );
88
110
  setNameplateImage(image);
89
- setPreFilter({[singleImageResp.image_analysis?.specification?.prefilter_value]: true});
90
- setAlgoliaFilter(`${settings.alogoliaFilterField}:'${singleImageResp.image_analysis?.specification?.prefilter_value}'`);
111
+ setPreFilter({
112
+ [singleImageResp.image_analysis?.specification?.prefilter_value]:
113
+ true,
114
+ });
115
+ setAlgoliaFilter(
116
+ `${settings.alogoliaFilterField}:'${singleImageResp.image_analysis?.specification?.prefilter_value}'`,
117
+ );
91
118
 
92
119
  setShowLoading(false);
93
120
  handleClose();
94
121
 
95
- setNameplateNotificationText(t('We have successfully defined the search criteria', { prefilter_value: specificationPrefilter, preFilterTitle: window.settings.preFilterTitle?.toLocaleLowerCase() }));
122
+ setNameplateNotificationText(
123
+ t('We have successfully defined the search criteria', {
124
+ prefilter_value: specificationPrefilter,
125
+ preFilterTitle:
126
+ window.settings.preFilterTitle?.toLocaleLowerCase(),
127
+ }),
128
+ );
96
129
  setTimeout(() => {
97
130
  setNameplateNotificationText('');
98
131
  }, 5000);
99
132
  }
100
133
  if (!hasPrefilter.length && window.settings.preFilterOption) {
101
- setSpecifications(clone({...singleImageResp.image_analysis.specification, specificationPrefilter}));
134
+ setSpecifications(
135
+ clone({
136
+ ...singleImageResp.image_analysis.specification,
137
+ specificationPrefilter,
138
+ }),
139
+ );
102
140
  setPreFilter({});
103
141
  setAlgoliaFilter('');
104
142
  setShowLoading(false);
105
143
  handleClose();
106
144
  setShowNotMatchedError(true);
107
145
  setTimeout(() => {
108
- setNameplateNotificationText(t('Extracted details from the nameplate could not be matched', { preFilterTitle: window.settings.preFilterTitle?.toLocaleLowerCase() }));
146
+ setNameplateNotificationText(
147
+ t('Extracted details from the nameplate could not be matched', {
148
+ preFilterTitle:
149
+ window.settings.preFilterTitle?.toLocaleLowerCase(),
150
+ }),
151
+ );
109
152
  }, 1000);
110
153
  setTimeout(() => {
111
154
  setNameplateNotificationText('');
@@ -113,7 +156,7 @@ function CustomCamera(props: Props) {
113
156
  }
114
157
  } else {
115
158
  if (!specifications?.is_nameplate) {
116
- setSpecifications({...specifications, is_nameplate: false});
159
+ setSpecifications({ ...specifications, is_nameplate: false });
117
160
  }
118
161
  setShowLoading(false);
119
162
  handleClose();
@@ -46,7 +46,7 @@ function DragDropFile(props: Props) {
46
46
  file: file,
47
47
  settings: window.settings,
48
48
  newSearch: true,
49
- }).then(res => {});
49
+ }).then(() => {});
50
50
 
51
51
  return;
52
52
  }
@@ -61,7 +61,7 @@ function DragDropFile(props: Props) {
61
61
  newSearch: true,
62
62
  }).then(singleImageResp => {
63
63
  const specificationPrefilter =
64
- singleImageResp.image_analysis?.specification?.prefilter_value || null;
64
+ singleImageResp?.image_analysis?.specification?.prefilter_value || null;
65
65
  const hasPrefilter = preFilterList.filter((filter: any) =>
66
66
  filter.values.includes(specificationPrefilter),
67
67
  );
@@ -154,9 +154,9 @@ function DragDropFile(props: Props) {
154
154
  <div
155
155
  className={twMerge([
156
156
  'drag-n-drop-inner',
157
- 'flex flex-col items-center w-full cursor-pointer pb-4 pt-4 rounded-[12px]',
158
- 'text-[#cacad1] hover:text-primary',
159
- 'border-2 border-dashed border-transparent hover:border-[#e0e0e0]',
157
+ 'flex flex-col items-center w-full cursor-pointer py-10 rounded-[12px]',
158
+ 'text-[#767A9F] hover:text-primary',
159
+ 'border-2 border-dashed border-[#BBBDCF]',
160
160
  isDragging && 'text-primary border-[#e0e0e0]',
161
161
  ])}
162
162
  >
@@ -51,7 +51,7 @@ function ExperienceVisualSearch({
51
51
  Start your visual search by selecting an image below.
52
52
  </div>
53
53
  <div className="custom-modal-body-content experience-visual-search-images">
54
- {new Array(4).fill(1).map((val, index) => {
54
+ {new Array(4).fill(1).map((_val, index) => {
55
55
  let itemImage: any;
56
56
 
57
57
  if (index <= experienceVisualSearchBlobs.length - 1) {
@@ -9,18 +9,20 @@ import TextSearch from './TextSearch';
9
9
  import useRequestStore from 'stores/request/requestStore';
10
10
  import { useState } from 'react';
11
11
  import LogoutModal from './LogoutModal';
12
- import { Popover, PopoverContent, PopoverTrigger } from './popover';
13
12
  import useResultStore from 'stores/result/resultStore';
14
13
  import Cart from './Cart';
14
+ import useUiStore from 'stores/ui/uiStore';
15
15
 
16
16
  function Header() {
17
17
  const { theme, auth0 } = window.settings;
18
- const { isAuthenticated, user, logout } = useAuth0();
18
+ const { isAuthenticated } = useAuth0();
19
19
  let location = useLocation();
20
20
 
21
21
  const reset = useRequestStore(state => state.reset);
22
22
  const resetResultStore = useResultStore(state => state.reset);
23
23
  const setSpecifications = useRequestStore(state => state.setSpecifications);
24
+ const showSidePanel = useUiStore(state => state.showSidePanel);
25
+ const setShowSidePanel = useUiStore(state => state.setShowSidePanel);
24
26
 
25
27
  const showSearchBar = location?.pathname === '/result';
26
28
 
@@ -29,124 +31,142 @@ function Header() {
29
31
  return (
30
32
  <div
31
33
  className={twMerge([
32
- 'h-12',
33
- 'desktop:h-[58px]',
34
- 'desktop:min-h-[58px]',
35
- location?.pathname === '/result' &&
36
- 'border-solid border-b border-[#afafaf52]',
37
- 'desktop:border-solid desktop:border-b desktop:border-[#E0E0E0]',
38
- 'pr-6',
39
- 'pl-4',
34
+ 'h-[56px]',
35
+ 'desktop:h-[68px]',
36
+ 'desktop:min-h-[68px]',
37
+ 'px-2',
38
+ 'desktop:px-4',
40
39
  'w-full',
41
40
  'flex-shrink-0',
41
+ 'bg-transparent',
42
+ 'mt-2',
43
+ 'desktop:mt-4',
44
+ 'mb-4',
45
+ 'desktop:mb-2',
46
+ 'flex',
47
+ 'gap-2',
48
+ 'justify-between',
49
+ 'desktop:items-start',
42
50
  ])}
43
- style={{ background: theme?.headerColor }}
44
51
  >
52
+ {location?.pathname === '/result' && (
53
+ <div
54
+ className={twMerge(
55
+ 'hidden desktop:flex',
56
+ 'h-[56px] min-w-[56px]',
57
+ 'desktop:min-w-[68px] desktop:h-[68px]',
58
+ 'border border-solid border-[#DDDEE7] items-center justify-center cursor-pointer bg-white',
59
+ 'rounded-2xl',
60
+ 'shadow-ds-1',
61
+ )}
62
+ onClick={() => {
63
+ setShowSidePanel(!showSidePanel);
64
+ }}
65
+ >
66
+ <Icon name={showSidePanel ? 'close' : 'hamburger'} />
67
+ </div>
68
+ )}
69
+
45
70
  <div
46
- className={twMerge(['w-full', 'flex', 'items-center', 'h-full'])}
47
- style={{ display: 'flex', alignItems: 'center', height: '100%' }}
71
+ className={twMerge(
72
+ 'h-[56px]',
73
+ 'min-w-[268px] desktop:h-[68px] flex items-center justify-between',
74
+ 'border border-solid border-[#DDDEE7]',
75
+ 'bg-white',
76
+ 'rounded-2xl',
77
+ 'shadow-ds-1',
78
+ location?.pathname === '/result' && 'flex-grow',
79
+ )}
48
80
  >
49
- {/* Left: Logo */}
50
- <div
51
- className="flex items-center justify-start min-w-0"
52
- style={{ flex: '0 0 auto' }}
81
+ <NavLink
82
+ to="/"
83
+ style={{ lineHeight: 0 }}
84
+ onClick={() => {
85
+ reset();
86
+ resetResultStore();
87
+ setSpecifications(null);
88
+ }}
89
+ className="pl-6"
53
90
  >
54
- <NavLink
55
- to="/"
56
- style={{ lineHeight: 0 }}
57
- onClick={() => {
58
- reset();
59
- resetResultStore();
60
- setSpecifications(null);
91
+ <img
92
+ src={theme?.appBarLogoUrl}
93
+ alt="logo"
94
+ style={{
95
+ aspectRatio: 1,
96
+ width: theme?.logoWidth,
97
+ height: theme?.logoHeight,
61
98
  }}
62
- >
63
- <img
64
- src={theme?.appBarLogoUrl}
65
- alt="logo"
66
- style={{
67
- aspectRatio: 1,
68
- width: theme?.logoWidth,
69
- height: theme?.logoHeight,
70
- }}
71
- />
72
- </NavLink>
73
- </div>
74
- {/* Center: Search bar */}
75
- <div className="flex-1 flex justify-center items-center min-w-0">
76
- <div
77
- className={twMerge(['hidden', showSearchBar && 'desktop:block'])}
78
- >
79
- <TextSearch />
80
- </div>
81
- </div>
82
- {/* Right: Cart and user actions */}
99
+ />
100
+ </NavLink>
101
+
83
102
  <div
84
- className="flex items-center justify-end min-w-0 gap-4"
85
- style={{ flex: '0 0 auto' }}
103
+ className={twMerge([
104
+ 'hidden',
105
+ showSearchBar && 'desktop:flex',
106
+ 'flex-1',
107
+ 'justify-center',
108
+ 'min-w-0',
109
+ ])}
86
110
  >
87
- {window.settings.cart && <Cart />}
111
+ <TextSearch className="w-full max-w-[531px] min-w-0" aiMode={true} />
112
+ </div>
88
113
 
89
- {auth0.enabled && isAuthenticated && (
114
+ {auth0.enabled &&
115
+ isAuthenticated &&
116
+ location?.pathname === '/result' && (
90
117
  <>
91
118
  <div
92
- className="hidden desktop:block"
93
- style={{ position: 'relative' }}
94
- >
95
- <Popover>
96
- <PopoverTrigger>
97
- <div
98
- style={{
99
- display: 'flex',
100
- columnGap: '16px',
101
- alignItems: 'center',
102
- cursor: 'pointer',
103
- }}
104
- >
105
- <p style={{ color: '#2B2C46' }}>{user?.email}</p>
106
- <Icon name="avatar" />
107
- </div>
108
- </PopoverTrigger>
109
- <PopoverContent className="w-[152px] bg-white p-2 shadow-outer">
110
- <div
111
- className={twMerge([
112
- 'flex',
113
- 'w-[75px]',
114
- 'h-[24px]',
115
- 'px-2',
116
- 'items-center',
117
- 'bg-[#2B2C46]',
118
- 'text-white',
119
- 'text-[10px]',
120
- 'cursor-pointer',
121
- ])}
122
- onClick={() => {
123
- logout({
124
- logoutParams: { returnTo: window.location.origin },
125
- });
126
- }}
127
- >
128
- Sign out
129
- </div>
130
- </PopoverContent>
131
- </Popover>
132
- </div>
133
- <div
134
- className="block desktop:hidden cursor-pointer"
119
+ className={twMerge(
120
+ 'cursor-pointer w-10 h-10 rounded-full bg-[#FAFAFA] flex items-center justify-center',
121
+ 'mr-3.5',
122
+ )}
135
123
  onClick={() => {
136
124
  setShowLogoutModal(true);
137
125
  }}
138
126
  >
139
127
  <Icon
140
128
  name="logout"
141
- className="text-[#AAABB5]"
142
- width={24}
143
- height={24}
129
+ className="text-black"
130
+ width={16}
131
+ height={16}
144
132
  />
145
133
  </div>
146
134
  </>
147
135
  )}
148
- </div>
149
136
  </div>
137
+ <div className="flex gap-2">
138
+ {auth0.enabled &&
139
+ isAuthenticated &&
140
+ location?.pathname !== '/result' && (
141
+ <>
142
+ <div
143
+ className={twMerge(
144
+ 'h-[56px] min-w-[56px]',
145
+ 'desktop:min-w-[68px] desktop:h-[68px] bg-white rounded-2xl flex items-center justify-center ',
146
+ 'border border-solid border-[#DDDEE7]',
147
+ 'shadow-ds-1',
148
+ )}
149
+ >
150
+ <button
151
+ className={twMerge(
152
+ 'relative flex items-center justify-center rounded-2xl',
153
+ 'h-10 w-10',
154
+ )}
155
+ style={{
156
+ backgroundColor: '#FAFAFA',
157
+ }}
158
+ onClick={() => {
159
+ setShowLogoutModal(true);
160
+ }}
161
+ >
162
+ <Icon name="logout" className={twMerge('text-[#BBBDCF]')} />
163
+ </button>
164
+ </div>
165
+ </>
166
+ )}
167
+ {window.settings.cart && <Cart />}
168
+ </div>
169
+
150
170
  <LogoutModal
151
171
  setShowLogoutModal={setShowLogoutModal}
152
172
  showModal={showLogoutModal}
@@ -1,4 +1,3 @@
1
- import React from 'react';
2
1
  import { Icon } from '@nyris/nyris-react-components';
3
2
  import { useTranslation } from 'react-i18next';
4
3
 
@@ -95,4 +94,4 @@ const Hint = () => {
95
94
  );
96
95
  };
97
96
 
98
- export default Hint;
97
+ export default Hint;
@@ -1,6 +1,7 @@
1
1
  import { useTranslation } from 'react-i18next';
2
2
  import { useHitsPerPage, UseHitsPerPageProps } from 'react-instantsearch';
3
3
  import PoweredBy from './PoweredBy';
4
+ import { twMerge } from 'tailwind-merge';
4
5
 
5
6
  export const HitsPerPage = (props: UseHitsPerPageProps) => {
6
7
  const { items, refine } = useHitsPerPage(props);
@@ -11,9 +12,14 @@ export const HitsPerPage = (props: UseHitsPerPageProps) => {
11
12
 
12
13
  return (
13
14
  <>
14
- <div className="w-full px-4 h-10 bg-white border border-solid border-[#DDDEE7] items-center hidden desktop:inline-flex justify-between">
15
- <div className="self-stretch pr-6 border-r border-solid border-[#DDDEE7] justify-center items-center gap-1 flex">
16
- <div className="text-[#2b2c46] text-[13px] font-normal font-['Source Sans 3'] leading-none tracking-tight">
15
+ <div
16
+ className={twMerge(
17
+ 'w-full min-h-10 bg-[#FFFFFF] border border-solid border-[#DDDEE7] items-center hidden desktop:inline-flex rounded-3xl justify-between pr-4',
18
+ 'shadow-ds-1',
19
+ )}
20
+ >
21
+ <div className="self-stretch pl-4 pr-6 border-r border-solid border-[#DDDEE7] justify-center items-center gap-1 flex">
22
+ <div className="text-[#3B3E5F] text-[13px] font-normal font-['Source Sans 3'] leading-none tracking-tight">
17
23
  {t('Items per page')}:
18
24
  </div>
19
25
  <select
@@ -0,0 +1,159 @@
1
+ import { useRefinementList } from 'react-instantsearch';
2
+ import Tooltip from './Tooltip/TooltipComponent';
3
+ import { twMerge } from 'tailwind-merge';
4
+ import useRequestStore from '../stores/request/requestStore';
5
+ import useResultStore from '../stores/result/resultStore';
6
+ import { Icon } from '@nyris/nyris-react-components';
7
+ import { useMediaQuery } from 'react-responsive';
8
+
9
+ const IdentifiedAttributes = ({
10
+ attr,
11
+ value,
12
+ specificationFilter,
13
+ imageAnalysis,
14
+ }: any) => {
15
+ const refinement = window.settings.refinements.filter((ref: any) => {
16
+ return ref.header?.toLocaleLowerCase() === attr?.toLocaleLowerCase();
17
+ });
18
+ const attribute = refinement?.length ? refinement[0].attribute : 'none';
19
+ const isMobile = useMediaQuery({ query: '(max-width: 776px)' });
20
+ const { refine } = useRefinementList({
21
+ attribute,
22
+ operator: 'or',
23
+ showMore: false,
24
+ limit: 1,
25
+ });
26
+
27
+ return isMobile ? (
28
+ <>
29
+ <div className="inline-flex justify-center items-start gap-2 " key={attr}>
30
+ <div className="pl-1 inline-flex justify-center items-center gap-2.5">
31
+ <div className="justify-start text-[#3B3E5F] text-sm font-semibold">
32
+ {attr}
33
+ </div>
34
+ </div>
35
+ <div
36
+ className={twMerge(
37
+ `p-1 bg-[#e4e3ff] rounded-lg inline-flex justify-center items-center gap-1.5`,
38
+ 'text-[#3e36dc]',
39
+ specificationFilter[attr]
40
+ ? 'border-[#3E36DC] bg-[#3E36DC] text-white'
41
+ : '',
42
+ )}
43
+ onClick={() => {
44
+ if (!value) {
45
+ return;
46
+ }
47
+ if (attribute !== 'none') {
48
+ refine(value);
49
+ } else {
50
+ const setSpecificationFilter =
51
+ useRequestStore.getState().setSpecificationFilter;
52
+
53
+ const setSpecificationFilteredProducts =
54
+ useResultStore.getState().setSpecificationFilteredProducts;
55
+
56
+ if (specificationFilter[attr]) {
57
+ setSpecificationFilter({});
58
+ setSpecificationFilteredProducts([]);
59
+ // setProducts(results);
60
+ } else {
61
+ setSpecificationFilter({
62
+ [attr]: value,
63
+ });
64
+ }
65
+ }
66
+ }}
67
+ >
68
+ <div className="justify-start items-center text-sm font-medium leading-none flex gap-2">
69
+ {imageAnalysis?.specification[attr] || 'N/A'}
70
+ <Icon
71
+ name="close"
72
+ className={twMerge(
73
+ 'w-3 h-3 text-white',
74
+ specificationFilter[attr] ? 'block' : 'hidden',
75
+ )}
76
+ />
77
+ </div>
78
+ </div>
79
+ </div>
80
+ </>
81
+ ) : (
82
+ <div key={attr} className="flex justify-between w-full gap-2 items-center">
83
+ <div className="self-stretch inline-flex justify-start items-center gap-1.5">
84
+ <div className="justify-start text-[#3B3E5F] text-xs font-semibold">
85
+ {attr}:
86
+ </div>
87
+ <Tooltip
88
+ content={
89
+ specificationFilter[attr]
90
+ ? 'Filter applied. Clear to choose a different value.'
91
+ : 'Click to apply as a search filter.'
92
+ }
93
+ delayDuration={1000}
94
+ disabled={!value}
95
+ >
96
+ <div
97
+ className={twMerge(
98
+ `px-1 py-1 bg-[#e4e3ff] rounded-[1px] flex justify-center items-center gap-1.5`,
99
+ 'border border-solid border-transparent hover:border-[#3E36DC]',
100
+ 'cursor-pointer',
101
+ specificationFilter[attr] ? 'border-[#3E36DC] bg-[#3E36DC] ' : '',
102
+ )}
103
+ onClick={() => {
104
+ if (!value) {
105
+ return;
106
+ }
107
+ if (attribute !== 'none') {
108
+ refine(value);
109
+ } else {
110
+ const setSpecificationFilter =
111
+ useRequestStore.getState().setSpecificationFilter;
112
+
113
+ const setSpecificationFilteredProducts =
114
+ useResultStore.getState().setSpecificationFilteredProducts;
115
+
116
+ if (specificationFilter[attr]) {
117
+ setSpecificationFilter({});
118
+ setSpecificationFilteredProducts([]);
119
+ // setProducts(results);
120
+ } else {
121
+ setSpecificationFilter({
122
+ [attr]: value,
123
+ });
124
+ }
125
+ }
126
+ }}
127
+ >
128
+ <div
129
+ className={twMerge(
130
+ 'justify-start text-[#3e36dc] text-[10px] leading-none px-0.5',
131
+ 'font-normal hover:font-bold hover:px-0',
132
+ specificationFilter[attr]
133
+ ? 'font-bold text-white hover:px-0.5'
134
+ : '',
135
+ 'max-line-1',
136
+ )}
137
+ >
138
+ {imageAnalysis?.specification[attr] || 'N/A'}
139
+ </div>
140
+ </div>
141
+ </Tooltip>
142
+ </div>
143
+ <div
144
+ onClick={() => {
145
+ navigator.clipboard.writeText(
146
+ imageAnalysis?.specification[attr] || '',
147
+ );
148
+ }}
149
+ >
150
+ <Icon
151
+ name="copy"
152
+ className="text-[#AAABB5] w-[12px] h-[12px] hover:text-[#3E36DC] cursor-pointer"
153
+ />
154
+ </div>
155
+ </div>
156
+ );
157
+ };
158
+
159
+ export default IdentifiedAttributes;