@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
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nyris/nyris-webapp",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.97",
|
|
4
4
|
"dependencies": {
|
|
5
5
|
"@auth0/auth0-react": "^2.2.4",
|
|
6
6
|
"@emailjs/browser": "^4.3.3",
|
|
7
7
|
"@google/genai": "^1.46.0",
|
|
8
|
-
"@nyris/nyris-api": "^0.3.
|
|
9
|
-
"@nyris/nyris-react-components": "^0.3.
|
|
8
|
+
"@nyris/nyris-api": "^0.3.97",
|
|
9
|
+
"@nyris/nyris-react-components": "^0.3.97",
|
|
10
10
|
"@radix-ui/react-accordion": "^1.2.2",
|
|
11
11
|
"@radix-ui/react-dialog": "^1.1.4",
|
|
12
12
|
"@radix-ui/react-popover": "^1.1.4",
|
|
@@ -97,7 +97,11 @@ var settings = {
|
|
|
97
97
|
},
|
|
98
98
|
],
|
|
99
99
|
// features
|
|
100
|
-
cart:
|
|
100
|
+
cart: {
|
|
101
|
+
enabled: true,
|
|
102
|
+
buttonText: 'Add to cart',
|
|
103
|
+
emailTemplateId: 'template_h3rt2pi',
|
|
104
|
+
},
|
|
101
105
|
showPoweredByNyris: '',
|
|
102
106
|
postFilterOption: '',
|
|
103
107
|
preFilterOption: '',
|
|
@@ -115,6 +119,7 @@ var settings = {
|
|
|
115
119
|
enabled: '',
|
|
116
120
|
emailTemplateId: '',
|
|
117
121
|
},
|
|
122
|
+
scopeFilter: true,
|
|
118
123
|
support: {
|
|
119
124
|
enabled: '',
|
|
120
125
|
emailInquiry: '',
|
package/src/components/Cart.tsx
CHANGED
|
@@ -4,6 +4,14 @@ import Tooltip from './Tooltip/TooltipComponent';
|
|
|
4
4
|
import { twMerge } from 'tailwind-merge';
|
|
5
5
|
import toast from 'react-hot-toast';
|
|
6
6
|
import { Icon } from '@nyris/nyris-react-components';
|
|
7
|
+
import { Send } from 'lucide-react';
|
|
8
|
+
import emailjs from '@emailjs/browser';
|
|
9
|
+
import { useAuth0 } from '@auth0/auth0-react';
|
|
10
|
+
import NoImage from '../common/assets/images/no-image.svg';
|
|
11
|
+
|
|
12
|
+
const EMAILJS_SERVICE_ID = 'service_zfsxshi';
|
|
13
|
+
const EMAILJS_TEMPLATE_ID = window.settings.cart?.emailTemplateId || '';
|
|
14
|
+
const EMAILJS_PUBLIC_KEY = 'SMGihPnuEGcYLm0V4';
|
|
7
15
|
|
|
8
16
|
const Cart = () => {
|
|
9
17
|
const [isCartOpen, setIsCartOpen] = useState(false);
|
|
@@ -13,6 +21,13 @@ const Cart = () => {
|
|
|
13
21
|
{},
|
|
14
22
|
);
|
|
15
23
|
|
|
24
|
+
const { user } = useAuth0();
|
|
25
|
+
const auth0Email = window.settings.auth0?.enabled ? user?.email : undefined;
|
|
26
|
+
|
|
27
|
+
const [isSendingEmail, setIsSendingEmail] = useState(false);
|
|
28
|
+
const [isEmailInputVisible, setIsEmailInputVisible] = useState(false);
|
|
29
|
+
const [userEmail, setUserEmail] = useState('');
|
|
30
|
+
|
|
16
31
|
const updateCount = () => {
|
|
17
32
|
const cart = sessionStorage.getItem('cart');
|
|
18
33
|
if (cart) {
|
|
@@ -111,6 +126,48 @@ const Cart = () => {
|
|
|
111
126
|
toast.success('Your data has been downloaded.');
|
|
112
127
|
};
|
|
113
128
|
|
|
129
|
+
const sendCSVByEmail = async (
|
|
130
|
+
e?: React.FormEvent,
|
|
131
|
+
emailOverride?: string,
|
|
132
|
+
) => {
|
|
133
|
+
if (e) {
|
|
134
|
+
e.preventDefault();
|
|
135
|
+
}
|
|
136
|
+
const emailToSend = emailOverride ?? userEmail;
|
|
137
|
+
if (!emailToSend || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(emailToSend)) {
|
|
138
|
+
toast.error('Please enter a valid email address.');
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
setIsSendingEmail(true);
|
|
143
|
+
try {
|
|
144
|
+
const csvContent = generateCSVContent();
|
|
145
|
+
// Use btoa to encode the CSV string to base64 directly as recommended by EmailJS
|
|
146
|
+
const base64Content = btoa(unescape(encodeURIComponent(csvContent)));
|
|
147
|
+
|
|
148
|
+
await emailjs.send(
|
|
149
|
+
EMAILJS_SERVICE_ID,
|
|
150
|
+
EMAILJS_TEMPLATE_ID,
|
|
151
|
+
{
|
|
152
|
+
email_id: emailToSend,
|
|
153
|
+
// machine_name: getMachineName(),
|
|
154
|
+
date: new Date().toLocaleDateString(),
|
|
155
|
+
item_count: amountOfParts,
|
|
156
|
+
content: base64Content,
|
|
157
|
+
},
|
|
158
|
+
EMAILJS_PUBLIC_KEY,
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
toast.success('Parts list has been sent by email.');
|
|
162
|
+
setIsEmailInputVisible(false);
|
|
163
|
+
setUserEmail('');
|
|
164
|
+
} catch (error) {
|
|
165
|
+
toast.error('Failed to send the parts list by email.');
|
|
166
|
+
} finally {
|
|
167
|
+
setIsSendingEmail(false);
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
|
|
114
171
|
return (
|
|
115
172
|
<>
|
|
116
173
|
<Tooltip
|
|
@@ -211,103 +268,113 @@ const Cart = () => {
|
|
|
211
268
|
) : (
|
|
212
269
|
''
|
|
213
270
|
)}
|
|
214
|
-
{cartParts.map((part: any, i: number) =>
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
'
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
271
|
+
{cartParts.map((part: any, i: number) => {
|
|
272
|
+
const imageSrc = !window.settings.algolia.enabled
|
|
273
|
+
? part['image']
|
|
274
|
+
: part['image(main_similarity)'] ||
|
|
275
|
+
part['main_image_link'];
|
|
276
|
+
|
|
277
|
+
return (
|
|
278
|
+
<div
|
|
279
|
+
key={part.sku + i}
|
|
280
|
+
className={twMerge(
|
|
281
|
+
'flex h-fit w-full items-center justify-between rounded-lg border border-solid border-[#DDDEE7] bg-[#F3F4F8]',
|
|
282
|
+
'p-2',
|
|
283
|
+
'gap-2',
|
|
284
|
+
)}
|
|
285
|
+
>
|
|
286
|
+
<div className="flex flex-1 items-center">
|
|
287
|
+
{!imageSrc ? (
|
|
288
|
+
<img
|
|
289
|
+
src={NoImage}
|
|
290
|
+
alt="image_item"
|
|
291
|
+
className="mr-2 h-[50px] w-[50px] object-contain"
|
|
292
|
+
/>
|
|
293
|
+
) : (
|
|
294
|
+
<img
|
|
295
|
+
src={imageSrc}
|
|
296
|
+
key={part.sku + i}
|
|
297
|
+
alt={`product ${i}`}
|
|
298
|
+
className="mr-2 h-[50px] w-[50px] bg-white object-contain"
|
|
299
|
+
/>
|
|
300
|
+
)}
|
|
301
|
+
|
|
237
302
|
<div>
|
|
238
|
-
<div
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
303
|
+
<div>
|
|
304
|
+
<div className="max-w-[175px] break-words text-[9px] font-normal">
|
|
305
|
+
{part.sku}
|
|
306
|
+
</div>
|
|
307
|
+
<div className="max-w-[175px] break-words text-xs font-semibold">
|
|
308
|
+
{part.title}
|
|
309
|
+
</div>
|
|
243
310
|
</div>
|
|
244
311
|
</div>
|
|
245
312
|
</div>
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
}
|
|
313
|
+
<div className="flex items-center">
|
|
314
|
+
<div className="ml-auto flex items-center gap-4 pr-4">
|
|
315
|
+
<button
|
|
316
|
+
className="h-4 w-4 rounded-full bg-[#FAFAFA]"
|
|
317
|
+
onClick={() => updateQuantity(part.sku, -1)}
|
|
318
|
+
>
|
|
319
|
+
<Icon name="minus" width="16" height="16" />
|
|
320
|
+
</button>
|
|
321
|
+
<input
|
|
322
|
+
className="flex h-[22px] w-[22px] items-center justify-center rounded border border-solid border-[#DDDEE7] bg-white text-center"
|
|
323
|
+
value={getQuantityDisplayValue(
|
|
324
|
+
part.sku,
|
|
325
|
+
part.quantity,
|
|
326
|
+
)}
|
|
327
|
+
onChange={e => {
|
|
328
|
+
const rawValue = e.target.value;
|
|
329
|
+
if (rawValue === '') {
|
|
330
|
+
setQuantityEdits(prev => ({
|
|
331
|
+
...prev,
|
|
332
|
+
[part.sku]: '',
|
|
333
|
+
}));
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
270
336
|
|
|
271
|
-
|
|
337
|
+
if (!/^[0-9]+$/.test(rawValue)) return;
|
|
272
338
|
|
|
273
|
-
|
|
274
|
-
|
|
339
|
+
const newValue = Number(rawValue);
|
|
340
|
+
if (newValue < 1) return;
|
|
275
341
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
342
|
+
setQuantityEdits(prev => ({
|
|
343
|
+
...prev,
|
|
344
|
+
[part.sku]: rawValue,
|
|
345
|
+
}));
|
|
346
|
+
updateQuantity(
|
|
347
|
+
part.sku,
|
|
348
|
+
newValue - (part.quantity || 0),
|
|
349
|
+
);
|
|
350
|
+
}}
|
|
351
|
+
onBlur={() => {
|
|
352
|
+
if (quantityEdits[part.sku] === '') {
|
|
353
|
+
setQuantityEdits(prev => {
|
|
354
|
+
const updated = { ...prev };
|
|
355
|
+
delete updated[part.sku];
|
|
356
|
+
return updated;
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
}}
|
|
360
|
+
/>
|
|
361
|
+
<button
|
|
362
|
+
className="h-4 w-4 rounded-full bg-[#FAFAFA] flex items-center justify-center"
|
|
363
|
+
onClick={() => updateQuantity(part.sku, 1)}
|
|
364
|
+
>
|
|
365
|
+
<Icon name="plus" width="10" height="10" />
|
|
366
|
+
</button>
|
|
367
|
+
</div>
|
|
295
368
|
<button
|
|
296
|
-
className="h-
|
|
297
|
-
onClick={() =>
|
|
369
|
+
className="h-7 w-7 rounded bg-[#FFE5EF4D] p-1.5"
|
|
370
|
+
onClick={() => removePartBySku(part.sku)}
|
|
298
371
|
>
|
|
299
|
-
<Icon name="
|
|
372
|
+
<Icon name="trash" color="#E31B5D" />
|
|
300
373
|
</button>
|
|
301
374
|
</div>
|
|
302
|
-
<button
|
|
303
|
-
className="h-7 w-7 rounded bg-[#FFE5EF4D] p-1.5"
|
|
304
|
-
onClick={() => removePartBySku(part.sku)}
|
|
305
|
-
>
|
|
306
|
-
<Icon name="trash" color="#E31B5D" />
|
|
307
|
-
</button>
|
|
308
375
|
</div>
|
|
309
|
-
|
|
310
|
-
)
|
|
376
|
+
);
|
|
377
|
+
})}
|
|
311
378
|
{amountOfParts > 0 ? (
|
|
312
379
|
<button
|
|
313
380
|
className="flex h-12 w-full items-center justify-center rounded-lg bg-[#3E36DC] text-base font-semibold text-white"
|
|
@@ -320,6 +387,54 @@ const Cart = () => {
|
|
|
320
387
|
) : (
|
|
321
388
|
''
|
|
322
389
|
)}
|
|
390
|
+
{amountOfParts > 0 &&
|
|
391
|
+
window.settings.cart?.emailTemplateId ? (
|
|
392
|
+
auth0Email ? (
|
|
393
|
+
<button
|
|
394
|
+
className="mt-2 flex h-12 w-full items-center justify-center gap-2 rounded-lg border border-solid border-[#3E36DC] bg-white text-base font-semibold text-[#3E36DC] disabled:cursor-not-allowed disabled:opacity-60"
|
|
395
|
+
type="button"
|
|
396
|
+
onClick={() => sendCSVByEmail(undefined, auth0Email)}
|
|
397
|
+
disabled={isSendingEmail}
|
|
398
|
+
>
|
|
399
|
+
<Icon name="email" />
|
|
400
|
+
Send by email
|
|
401
|
+
</button>
|
|
402
|
+
) : isEmailInputVisible ? (
|
|
403
|
+
<form
|
|
404
|
+
className="cart-email-form flex items-center relative mt-2 h-12 w-full"
|
|
405
|
+
onSubmit={sendCSVByEmail}
|
|
406
|
+
>
|
|
407
|
+
<input
|
|
408
|
+
type="email"
|
|
409
|
+
className="w-full h-full px-4 border border-[#CACAD1] rounded-lg pr-12 focus:outline-none focus:border-[#3e36dc]"
|
|
410
|
+
placeholder="Enter your email"
|
|
411
|
+
value={userEmail}
|
|
412
|
+
onChange={e => setUserEmail(e.target.value)}
|
|
413
|
+
disabled={isSendingEmail}
|
|
414
|
+
autoFocus
|
|
415
|
+
/>
|
|
416
|
+
<button
|
|
417
|
+
type="submit"
|
|
418
|
+
className="absolute right-2 h-8 w-8 flex items-center justify-center rounded disabled:opacity-50"
|
|
419
|
+
disabled={isSendingEmail || !userEmail}
|
|
420
|
+
>
|
|
421
|
+
<Send className="w-5 h-5 " />
|
|
422
|
+
</button>
|
|
423
|
+
</form>
|
|
424
|
+
) : (
|
|
425
|
+
<button
|
|
426
|
+
className="mt-2 flex h-12 w-full items-center justify-center gap-2 rounded-lg border border-solid border-[#3E36DC] bg-white text-base font-semibold text-[#3E36DC] disabled:cursor-not-allowed disabled:opacity-60"
|
|
427
|
+
type="button"
|
|
428
|
+
onClick={() => setIsEmailInputVisible(true)}
|
|
429
|
+
disabled={isSendingEmail}
|
|
430
|
+
>
|
|
431
|
+
<Icon name="email" />
|
|
432
|
+
Send by email
|
|
433
|
+
</button>
|
|
434
|
+
)
|
|
435
|
+
) : (
|
|
436
|
+
''
|
|
437
|
+
)}
|
|
323
438
|
</div>
|
|
324
439
|
</div>
|
|
325
440
|
</div>
|
|
@@ -66,10 +66,10 @@ export default function InquiryModal({
|
|
|
66
66
|
useEffect(() => {
|
|
67
67
|
function omitKeys(obj: any, keys: string[]) {
|
|
68
68
|
return Object.fromEntries(
|
|
69
|
-
Object.entries(obj)
|
|
69
|
+
Object.entries(obj)?.filter(([key]) => !keys.includes(key)),
|
|
70
70
|
);
|
|
71
71
|
}
|
|
72
|
-
const omittedSpecification = omitKeys(specifications, [
|
|
72
|
+
const omittedSpecification = omitKeys(specifications || {}, [
|
|
73
73
|
'is_nameplate',
|
|
74
74
|
'prefilter_value',
|
|
75
75
|
]);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useEffect, useState } from 'react';
|
|
1
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
2
2
|
import {
|
|
3
3
|
AccordionContent,
|
|
4
4
|
AccordionItem,
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
import { useRefinementList } from 'react-instantsearch';
|
|
8
8
|
import { Icon } from '@nyris/nyris-react-components';
|
|
9
9
|
import { useTranslation } from 'react-i18next';
|
|
10
|
+
import useScopeStore from 'stores/scope/scopeStore';
|
|
10
11
|
|
|
11
12
|
function PostFilter({
|
|
12
13
|
attribute,
|
|
@@ -23,7 +24,11 @@ function PostFilter({
|
|
|
23
24
|
}) {
|
|
24
25
|
const [itemsLimit, setItemsLimit] = useState(10);
|
|
25
26
|
const [searchInput, setSearchInput] = useState<string>('');
|
|
26
|
-
const {
|
|
27
|
+
const {
|
|
28
|
+
items: allItems,
|
|
29
|
+
refine,
|
|
30
|
+
searchForItems,
|
|
31
|
+
} = useRefinementList({
|
|
27
32
|
attribute: attribute,
|
|
28
33
|
operator: 'or',
|
|
29
34
|
showMore: false,
|
|
@@ -32,6 +37,32 @@ function PostFilter({
|
|
|
32
37
|
|
|
33
38
|
const { t } = useTranslation();
|
|
34
39
|
|
|
40
|
+
const scopeFilterEnabled = window.settings.scopeFilter;
|
|
41
|
+
const scopeFilterAttributes = useScopeStore(state => state.filterAttributes);
|
|
42
|
+
|
|
43
|
+
const scopedAllowedValues = useMemo(() => {
|
|
44
|
+
if (!scopeFilterEnabled || !window.settings.auth0.enabled) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
// todo: support multiple attributes in the future
|
|
48
|
+
const scopeAttribute = scopeFilterAttributes[0];
|
|
49
|
+
if (!scopeAttribute) {
|
|
50
|
+
return new Set<string>();
|
|
51
|
+
}
|
|
52
|
+
return new Set(
|
|
53
|
+
scopeAttribute.values.map(value => value.toLocaleLowerCase()),
|
|
54
|
+
);
|
|
55
|
+
}, [scopeFilterEnabled, scopeFilterAttributes]);
|
|
56
|
+
|
|
57
|
+
const items = useMemo(() => {
|
|
58
|
+
if (!scopedAllowedValues) {
|
|
59
|
+
return allItems;
|
|
60
|
+
}
|
|
61
|
+
return allItems.filter(item =>
|
|
62
|
+
scopedAllowedValues.has(item.value.toLocaleLowerCase()),
|
|
63
|
+
);
|
|
64
|
+
}, [allItems, scopedAllowedValues]);
|
|
65
|
+
|
|
35
66
|
useEffect(() => {
|
|
36
67
|
searchForItems(searchInput);
|
|
37
68
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
@@ -39,7 +70,9 @@ function PostFilter({
|
|
|
39
70
|
|
|
40
71
|
const hasSelection = items.some(item => item.isRefined);
|
|
41
72
|
const hasAvailableValues = items.length > 0 || searchInput.length > 0;
|
|
42
|
-
const isVisible =
|
|
73
|
+
const isVisible =
|
|
74
|
+
(!scopedAllowedValues || scopedAllowedValues.size > 0) &&
|
|
75
|
+
(hasAvailableValues || hasSelection);
|
|
43
76
|
|
|
44
77
|
useEffect(() => {
|
|
45
78
|
onVisibilityChange?.(attribute, isVisible);
|
|
@@ -10,7 +10,9 @@ import { isEmpty } from 'lodash';
|
|
|
10
10
|
import useResultStore from 'stores/result/resultStore';
|
|
11
11
|
import useRequestStore from 'stores/request/requestStore';
|
|
12
12
|
import useUiStore from 'stores/ui/uiStore';
|
|
13
|
+
import useScopeStore from 'stores/scope/scopeStore';
|
|
13
14
|
import { find } from 'services/visualSearch';
|
|
15
|
+
import { useAuth0 } from '@auth0/auth0-react';
|
|
14
16
|
|
|
15
17
|
let postFilterRequestCounter = 0;
|
|
16
18
|
|
|
@@ -31,6 +33,8 @@ function PostFilterFindApi({
|
|
|
31
33
|
onVisibilityChange?: (attribute: string, isVisible: boolean) => void;
|
|
32
34
|
isLastVisible?: boolean;
|
|
33
35
|
}) {
|
|
36
|
+
const { user } = useAuth0();
|
|
37
|
+
|
|
34
38
|
const [itemsLimit, setItemsLimit] = useState(10);
|
|
35
39
|
const [searchInput, setSearchInput] = useState<string>('');
|
|
36
40
|
|
|
@@ -52,6 +56,23 @@ function PostFilterFindApi({
|
|
|
52
56
|
const { t } = useTranslation();
|
|
53
57
|
const settings = window.settings;
|
|
54
58
|
|
|
59
|
+
const scopeFilterEnabled = settings.scopeFilter;
|
|
60
|
+
const scopeFilterAttributes = useScopeStore(state => state.filterAttributes);
|
|
61
|
+
|
|
62
|
+
const scopedAllowedValues = useMemo(() => {
|
|
63
|
+
if (!scopeFilterEnabled || !window.settings.auth0?.enabled) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
// todo: support multiple attributes in the future
|
|
67
|
+
const scopeAttribute = scopeFilterAttributes[0];
|
|
68
|
+
if (!scopeAttribute) {
|
|
69
|
+
return new Set<string>();
|
|
70
|
+
}
|
|
71
|
+
return new Set(
|
|
72
|
+
scopeAttribute.values.map(value => value.toLocaleLowerCase()),
|
|
73
|
+
);
|
|
74
|
+
}, [scopeFilterEnabled, scopeFilterAttributes]);
|
|
75
|
+
|
|
55
76
|
const matchedFacet = useMemo(() => {
|
|
56
77
|
const facets = Array.isArray(postFilterFacets) ? postFilterFacets : [];
|
|
57
78
|
return facets.find(
|
|
@@ -96,6 +117,11 @@ function PostFilterFindApi({
|
|
|
96
117
|
count: valueCounts[label] || 0,
|
|
97
118
|
isRefined: selectedValues.includes(label),
|
|
98
119
|
}))
|
|
120
|
+
.filter(
|
|
121
|
+
item =>
|
|
122
|
+
!scopedAllowedValues ||
|
|
123
|
+
scopedAllowedValues.has(item.value.toLocaleLowerCase()),
|
|
124
|
+
)
|
|
99
125
|
.filter(
|
|
100
126
|
item =>
|
|
101
127
|
!searchInput ||
|
|
@@ -103,13 +129,22 @@ function PostFilterFindApi({
|
|
|
103
129
|
)
|
|
104
130
|
.sort((a, b) => b.count - a.count || a.label.localeCompare(b.label))
|
|
105
131
|
.slice(0, itemsLimit);
|
|
106
|
-
}, [
|
|
132
|
+
}, [
|
|
133
|
+
attribute,
|
|
134
|
+
itemsLimit,
|
|
135
|
+
postFilterSelections,
|
|
136
|
+
scopedAllowedValues,
|
|
137
|
+
searchInput,
|
|
138
|
+
valueCounts,
|
|
139
|
+
]);
|
|
107
140
|
|
|
108
141
|
const selectedValues = postFilterSelections?.[attribute] || [];
|
|
109
142
|
const hasSelection = selectedValues.length > 0;
|
|
110
143
|
const hasAvailableValues = Object.keys(valueCounts).length > 0;
|
|
111
144
|
const hasSearch = searchable && searchInput.length > 0;
|
|
112
|
-
const isVisible =
|
|
145
|
+
const isVisible =
|
|
146
|
+
(!scopedAllowedValues || scopedAllowedValues.size > 0) &&
|
|
147
|
+
(hasAvailableValues || hasSelection || hasSearch);
|
|
113
148
|
|
|
114
149
|
useEffect(() => {
|
|
115
150
|
onVisibilityChange?.(attribute, isVisible);
|
|
@@ -180,6 +215,7 @@ function PostFilterFindApi({
|
|
|
180
215
|
region: regions[0],
|
|
181
216
|
filters,
|
|
182
217
|
text: !requestImages[0] ? query : undefined,
|
|
218
|
+
scopeIds: window.settings?.scopeFilter ? user?.email : undefined,
|
|
183
219
|
})
|
|
184
220
|
.then(response => {
|
|
185
221
|
if (requestId !== postFilterRequestCounter) {
|
|
@@ -264,7 +300,9 @@ function PostFilterFindApi({
|
|
|
264
300
|
<input
|
|
265
301
|
type="checkbox"
|
|
266
302
|
checked={item.isRefined}
|
|
267
|
-
onChange={() =>
|
|
303
|
+
onChange={() =>
|
|
304
|
+
handleFilterClick(item.value, item.isRefined)
|
|
305
|
+
}
|
|
268
306
|
className="cursor-pointer"
|
|
269
307
|
/>
|
|
270
308
|
<span className="text-xs font-normal pl-2 pr-1">
|
|
@@ -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,7 +80,11 @@ const PreFilterComponent = (props: Props) => {
|
|
|
76
80
|
};
|
|
77
81
|
}, []);
|
|
78
82
|
|
|
79
|
-
const
|
|
83
|
+
const activeFilterList = scopeFilterEnabled
|
|
84
|
+
? scopeFilterAttributes
|
|
85
|
+
: preFilterList;
|
|
86
|
+
|
|
87
|
+
const matchedPreFilter = activeFilterList.find(
|
|
80
88
|
value =>
|
|
81
89
|
value.key?.toLocaleLowerCase() ===
|
|
82
90
|
settings.visualSearchFilterKey?.toLocaleLowerCase(),
|
|
@@ -105,6 +113,26 @@ const PreFilterComponent = (props: Props) => {
|
|
|
105
113
|
return;
|
|
106
114
|
}
|
|
107
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
|
+
|
|
108
136
|
const data = await searchFilters(
|
|
109
137
|
preFilterApiKey,
|
|
110
138
|
encodeURIComponent(value),
|
|
@@ -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
|