@nyris/nyris-webapp 0.3.95 → 0.3.97

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 (39) hide show
  1. package/build/asset-manifest.json +6 -6
  2. package/build/index.html +1 -1
  3. package/build/js/settings.example.js +6 -1
  4. package/build/static/css/main.7c7f5447.css +4 -0
  5. package/build/static/css/main.7c7f5447.css.map +1 -0
  6. package/build/static/js/main.9e7fb38a.js +3 -0
  7. package/build/static/js/{main.b80e9533.js.map → main.9e7fb38a.js.map} +1 -1
  8. package/package.json +3 -3
  9. package/public/js/settings.example.js +6 -1
  10. package/src/components/Cart.tsx +200 -85
  11. package/src/components/Header.tsx +1 -1
  12. package/src/components/Inquiry/InquiryModal.tsx +2 -2
  13. package/src/components/PostFilter/PostFilter.tsx +36 -3
  14. package/src/components/PostFilter/PostFilterFindApi.tsx +157 -65
  15. package/src/components/PreFilter/PreFilter.tsx +39 -5
  16. package/src/components/Product/Product.tsx +2 -2
  17. package/src/components/Product/ProductDetailView.tsx +2 -2
  18. package/src/components/Product/ProductList.tsx +4 -0
  19. package/src/components/ScopeMissing.tsx +50 -0
  20. package/src/components/SmartFilter.tsx +59 -69
  21. package/src/components/TextSearch.tsx +3 -1
  22. package/src/hooks/useEffectiveGroundingResults.ts +6 -1
  23. package/src/hooks/useImageSearch.ts +49 -6
  24. package/src/hooks/useResultEvaluator.ts +8 -8
  25. package/src/layouts/AppLayout.tsx +27 -6
  26. package/src/pages/Result.tsx +2 -1
  27. package/src/routes/ProctedRoute.tsx +52 -9
  28. package/src/services/filter.ts +13 -0
  29. package/src/services/visualSearch.ts +10 -1
  30. package/src/stores/result/prodcuts/products.initialState.ts +2 -0
  31. package/src/stores/result/prodcuts/products.slice.ts +8 -0
  32. package/src/stores/scope/scopeStore.ts +35 -0
  33. package/src/stores/types.ts +9 -0
  34. package/src/styles/Cart.scss +22 -0
  35. package/src/types.ts +6 -1
  36. package/build/static/css/main.5fff4ca0.css +0 -4
  37. package/build/static/css/main.5fff4ca0.css.map +0 -1
  38. package/build/static/js/main.b80e9533.js +0 -3
  39. /package/build/static/js/{main.b80e9533.js.LICENSE.txt → main.9e7fb38a.js.LICENSE.txt} +0 -0
@@ -6,9 +6,19 @@ import {
6
6
  } from 'components/Accordion';
7
7
  import { Icon } from '@nyris/nyris-react-components';
8
8
  import { useTranslation } from 'react-i18next';
9
+ import { isEmpty } from 'lodash';
9
10
  import useResultStore from 'stores/result/resultStore';
10
11
  import useRequestStore from 'stores/request/requestStore';
11
- import { filterProductsByText } from 'utils/textSearchFilter';
12
+ import useUiStore from 'stores/ui/uiStore';
13
+ import useScopeStore from 'stores/scope/scopeStore';
14
+ import { find } from 'services/visualSearch';
15
+ import { useAuth0 } from '@auth0/auth0-react';
16
+
17
+ let postFilterRequestCounter = 0;
18
+
19
+ export function invalidatePostFilterRequests() {
20
+ postFilterRequestCounter += 1;
21
+ }
12
22
 
13
23
  function PostFilterFindApi({
14
24
  attribute,
@@ -23,87 +33,75 @@ function PostFilterFindApi({
23
33
  onVisibilityChange?: (attribute: string, isVisible: boolean) => void;
24
34
  isLastVisible?: boolean;
25
35
  }) {
36
+ const { user } = useAuth0();
37
+
26
38
  const [itemsLimit, setItemsLimit] = useState(10);
27
39
  const [searchInput, setSearchInput] = useState<string>('');
28
40
 
29
- const productsFromFindApi = useResultStore(
30
- state => state.productsFromFindApi,
41
+ const postFilterFacets = useResultStore(state => state.postFilterFacets);
42
+ const setFindApiProducts = useResultStore(state => state.setFindApiProducts);
43
+ const setPostFilterSelections = useRequestStore(
44
+ state => state.setPostFilterSelections,
31
45
  );
32
- const groundingFilterResult = useResultStore(
33
- state => state.groundingFilterResult,
34
- );
35
- const showingGroundingFilterResult = useResultStore(
36
- state => state.showingGroundingFilterResult,
37
- );
38
- const query = useRequestStore(state => state.query);
39
- const requestImages = useRequestStore(state => state.requestImages);
40
46
  const postFilterSelections = useRequestStore(
41
47
  state => state.postFilterSelections,
42
48
  );
43
- const togglePostFilterSelection = useRequestStore(
44
- state => state.togglePostFilterSelection,
45
- );
49
+ const query = useRequestStore(state => state.query);
50
+ const requestImages = useRequestStore(state => state.requestImages);
51
+ const regions = useRequestStore(state => state.regions);
52
+ const preFilter = useRequestStore(state => state.preFilter);
53
+ const preFilterList = useRequestStore(state => state.preFilterList);
54
+ const metaFilter = useRequestStore(state => state.metaFilter);
55
+ const setIsFindApiLoading = useUiStore(state => state.setIsFindApiLoading);
46
56
  const { t } = useTranslation();
57
+ const settings = window.settings;
47
58
 
48
- const productsForFilters = useMemo(() => {
49
- let baseProducts = productsFromFindApi;
59
+ const scopeFilterEnabled = settings.scopeFilter;
60
+ const scopeFilterAttributes = useScopeStore(state => state.filterAttributes);
50
61
 
51
- if (requestImages?.length && query?.trim()) {
52
- baseProducts = filterProductsByText(query, productsFromFindApi);
62
+ const scopedAllowedValues = useMemo(() => {
63
+ if (!scopeFilterEnabled || !window.settings.auth0?.enabled) {
64
+ return null;
53
65
  }
54
-
55
- if (showingGroundingFilterResult && groundingFilterResult?.length > 0) {
56
- const grounded = baseProducts.filter((product: any) =>
57
- groundingFilterResult.some((result: any) => product.sku === result.sku),
58
- );
59
- if (grounded.length > 0) {
60
- return grounded;
61
- }
66
+ // todo: support multiple attributes in the future
67
+ const scopeAttribute = scopeFilterAttributes[0];
68
+ if (!scopeAttribute) {
69
+ return new Set<string>();
62
70
  }
63
-
64
- return baseProducts;
65
- }, [
66
- productsFromFindApi,
67
- query,
68
- requestImages,
69
- showingGroundingFilterResult,
70
- groundingFilterResult,
71
- ]);
72
-
73
- const filteredProductsForCounts = useMemo(() => {
74
- const selections = postFilterSelections || {};
75
- const activeSelections = Object.entries(selections).filter(
76
- ([attr, values]) => attr !== attribute && Array.isArray(values),
71
+ return new Set(
72
+ scopeAttribute.values.map(value => value.toLocaleLowerCase()),
77
73
  );
74
+ }, [scopeFilterEnabled, scopeFilterAttributes]);
78
75
 
79
- if (!activeSelections.length) {
80
- return productsForFilters;
81
- }
82
-
83
- return productsForFilters.filter((product: any) =>
84
- activeSelections.every(([attr, values]) => {
85
- const filterValues = product.filters?.[attr];
86
- if (!Array.isArray(filterValues) || filterValues.length === 0) {
87
- return false;
88
- }
89
- return values.some(value => filterValues.includes(value));
90
- }),
76
+ const matchedFacet = useMemo(() => {
77
+ const facets = Array.isArray(postFilterFacets) ? postFilterFacets : [];
78
+ return facets.find(
79
+ (entry: { name?: string }) =>
80
+ entry.name?.toLocaleLowerCase() === attribute.toLocaleLowerCase(),
91
81
  );
92
- }, [attribute, postFilterSelections, productsForFilters]);
82
+ }, [attribute, postFilterFacets]);
83
+
84
+ const preFilterApiKey =
85
+ preFilterList.find(
86
+ filter =>
87
+ filter.key?.toLocaleLowerCase() ===
88
+ settings.visualSearchFilterKey?.toLocaleLowerCase(),
89
+ )?.key ?? settings.visualSearchFilterKey;
93
90
 
94
91
  const valueCounts = useMemo(() => {
95
92
  const counts: Record<string, number> = {};
96
- filteredProductsForCounts?.forEach((product: any) => {
97
- const filterValues = product.filters?.[attribute];
98
- if (Array.isArray(filterValues)) {
99
- filterValues.forEach((val: string) => {
100
- if (val) counts[val] = (counts[val] || 0) + 1;
101
- });
102
- }
103
- });
93
+
94
+ (matchedFacet?.values || []).forEach(
95
+ (entry: { value?: string; count?: number }) => {
96
+ const value = entry?.value;
97
+ if (value) {
98
+ counts[value] = entry.count ?? 0;
99
+ }
100
+ },
101
+ );
104
102
 
105
103
  return counts;
106
- }, [attribute, filteredProductsForCounts]);
104
+ }, [matchedFacet]);
107
105
 
108
106
  const items = useMemo(() => {
109
107
  const selectedValues = postFilterSelections?.[attribute] || [];
@@ -119,6 +117,11 @@ function PostFilterFindApi({
119
117
  count: valueCounts[label] || 0,
120
118
  isRefined: selectedValues.includes(label),
121
119
  }))
120
+ .filter(
121
+ item =>
122
+ !scopedAllowedValues ||
123
+ scopedAllowedValues.has(item.value.toLocaleLowerCase()),
124
+ )
122
125
  .filter(
123
126
  item =>
124
127
  !searchInput ||
@@ -126,18 +129,107 @@ function PostFilterFindApi({
126
129
  )
127
130
  .sort((a, b) => b.count - a.count || a.label.localeCompare(b.label))
128
131
  .slice(0, itemsLimit);
129
- }, [attribute, itemsLimit, postFilterSelections, searchInput, valueCounts]);
132
+ }, [
133
+ attribute,
134
+ itemsLimit,
135
+ postFilterSelections,
136
+ scopedAllowedValues,
137
+ searchInput,
138
+ valueCounts,
139
+ ]);
130
140
 
131
141
  const selectedValues = postFilterSelections?.[attribute] || [];
132
142
  const hasSelection = selectedValues.length > 0;
133
143
  const hasAvailableValues = Object.keys(valueCounts).length > 0;
134
144
  const hasSearch = searchable && searchInput.length > 0;
135
- const isVisible = hasAvailableValues || hasSelection || hasSearch;
145
+ const isVisible =
146
+ (!scopedAllowedValues || scopedAllowedValues.size > 0) &&
147
+ (hasAvailableValues || hasSelection || hasSearch);
136
148
 
137
149
  useEffect(() => {
138
150
  onVisibilityChange?.(attribute, isVisible);
139
151
  }, [attribute, isVisible, onVisibilityChange]);
140
152
 
153
+ const handleFilterClick = (value: string, isRefined: boolean) => {
154
+ const currentSelections =
155
+ useRequestStore.getState().postFilterSelections || {};
156
+ const currentAttributeValues = currentSelections[attribute] || [];
157
+ const nextAttributeValues = isRefined
158
+ ? currentAttributeValues.filter(item => item !== value)
159
+ : [...currentAttributeValues, value];
160
+
161
+ const nextSelections = { ...currentSelections };
162
+ if (nextAttributeValues.length > 0) {
163
+ nextSelections[attribute] = nextAttributeValues;
164
+ } else {
165
+ delete nextSelections[attribute];
166
+ }
167
+
168
+ setPostFilterSelections(nextSelections);
169
+
170
+ const hasAnySelection = Object.values(nextSelections).some(
171
+ values => values.length > 0,
172
+ );
173
+
174
+ if (!hasAnySelection) {
175
+ const snapshot = useResultStore.getState().initialFindSearchSnapshot;
176
+ if (snapshot) {
177
+ setFindApiProducts(snapshot.results);
178
+ }
179
+ return;
180
+ }
181
+
182
+ const facets = useResultStore.getState().postFilterFacets || [];
183
+ const filters = [];
184
+
185
+ if (!isEmpty(preFilter)) {
186
+ filters.push({
187
+ key: preFilterApiKey,
188
+ values: Object.keys(preFilter).filter(key => preFilter[key]),
189
+ });
190
+ } else if (metaFilter) {
191
+ filters.push({
192
+ key: preFilterApiKey,
193
+ values: [metaFilter],
194
+ });
195
+ }
196
+
197
+ Object.entries(nextSelections).forEach(([attr, values]) => {
198
+ if (values.length > 0) {
199
+ const facet = facets.find(
200
+ (entry: { name?: string }) =>
201
+ entry.name?.toLocaleLowerCase() === attr.toLocaleLowerCase(),
202
+ );
203
+ filters.push({
204
+ key: facet?.name ?? attr,
205
+ values,
206
+ });
207
+ }
208
+ });
209
+
210
+ const requestId = ++postFilterRequestCounter;
211
+ setIsFindApiLoading(true);
212
+ void find({
213
+ image: requestImages[0],
214
+ settings,
215
+ region: regions[0],
216
+ filters,
217
+ text: !requestImages[0] ? query : undefined,
218
+ scopeIds: window.settings?.scopeFilter ? user?.email : undefined,
219
+ })
220
+ .then(response => {
221
+ if (requestId !== postFilterRequestCounter) {
222
+ return;
223
+ }
224
+ setFindApiProducts(response?.results || []);
225
+ })
226
+ .finally(() => {
227
+ if (requestId === postFilterRequestCounter) {
228
+ setIsFindApiLoading(false);
229
+ }
230
+ });
231
+ };
232
+
141
233
  if (!isVisible) {
142
234
  return null;
143
235
  }
@@ -209,7 +301,7 @@ function PostFilterFindApi({
209
301
  type="checkbox"
210
302
  checked={item.isRefined}
211
303
  onChange={() =>
212
- togglePostFilterSelection(attribute, item.value)
304
+ handleFilterClick(item.value, item.isRefined)
213
305
  }
214
306
  className="cursor-pointer"
215
307
  />
@@ -10,6 +10,7 @@ import { truncateString } from 'utils/truncateString';
10
10
  import { twMerge } from 'tailwind-merge';
11
11
  import Tooltip from 'components/Tooltip/TooltipComponent';
12
12
  import useResultStore from '../../stores/result/resultStore';
13
+ import useScopeStore from 'stores/scope/scopeStore';
13
14
 
14
15
  interface Props {
15
16
  handleClose?: any;
@@ -42,6 +43,9 @@ const PreFilterComponent = (props: Props) => {
42
43
  const setImageAnalysis = useResultStore(state => state.setImageAnalysis);
43
44
  const regions = useRequestStore(state => state.regions);
44
45
 
46
+ const scopeFilterEnabled = !!settings.scopeFilter;
47
+ const scopeFilterAttributes = useScopeStore(state => state.filterAttributes);
48
+
45
49
  const [keyFilter, setKeyFilter] = useState<Record<string, boolean>>(
46
50
  keyFilterState || {},
47
51
  );
@@ -66,7 +70,7 @@ const PreFilterComponent = (props: Props) => {
66
70
  setLoading(true);
67
71
  getDataFilterDesktop();
68
72
  // eslint-disable-next-line react-hooks/exhaustive-deps
69
- }, [preFilterList, searchKey]);
73
+ }, [preFilterList, scopeFilterAttributes, searchKey]);
70
74
 
71
75
  useEffect(() => {
72
76
  return () => {
@@ -76,10 +80,20 @@ const PreFilterComponent = (props: Props) => {
76
80
  };
77
81
  }, []);
78
82
 
83
+ const activeFilterList = scopeFilterEnabled
84
+ ? scopeFilterAttributes
85
+ : preFilterList;
86
+
87
+ const matchedPreFilter = activeFilterList.find(
88
+ value =>
89
+ value.key?.toLocaleLowerCase() ===
90
+ settings.visualSearchFilterKey?.toLocaleLowerCase(),
91
+ );
92
+ const preFilterApiKey =
93
+ matchedPreFilter?.key ?? settings.visualSearchFilterKey;
94
+
79
95
  const getDataFilterDesktop = () => {
80
- const arrResult =
81
- preFilterList.find(value => value.key === settings.visualSearchFilterKey)
82
- ?.values || [];
96
+ const arrResult = matchedPreFilter?.values || [];
83
97
 
84
98
  const newResult = arrResult.sort().reduce((a: any, c: any) => {
85
99
  if (!c[0]) return a;
@@ -99,8 +113,28 @@ const PreFilterComponent = (props: Props) => {
99
113
  return;
100
114
  }
101
115
  setLoading(true);
116
+
117
+ if (scopeFilterEnabled) {
118
+ const arrResult = matchedPreFilter?.values || [];
119
+ const lowerValue = String(value).toLocaleLowerCase();
120
+ const res = arrResult.filter(item =>
121
+ String(item).toLocaleLowerCase().includes(lowerValue),
122
+ );
123
+ if (res.length > 0) {
124
+ setResultFilter({ [res[0][0].toLocaleUpperCase()]: res });
125
+ if (res.length <= 20) setColumns(1);
126
+ else if (res.length <= 40) setColumns(2);
127
+ else setColumns(4);
128
+ } else {
129
+ setResultFilter({});
130
+ setColumns(4);
131
+ }
132
+ setLoading(false);
133
+ return;
134
+ }
135
+
102
136
  const data = await searchFilters(
103
- settings.visualSearchFilterKey,
137
+ preFilterApiKey,
104
138
  encodeURIComponent(value),
105
139
  settings,
106
140
  )
@@ -673,7 +673,7 @@ function Product(props: Props) {
673
673
  </div>
674
674
  )}
675
675
 
676
- {settings.cart && (
676
+ {settings.cart?.enabled && (
677
677
  <div
678
678
  style={{
679
679
  boxShadow: '-2px 2px 4px rgba(170, 171, 181, 0.5)',
@@ -717,7 +717,7 @@ function Product(props: Props) {
717
717
  paddingRight: '8px',
718
718
  }}
719
719
  >
720
- Add to cart
720
+ {settings.cart?.buttonText || 'Add to cart'}
721
721
  </div>
722
722
  <div style={{ width: '16px' }}>
723
723
  <Icon
@@ -456,7 +456,7 @@ function ProductDetailView(props: Props) {
456
456
  </div>
457
457
  )}
458
458
  </div>
459
- {settings.cart && (
459
+ {settings.cart?.enabled && (
460
460
  <div
461
461
  className="bg-[#2B2C46] rounded mt-2 flex justify-between items-center h-8 desktop:h-12 w-full btn-detail-item"
462
462
  style={{
@@ -472,7 +472,7 @@ function ProductDetailView(props: Props) {
472
472
  }}
473
473
  >
474
474
  <div className="text-f16 fw-600 max-line-1 text-white pr-1.5">
475
- Add to cart
475
+ {settings.cart?.buttonText || 'Add to cart'}
476
476
  </div>
477
477
  <div style={{ width: '16px' }}>
478
478
  <Icon
@@ -63,6 +63,10 @@ function ProductList({ sendFeedBackAction }: Props): JSX.Element {
63
63
  }, [productsFromFindApi, query, requestImages]);
64
64
 
65
65
  const postFilteredProducts = useMemo(() => {
66
+ if (!window.settings.algolia?.enabled && window.settings.postFilterOption) {
67
+ return textFilteredProducts;
68
+ }
69
+
66
70
  const entries = Object.entries(postFilterSelections || {}).filter(
67
71
  ([, values]) => values?.length,
68
72
  );
@@ -0,0 +1,50 @@
1
+ const ScopeMissing = () => {
2
+ const { supportEmail } = window.settings.auth0;
3
+
4
+ return (
5
+ <div className="pl-8 desktop:pl-16 pt-6 desktop:pt-8 flex flex-col gap-4 bg-white h-full">
6
+ <div
7
+ style={{
8
+ fontSize: '20px',
9
+ fontWeight: 'bold',
10
+ color: '#2B2C46',
11
+ }}
12
+ >
13
+ Access Denied
14
+ </div>
15
+
16
+ <div
17
+ style={{
18
+ width: '200px',
19
+ height: '4px',
20
+ borderRadius: '4px',
21
+ }}
22
+ className="bg-red-500"
23
+ />
24
+
25
+ <p style={{ color: '#2B2C46', fontSize: '13px' }}>
26
+ You do not have access to the {window.settings.visualSearchFilterKey}.
27
+ If you believe this is an error, please contact support.
28
+ </p>
29
+ <a
30
+ className="contact-support"
31
+ style={{
32
+ backgroundColor: '#2B2C46',
33
+ padding: '8px 16px 8px 16px',
34
+ color: '#fff',
35
+ border: 'none',
36
+ cursor: 'pointer',
37
+ fontSize: '14px',
38
+ width: 'fit-content',
39
+ }}
40
+ href={`mailto:${
41
+ supportEmail || 'support@nyris.io'
42
+ }?subject=Access Denied&body=`}
43
+ >
44
+ Contact Support
45
+ </a>
46
+ </div>
47
+ );
48
+ };
49
+
50
+ export default ScopeMissing;
@@ -1,5 +1,4 @@
1
1
  import React, { useMemo } from 'react';
2
- import ItemSpecification from './ItemSpecification';
3
2
  import { Icon } from '@nyris/nyris-react-components';
4
3
  import useRequestStore from 'stores/request/requestStore';
5
4
  import useResultStore from 'stores/result/resultStore';
@@ -19,8 +18,12 @@ function SmartFilter({
19
18
  const isSmartFiltersEnabled = !!settings.useSmartFilters;
20
19
  const smartFilters = useResultStore(state => state.smartFilters);
21
20
  const setSmartFilters = useResultStore(state => state.setSmartFilters);
22
- const productsFromFindApi = useResultStore(state => state.productsFromFindApi);
23
- const productsFromAlgolia = useResultStore(state => state.productsFromAlgolia);
21
+ const productsFromFindApi = useResultStore(
22
+ state => state.productsFromFindApi,
23
+ );
24
+ const productsFromAlgolia = useResultStore(
25
+ state => state.productsFromAlgolia,
26
+ );
24
27
  const firstSearchResults = useResultStore(state => state.firstSearchResults);
25
28
  const setFindApiProducts = useResultStore(state => state.setFindApiProducts);
26
29
  const setAlgoliaProducts = useResultStore(state => state.setAlgoliaProducts);
@@ -194,7 +197,10 @@ function SmartFilter({
194
197
  return;
195
198
  }
196
199
 
197
- if (rerankedSkus.length === 0 || !applySkuOrderingToCurrentResults(rerankedSkus)) {
200
+ if (
201
+ rerankedSkus.length === 0 ||
202
+ !applySkuOrderingToCurrentResults(rerankedSkus)
203
+ ) {
198
204
  setSmartFilters({
199
205
  ...smartFilters,
200
206
  selectedFilterKey: filter.key,
@@ -214,7 +220,9 @@ function SmartFilter({
214
220
  errorMessage: null,
215
221
  });
216
222
  } catch (error: any) {
217
- const timeout = String(error?.message || '').includes('SMART_FILTER_TIMEOUT');
223
+ const timeout = String(error?.message || '').includes(
224
+ 'SMART_FILTER_TIMEOUT',
225
+ );
218
226
  setSmartFilters({
219
227
  ...smartFilters,
220
228
  selectedFilterKey: filter.key,
@@ -241,25 +249,7 @@ function SmartFilter({
241
249
  </div>
242
250
  <Icon name="smart_filter_indication" className="mt-1" />
243
251
  </div>
244
- <div className={`flex flex-wrap mx-4 gap-2 ${imageAnalysis?.specification ? 'mt-4' : ''}`}>
245
- {Object.keys(imageAnalysis?.specification || {})
246
- .filter(key => key !== 'is_nameplate' && key !== 'prefilter_value')
247
- .map(key => {
248
- const value = imageAnalysis?.specification[key];
249
- if (!value) {
250
- return null;
251
- }
252
- return (
253
- <ItemSpecification
254
- key={key}
255
- attr={key}
256
- value={value}
257
- specificationFilter={specificationFilter}
258
- imageAnalysis={imageAnalysis}
259
- />
260
- );
261
- })}
262
- </div>
252
+
263
253
  {smartFilters.status === 'loading' && (
264
254
  <div className="ml-[30px] mt-3 text-xs text-[#3E36DC]">
265
255
  Loading smart filters...
@@ -270,11 +260,12 @@ function SmartFilter({
270
260
  {smartFilters.errorMessage || 'Could not generate smart filters.'}
271
261
  </div>
272
262
  )}
273
- {smartFilters.status === 'success' && smartFilters.filters.length === 0 && (
274
- <div className="ml-[30px] mt-3 text-xs text-[#767A9F]">
275
- No smart filters available for this image.
276
- </div>
277
- )}
263
+ {smartFilters.status === 'success' &&
264
+ smartFilters.filters.length === 0 && (
265
+ <div className="ml-[30px] mt-3 text-xs text-[#767A9F]">
266
+ No smart filters available for this image.
267
+ </div>
268
+ )}
278
269
  <div
279
270
  className={
280
271
  variant === 'results'
@@ -298,47 +289,46 @@ function SmartFilter({
298
289
  ? 'flex flex-row flex-nowrap gap-2 pb-1'
299
290
  : 'flex flex-row flex-wrap gap-2 pb-1'
300
291
  }
301
- style={variant === 'results' ? { width: 'max-content' } : { width: 'auto' }}
292
+ style={
293
+ variant === 'results' ? { width: 'max-content' } : { width: 'auto' }
294
+ }
302
295
  >
303
- {smartFilters.filters.map((filter, index) => {
304
- const isSelected =
305
- smartFilters.selectedFilterKey === filter.key &&
306
- smartFilters.selectedFilterValue === filter.value;
307
- const hasSelection =
308
- !!smartFilters.selectedFilterKey &&
309
- !!smartFilters.selectedFilterValue;
310
- const isRerankLoading = smartFilters.rerankStatus === 'loading';
311
- const isDisabled = !isSelected && (hasSelection || isRerankLoading);
312
- return (
313
- <button
314
- key={`${filter.key}-${filter.value}-${index}`}
315
- type="button"
316
- disabled={isDisabled}
317
- aria-pressed={isSelected}
318
- style={variant === 'results' ? { flex: '0 0 auto' } : undefined}
319
- className={`px-3.5 py-2 rounded-lg border text-sm inline-flex items-center gap-1.5 ${
320
- isSelected
321
- ? 'bg-[#545987] text-white border-[#545987]'
322
- : 'bg-[#E7E8F1] text-[#767A9F] border-transparent'
323
- } ${isDisabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer'}`}
324
- onClick={() => {
325
- if (isRerankLoading) return;
326
- void onFilterClick({
327
- key: filter.key,
328
- value: filter.value,
329
- });
330
- }}
331
- >
332
- <span>{filter.value}</span>
333
- {isSelected && (
334
- <Icon
335
- name="close"
336
- className="w-3 h-3 text-white"
337
- />
338
- )}
339
- </button>
340
- );
341
- })}
296
+ {smartFilters.filters.map((filter, index) => {
297
+ const isSelected =
298
+ smartFilters.selectedFilterKey === filter.key &&
299
+ smartFilters.selectedFilterValue === filter.value;
300
+ const hasSelection =
301
+ !!smartFilters.selectedFilterKey &&
302
+ !!smartFilters.selectedFilterValue;
303
+ const isRerankLoading = smartFilters.rerankStatus === 'loading';
304
+ const isDisabled = !isSelected && (hasSelection || isRerankLoading);
305
+ return (
306
+ <button
307
+ key={`${filter.key}-${filter.value}-${index}`}
308
+ type="button"
309
+ disabled={isDisabled}
310
+ aria-pressed={isSelected}
311
+ style={variant === 'results' ? { flex: '0 0 auto' } : undefined}
312
+ className={`px-3.5 py-2 rounded-lg border text-sm inline-flex items-center gap-1.5 ${
313
+ isSelected
314
+ ? 'bg-[#545987] text-white border-[#545987]'
315
+ : 'bg-[#E7E8F1] text-[#767A9F] border-transparent'
316
+ } ${isDisabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer'}`}
317
+ onClick={() => {
318
+ if (isRerankLoading) return;
319
+ void onFilterClick({
320
+ key: filter.key,
321
+ value: filter.value,
322
+ });
323
+ }}
324
+ >
325
+ <span>{filter.value}</span>
326
+ {isSelected && (
327
+ <Icon name="close" className="w-3 h-3 text-white" />
328
+ )}
329
+ </button>
330
+ );
331
+ })}
342
332
  </div>
343
333
  </div>
344
334
  </div>
@@ -204,7 +204,9 @@ function TextSearch({
204
204
  !isEmpty(preFilter)
205
205
  ? 'fill-theme-primary'
206
206
  : 'fill-[#2B2C46]',
207
- `desktop:fill-[#3B3E5F]`,
207
+ !isEmpty(preFilter)
208
+ ? 'desktop:fill-white'
209
+ : 'desktop:fill-[#2B2C46]',
208
210
  )}
209
211
  />
210
212
  </div>
@@ -26,7 +26,12 @@ export function useEffectiveGroundingResults() {
26
26
  const postEntries = Object.entries(postFilterSelections || {}).filter(
27
27
  ([, values]) => values?.length,
28
28
  );
29
- if (postEntries.length > 0) {
29
+ if (
30
+ postEntries.length > 0 &&
31
+ !(
32
+ !window.settings.algolia?.enabled && window.settings.postFilterOption
33
+ )
34
+ ) {
30
35
  filteredProducts = filteredProducts.filter((product: any) => {
31
36
  const productFilters = product?.filters || {};
32
37
  return postEntries.every(([attribute, selectedValues]) => {