@nyris/nyris-webapp 0.3.96 → 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.fd12cce3.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 +41 -3
- package/src/components/PreFilter/PreFilter.tsx +30 -2
- package/src/components/Product/Product.tsx +2 -2
- package/src/components/Product/ProductDetailView.tsx +2 -2
- package/src/components/ScopeMissing.tsx +50 -0
- package/src/components/SmartFilter.tsx +59 -69
- package/src/components/TextSearch.tsx +3 -1
- package/src/hooks/useImageSearch.ts +9 -1
- 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/scope/scopeStore.ts +35 -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.fd12cce3.js +0 -3
- /package/build/static/js/{main.fd12cce3.js.LICENSE.txt → main.9e7fb38a.js.LICENSE.txt} +0 -0
|
@@ -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>
|
|
@@ -20,8 +20,11 @@ import { useGroundedSearch } from './useGroundedSearch';
|
|
|
20
20
|
import { generateSmartFilters } from '../services/vizo';
|
|
21
21
|
import { cropImage } from 'utils/cropImageToBase64';
|
|
22
22
|
import { invalidatePostFilterRequests } from 'components/PostFilter/PostFilterFindApi';
|
|
23
|
+
import { useAuth0 } from '@auth0/auth0-react';
|
|
23
24
|
|
|
24
25
|
export const useImageSearch = () => {
|
|
26
|
+
const { user } = useAuth0();
|
|
27
|
+
|
|
25
28
|
const setRegions = useRequestStore(state => state.setRegions);
|
|
26
29
|
const query = useRequestStore(state => state.query);
|
|
27
30
|
|
|
@@ -69,7 +72,9 @@ export const useImageSearch = () => {
|
|
|
69
72
|
state => state.setGroundingFilterResult,
|
|
70
73
|
);
|
|
71
74
|
const setSmartFilters = useResultStore(state => state.setSmartFilters);
|
|
72
|
-
const setPostFilterFacets = useResultStore(
|
|
75
|
+
const setPostFilterFacets = useResultStore(
|
|
76
|
+
state => state.setPostFilterFacets,
|
|
77
|
+
);
|
|
73
78
|
const setInitialFindSearchSnapshot = useResultStore(
|
|
74
79
|
state => state.setInitialFindSearchSnapshot,
|
|
75
80
|
);
|
|
@@ -208,6 +213,7 @@ export const useImageSearch = () => {
|
|
|
208
213
|
];
|
|
209
214
|
|
|
210
215
|
let smartFilterPromise: Promise<void> | null = null;
|
|
216
|
+
|
|
211
217
|
if (isSmartFiltersEnabled && requestImage && (newSearch || imageRegion)) {
|
|
212
218
|
setSmartFilters({
|
|
213
219
|
status: 'loading',
|
|
@@ -324,6 +330,7 @@ export const useImageSearch = () => {
|
|
|
324
330
|
? query
|
|
325
331
|
: text
|
|
326
332
|
: undefined,
|
|
333
|
+
scopeIds: window.settings?.scopeFilter ? user?.email : undefined,
|
|
327
334
|
});
|
|
328
335
|
useResultStore.getState().resetGrounding();
|
|
329
336
|
|
|
@@ -421,6 +428,7 @@ export const useImageSearch = () => {
|
|
|
421
428
|
setSmartFilters,
|
|
422
429
|
setPostFilterFacets,
|
|
423
430
|
setInitialFindSearchSnapshot,
|
|
431
|
+
user,
|
|
424
432
|
],
|
|
425
433
|
);
|
|
426
434
|
|
|
@@ -339,14 +339,14 @@ Evaluate if these results match the image. If they do (same type of product), ro
|
|
|
339
339
|
|
|
340
340
|
// Enforce ≤5 rule: if initial results are ≤5, strip filter options, keep only text_search_prompt
|
|
341
341
|
if (activeProducts.length <= 5 && activeProducts.length > 0) {
|
|
342
|
-
mappedOptions = [
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
];
|
|
342
|
+
// mappedOptions = [
|
|
343
|
+
// {
|
|
344
|
+
// label: "Describe what you're looking for",
|
|
345
|
+
// action_type: 'text_search_prompt' as const,
|
|
346
|
+
// key: 'text_search',
|
|
347
|
+
// value: 'text_search',
|
|
348
|
+
// },
|
|
349
|
+
// ];
|
|
350
350
|
|
|
351
351
|
if (
|
|
352
352
|
window.settings.vizo?.findRelatedParts &&
|
|
@@ -22,6 +22,7 @@ import { getFilters } from 'services/filter';
|
|
|
22
22
|
import { useUploadSearch } from 'hooks/useUploadSearch';
|
|
23
23
|
import { ToastHelper } from 'helpers/ToastHelper';
|
|
24
24
|
import { useTranslation } from 'react-i18next';
|
|
25
|
+
import useScopeStore from 'stores/scope/scopeStore';
|
|
25
26
|
|
|
26
27
|
i18n.use(initReactI18next).init({
|
|
27
28
|
resources: translations,
|
|
@@ -49,6 +50,7 @@ function AppLayout(): JSX.Element {
|
|
|
49
50
|
|
|
50
51
|
const algoliaFilter = useRequestStore(state => state.algoliaFilter);
|
|
51
52
|
const metaFilter = useRequestStore(state => state.metaFilter);
|
|
53
|
+
const scopeItemKeys = useScopeStore(state => state.filterAttributes);
|
|
52
54
|
|
|
53
55
|
const setAlgoliaProducts = useResultStore(state => state.setAlgoliaProducts);
|
|
54
56
|
const setIsAlgoliaLoading = useUiStore(state => state.setIsAlgoliaLoading);
|
|
@@ -88,7 +90,7 @@ function AppLayout(): JSX.Element {
|
|
|
88
90
|
useEffect(() => {
|
|
89
91
|
setAlgoliaProducts(items);
|
|
90
92
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
91
|
-
}, [items]);
|
|
93
|
+
}, [items, scopeItemKeys]);
|
|
92
94
|
|
|
93
95
|
useEffect(() => {
|
|
94
96
|
if (status === 'loading' || status === 'stalled') {
|
|
@@ -145,10 +147,10 @@ function AppLayout(): JSX.Element {
|
|
|
145
147
|
}, [settings, setPreFilterList, setPreFilterLoading]);
|
|
146
148
|
|
|
147
149
|
useEffect(() => {
|
|
148
|
-
if (showPreFilter) {
|
|
150
|
+
if (showPreFilter && !settings.scopeFilter) {
|
|
149
151
|
getPreFilters();
|
|
150
152
|
}
|
|
151
|
-
}, [getPreFilters, showPreFilter]);
|
|
153
|
+
}, [getPreFilters, showPreFilter, settings.scopeFilter]);
|
|
152
154
|
|
|
153
155
|
const getImageFromClipboard = useCallback(
|
|
154
156
|
(event: ClipboardEvent): File | null => {
|
|
@@ -213,6 +215,25 @@ function AppLayout(): JSX.Element {
|
|
|
213
215
|
};
|
|
214
216
|
}, [handleClipboardPaste, showLayout]);
|
|
215
217
|
|
|
218
|
+
const applyScopeFilter = (filters: string | undefined) => {
|
|
219
|
+
const scopeFilterClause =
|
|
220
|
+
window.settings.scopeFilter && scopeItemKeys.length
|
|
221
|
+
? `(${scopeItemKeys[0].values.map(key => `${window.settings.alogoliaFilterField}:'${key}'`).join(' OR ')})`
|
|
222
|
+
: '';
|
|
223
|
+
|
|
224
|
+
if (!window.settings.scopeFilter || !window.settings.auth0?.enabled) {
|
|
225
|
+
return filters;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (!scopeFilterClause) {
|
|
229
|
+
return `sku:NOT_FOUND`;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return filters
|
|
233
|
+
? `${scopeFilterClause} AND (${filters})`
|
|
234
|
+
: scopeFilterClause;
|
|
235
|
+
};
|
|
236
|
+
|
|
216
237
|
if (!showLayout) {
|
|
217
238
|
return <Outlet />;
|
|
218
239
|
}
|
|
@@ -226,7 +247,7 @@ function AppLayout(): JSX.Element {
|
|
|
226
247
|
{window.settings?.algolia.enabled && (
|
|
227
248
|
<Configure
|
|
228
249
|
query={query}
|
|
229
|
-
filters={
|
|
250
|
+
filters={applyScopeFilter(
|
|
230
251
|
specifications && !requestImages.length
|
|
231
252
|
? `${alogoliaFilterField}:'${specifications.prefilter_value}'`
|
|
232
253
|
: !query && !algoliaFilter.includes('score=1')
|
|
@@ -237,8 +258,8 @@ function AppLayout(): JSX.Element {
|
|
|
237
258
|
algoliaFilter ? 'AND ' : ''
|
|
238
259
|
}${alogoliaFilterField}:'${metaFilter}'`
|
|
239
260
|
: ''
|
|
240
|
-
}
|
|
241
|
-
}
|
|
261
|
+
}`,
|
|
262
|
+
)}
|
|
242
263
|
// facets={['brand', 'keyword_0']}
|
|
243
264
|
hitsPerPage={20}
|
|
244
265
|
/>
|
package/src/pages/Result.tsx
CHANGED
|
@@ -322,7 +322,7 @@ function Results() {
|
|
|
322
322
|
>
|
|
323
323
|
{t('Showing results for machine', {
|
|
324
324
|
prefilter_title:
|
|
325
|
-
window.settings.
|
|
325
|
+
window.settings.visualSearchFilterKey,
|
|
326
326
|
prefilter_value: specifications.prefilter_value,
|
|
327
327
|
})}
|
|
328
328
|
</div>
|
|
@@ -570,6 +570,7 @@ function Results() {
|
|
|
570
570
|
onClose={() => {
|
|
571
571
|
setOpenModalCamera(s => !s);
|
|
572
572
|
}}
|
|
573
|
+
newSearch={true}
|
|
573
574
|
/>
|
|
574
575
|
{isAiModeOpen && (
|
|
575
576
|
<div className="desktop:hidden">
|
|
@@ -1,15 +1,25 @@
|
|
|
1
1
|
import { useAuth0 } from '@auth0/auth0-react';
|
|
2
2
|
import EmailVerification from 'components/EmailVerification';
|
|
3
|
+
import ScopeMissing from 'components/ScopeMissing';
|
|
3
4
|
import { useEffect, ReactElement } from 'react';
|
|
5
|
+
import { getScopes } from 'services/filter';
|
|
6
|
+
import useScopeStore from 'stores/scope/scopeStore';
|
|
4
7
|
|
|
5
8
|
export default function ProtectedRoute({
|
|
6
9
|
children,
|
|
7
10
|
}: {
|
|
8
11
|
children: ReactElement;
|
|
9
12
|
}) {
|
|
10
|
-
const { auth0 } = window.settings;
|
|
13
|
+
const { auth0, scopeFilter } = window.settings;
|
|
11
14
|
|
|
12
15
|
const { user, isAuthenticated, isLoading, loginWithRedirect } = useAuth0();
|
|
16
|
+
const setScope = useScopeStore(state => state.setScope);
|
|
17
|
+
const filterAttributes = useScopeStore(state => state.filterAttributes);
|
|
18
|
+
|
|
19
|
+
const setScopeLoading = useScopeStore(state => state.setScopeLoading);
|
|
20
|
+
const scopeIsLoading = useScopeStore(state => state.isLoading);
|
|
21
|
+
const hasFetchedScope = useScopeStore(state => state.hasFetched);
|
|
22
|
+
const setScopeFetched = useScopeStore(state => state.setScopeFetched);
|
|
13
23
|
|
|
14
24
|
useEffect(() => {
|
|
15
25
|
if (!isLoading && !user && !isAuthenticated && auth0.enabled) {
|
|
@@ -17,21 +27,54 @@ export default function ProtectedRoute({
|
|
|
17
27
|
}
|
|
18
28
|
}, [loginWithRedirect, isLoading, user, isAuthenticated, auth0]);
|
|
19
29
|
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
if (!scopeFilter || !isAuthenticated || !user?.email || hasFetchedScope) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
setScopeFetched(true);
|
|
35
|
+
setScopeLoading(true);
|
|
36
|
+
getScopes(user.email, window.settings)
|
|
37
|
+
.then(response => {
|
|
38
|
+
setScope(response.itemKeys || [], response.filterAttributes || []);
|
|
39
|
+
})
|
|
40
|
+
.catch((e: any) => {
|
|
41
|
+
console.log('err getScopes', e);
|
|
42
|
+
})
|
|
43
|
+
.finally(() => {
|
|
44
|
+
setScopeLoading(false);
|
|
45
|
+
});
|
|
46
|
+
}, [
|
|
47
|
+
scopeFilter,
|
|
48
|
+
isAuthenticated,
|
|
49
|
+
user,
|
|
50
|
+
hasFetchedScope,
|
|
51
|
+
setScope,
|
|
52
|
+
setScopeLoading,
|
|
53
|
+
setScopeFetched,
|
|
54
|
+
]);
|
|
55
|
+
|
|
20
56
|
if (!auth0.enabled) {
|
|
21
57
|
return children;
|
|
22
58
|
}
|
|
23
59
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
// }
|
|
27
|
-
|
|
28
|
-
if (!isLoading && isAuthenticated && user?.email_verified) {
|
|
29
|
-
return children;
|
|
60
|
+
if (isLoading || !isAuthenticated) {
|
|
61
|
+
return <div></div>;
|
|
30
62
|
}
|
|
31
63
|
|
|
32
|
-
if (!
|
|
64
|
+
if (!user?.email_verified) {
|
|
33
65
|
return <EmailVerification />;
|
|
34
66
|
}
|
|
35
67
|
|
|
36
|
-
|
|
68
|
+
if (scopeFilter) {
|
|
69
|
+
// wait for the scope fetch to finish before deciding access, otherwise ScopeMissing flashes early
|
|
70
|
+
if (scopeIsLoading || !hasFetchedScope) {
|
|
71
|
+
return <div></div>;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (filterAttributes.length === 0) {
|
|
75
|
+
return <ScopeMissing />;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return children;
|
|
37
80
|
}
|
package/src/services/filter.ts
CHANGED
|
@@ -22,3 +22,16 @@ export const searchFilters = async (
|
|
|
22
22
|
const newValue = value ? value : '';
|
|
23
23
|
return nyrisApi.searchFilters(key, newValue);
|
|
24
24
|
};
|
|
25
|
+
|
|
26
|
+
export interface ScopeResponse {
|
|
27
|
+
itemKeys: string[];
|
|
28
|
+
filterAttributes: Filter[];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const getScopes = async (
|
|
32
|
+
scopeIds: string,
|
|
33
|
+
settings: NyrisAPISettings,
|
|
34
|
+
): Promise<ScopeResponse> => {
|
|
35
|
+
const nyrisApi = new NyrisAPI(settings);
|
|
36
|
+
return nyrisApi.getScopes(scopeIds);
|
|
37
|
+
};
|
|
@@ -58,12 +58,14 @@ export const find = async ({
|
|
|
58
58
|
region,
|
|
59
59
|
filters,
|
|
60
60
|
text,
|
|
61
|
+
scopeIds,
|
|
61
62
|
}: {
|
|
62
63
|
image?: HTMLCanvasElement;
|
|
63
64
|
settings: NyrisAPISettings;
|
|
64
65
|
region?: RectCoords;
|
|
65
66
|
filters?: Filter[];
|
|
66
67
|
text?: string;
|
|
68
|
+
scopeIds?: string;
|
|
67
69
|
}) => {
|
|
68
70
|
const nyrisApi = new NyrisAPI(settings);
|
|
69
71
|
let options: ImageSearchOptions = text ? { text } : {};
|
|
@@ -82,7 +84,14 @@ export const find = async ({
|
|
|
82
84
|
if (region) {
|
|
83
85
|
options = { ...options, cropRect: region };
|
|
84
86
|
}
|
|
85
|
-
return nyrisApi.find(
|
|
87
|
+
return nyrisApi.find(
|
|
88
|
+
options,
|
|
89
|
+
image,
|
|
90
|
+
filters,
|
|
91
|
+
undefined,
|
|
92
|
+
undefined,
|
|
93
|
+
scopeIds ? [scopeIds] : [],
|
|
94
|
+
);
|
|
86
95
|
};
|
|
87
96
|
|
|
88
97
|
export const findCad = ({
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { create } from 'zustand';
|
|
2
|
+
import { Filter } from '@nyris/nyris-api';
|
|
3
|
+
|
|
4
|
+
interface ScopeState {
|
|
5
|
+
itemKeys: string[];
|
|
6
|
+
filterAttributes: Filter[];
|
|
7
|
+
isLoading: boolean;
|
|
8
|
+
hasFetched: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface ScopeAction {
|
|
12
|
+
setScope: (itemKeys: string[], filterAttributes: Filter[]) => void;
|
|
13
|
+
setScopeLoading: (isLoading: boolean) => void;
|
|
14
|
+
setScopeFetched: (hasFetched: boolean) => void;
|
|
15
|
+
resetScope: () => void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const initialState: ScopeState = {
|
|
19
|
+
itemKeys: [],
|
|
20
|
+
filterAttributes: [],
|
|
21
|
+
isLoading: false,
|
|
22
|
+
hasFetched: false,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const useScopeStore = create<ScopeState & ScopeAction>()(set => ({
|
|
26
|
+
...initialState,
|
|
27
|
+
|
|
28
|
+
setScope: (itemKeys, filterAttributes) =>
|
|
29
|
+
set({ itemKeys, filterAttributes, hasFetched: true }),
|
|
30
|
+
setScopeLoading: isLoading => set({ isLoading }),
|
|
31
|
+
setScopeFetched: hasFetched => set({ hasFetched }),
|
|
32
|
+
resetScope: () => set(initialState),
|
|
33
|
+
}));
|
|
34
|
+
|
|
35
|
+
export default useScopeStore;
|
package/src/styles/Cart.scss
CHANGED
|
@@ -205,6 +205,28 @@
|
|
|
205
205
|
margin-right: 8px;
|
|
206
206
|
}
|
|
207
207
|
}
|
|
208
|
+
.cart-email-button {
|
|
209
|
+
background-color: #fff;
|
|
210
|
+
color: #3e36dc;
|
|
211
|
+
border: 1px solid #3e36dc;
|
|
212
|
+
width: 100%;
|
|
213
|
+
height: 48px;
|
|
214
|
+
display: flex;
|
|
215
|
+
flex-direction: row;
|
|
216
|
+
align-items: center;
|
|
217
|
+
justify-content: center;
|
|
218
|
+
font-weight: 600;
|
|
219
|
+
font-size: 16px;
|
|
220
|
+
border-radius: 8px;
|
|
221
|
+
margin-top: 8px;
|
|
222
|
+
&:disabled {
|
|
223
|
+
opacity: 0.6;
|
|
224
|
+
cursor: not-allowed;
|
|
225
|
+
}
|
|
226
|
+
svg {
|
|
227
|
+
margin-right: 8px;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
208
230
|
}
|
|
209
231
|
}
|
|
210
232
|
}
|
package/src/types.ts
CHANGED
|
@@ -79,7 +79,11 @@ export interface AppSettings extends NyrisAPISettings {
|
|
|
79
79
|
brandName?: string;
|
|
80
80
|
cadenas?: Cadenas;
|
|
81
81
|
cadSearch?: boolean;
|
|
82
|
-
cart?:
|
|
82
|
+
cart?: {
|
|
83
|
+
enabled: boolean;
|
|
84
|
+
buttonText: string;
|
|
85
|
+
emailTemplateId: string;
|
|
86
|
+
};
|
|
83
87
|
clarityId?: string;
|
|
84
88
|
CTAButton?: CTAButtonSettings;
|
|
85
89
|
experienceVisualSearch?: boolean;
|
|
@@ -110,6 +114,7 @@ export interface AppSettings extends NyrisAPISettings {
|
|
|
110
114
|
refinements?: any;
|
|
111
115
|
regions: boolean;
|
|
112
116
|
rfq?: Rfq;
|
|
117
|
+
scopeFilter?: boolean;
|
|
113
118
|
secondaryCTAButton?: SecondaryCTAButton;
|
|
114
119
|
secondaryCTAButtonText?: string;
|
|
115
120
|
secondaryTitle: string;
|