@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.
- package/build/asset-manifest.json +6 -6
- package/build/index.html +1 -1
- package/build/js/settings.example.js +6 -1
- package/build/static/css/main.7c7f5447.css +4 -0
- package/build/static/css/main.7c7f5447.css.map +1 -0
- package/build/static/js/main.9e7fb38a.js +3 -0
- package/build/static/js/{main.b80e9533.js.map → main.9e7fb38a.js.map} +1 -1
- package/package.json +3 -3
- package/public/js/settings.example.js +6 -1
- package/src/components/Cart.tsx +200 -85
- package/src/components/Header.tsx +1 -1
- package/src/components/Inquiry/InquiryModal.tsx +2 -2
- package/src/components/PostFilter/PostFilter.tsx +36 -3
- package/src/components/PostFilter/PostFilterFindApi.tsx +157 -65
- package/src/components/PreFilter/PreFilter.tsx +39 -5
- package/src/components/Product/Product.tsx +2 -2
- package/src/components/Product/ProductDetailView.tsx +2 -2
- package/src/components/Product/ProductList.tsx +4 -0
- package/src/components/ScopeMissing.tsx +50 -0
- package/src/components/SmartFilter.tsx +59 -69
- package/src/components/TextSearch.tsx +3 -1
- package/src/hooks/useEffectiveGroundingResults.ts +6 -1
- package/src/hooks/useImageSearch.ts +49 -6
- package/src/hooks/useResultEvaluator.ts +8 -8
- package/src/layouts/AppLayout.tsx +27 -6
- package/src/pages/Result.tsx +2 -1
- package/src/routes/ProctedRoute.tsx +52 -9
- package/src/services/filter.ts +13 -0
- package/src/services/visualSearch.ts +10 -1
- package/src/stores/result/prodcuts/products.initialState.ts +2 -0
- package/src/stores/result/prodcuts/products.slice.ts +8 -0
- package/src/stores/scope/scopeStore.ts +35 -0
- package/src/stores/types.ts +9 -0
- package/src/styles/Cart.scss +22 -0
- package/src/types.ts +6 -1
- package/build/static/css/main.5fff4ca0.css +0 -4
- package/build/static/css/main.5fff4ca0.css.map +0 -1
- package/build/static/js/main.b80e9533.js +0 -3
- /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
|
|
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
|
|
30
|
-
|
|
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
|
|
44
|
-
|
|
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
|
|
49
|
-
|
|
59
|
+
const scopeFilterEnabled = settings.scopeFilter;
|
|
60
|
+
const scopeFilterAttributes = useScopeStore(state => state.filterAttributes);
|
|
50
61
|
|
|
51
|
-
|
|
52
|
-
|
|
62
|
+
const scopedAllowedValues = useMemo(() => {
|
|
63
|
+
if (!scopeFilterEnabled || !window.settings.auth0?.enabled) {
|
|
64
|
+
return null;
|
|
53
65
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
|
|
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
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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,
|
|
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
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
|
-
}, [
|
|
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
|
-
}, [
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
23
|
-
|
|
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 (
|
|
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(
|
|
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
|
-
|
|
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' &&
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
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={
|
|
292
|
+
style={
|
|
293
|
+
variant === 'results' ? { width: 'max-content' } : { width: 'auto' }
|
|
294
|
+
}
|
|
302
295
|
>
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
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>
|
|
@@ -26,7 +26,12 @@ export function useEffectiveGroundingResults() {
|
|
|
26
26
|
const postEntries = Object.entries(postFilterSelections || {}).filter(
|
|
27
27
|
([, values]) => values?.length,
|
|
28
28
|
);
|
|
29
|
-
if (
|
|
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]) => {
|