@nyris/nyris-webapp 0.3.97 → 0.3.99
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 +7 -6
- package/build/index.html +1 -1
- package/build/js/settings.example.js +41 -0
- package/build/static/css/main.5cb72c09.css +4 -0
- package/build/static/css/main.5cb72c09.css.map +1 -0
- package/build/static/js/main.2339271b.js +3 -0
- package/build/static/js/{main.9e7fb38a.js.map → main.2339271b.js.map} +1 -1
- package/build/static/media/plate_info.6beaea36d35053be3a45.png +0 -0
- package/package.json +3 -3
- package/public/js/settings.example.js +41 -0
- package/src/common/assets/images/plate_info.png +0 -0
- package/src/components/AuthApiKeySync.tsx +34 -0
- package/src/components/Cart.tsx +41 -0
- package/src/components/ChatAssistant/useChatAssistantLogic.ts +3 -2
- package/src/components/GoBackButton.tsx +29 -2
- package/src/components/Header.tsx +3 -0
- package/src/components/HitsPerPage.tsx +55 -18
- package/src/components/ImagePreview.tsx +31 -1
- package/src/components/ImageUpload.tsx +14 -3
- package/src/components/Inquiry/InquiryModal.tsx +3 -0
- package/src/components/LogoutModal.tsx +2 -0
- package/src/components/Pagination.tsx +33 -6
- package/src/components/PostFilter/PostFilter.tsx +8 -1
- package/src/components/PostFilter/PostFilterComponent.tsx +8 -22
- package/src/components/PostFilter/PostFilterFindApi.test.tsx +249 -0
- package/src/components/PostFilter/PostFilterFindApi.tsx +128 -52
- package/src/components/PreFilter/PreFilter.tsx +10 -2
- package/src/components/Product/Product.tsx +60 -10
- package/src/components/Product/ProductDetailView.tsx +23 -1
- package/src/components/Product/ProductDetailViewModal.tsx +5 -1
- package/src/components/Product/ProductList.tsx +51 -16
- package/src/components/Rfq/RfqModal.tsx +3 -0
- package/src/components/ScopeSelect/ScopeSelectForm.tsx +514 -0
- package/src/components/ScopeSelect/ScopeSelectImageActions.tsx +62 -0
- package/src/components/ScopeSelect/ScopeSelectImagePreview.tsx +141 -0
- package/src/components/ScopeSelect/ScopeSelectInfo.tsx +68 -0
- package/src/components/ScopeSelect/ScopeSelectInput.tsx +123 -0
- package/src/components/ScopeSelect/ScopeSelectModal.tsx +310 -0
- package/src/components/ScopeSelect/identifyTypePlateImage.test.ts +129 -0
- package/src/components/ScopeSelect/identifyTypePlateImage.ts +83 -0
- package/src/components/ScopeSelect/parseScopeIdResponse.test.ts +103 -0
- package/src/components/ScopeSelect/parseScopeIdResponse.ts +72 -0
- package/src/components/ScopeSelect/scopeSelectHelpers.ts +54 -0
- package/src/components/SidePanel.tsx +43 -0
- package/src/components/TextSearch.tsx +80 -14
- package/src/components/Tooltip/TooltipComponent.tsx +12 -4
- package/src/components/TypePlateUpload.tsx +132 -0
- package/src/hooks/useEffectiveGroundingResults.ts +8 -6
- package/src/hooks/useGoodResultsChat.ts +8 -22
- package/src/hooks/useImageSearch.ts +325 -20
- package/src/hooks/useMachinePartsListingPagination.ts +95 -0
- package/src/hooks/useResultEvaluator.ts +43 -19
- package/src/index.css +1 -1
- package/src/index.tsx +4 -0
- package/src/layouts/AppLayout.tsx +95 -9
- package/src/pages/Home.tsx +60 -14
- package/src/pages/Logout.tsx +2 -0
- package/src/pages/Result.tsx +206 -20
- package/src/providers/AuthProvider.tsx +5 -0
- package/src/services/Feedback.ts +224 -35
- package/src/services/feedbackSubtypes.ts +63 -0
- package/src/services/filter.ts +4 -3
- package/src/services/initialMachinePartsText.payload.test.ts +61 -0
- package/src/services/initialMachinePartsText.test.ts +33 -0
- package/src/services/initialMachinePartsText.ts +11 -0
- package/src/services/machinePartsPage.test.ts +74 -0
- package/src/services/machinePartsPage.ts +120 -0
- package/src/services/normalizeChatResponse.ts +26 -0
- package/src/services/scopeLookup.test.ts +67 -0
- package/src/services/scopeLookup.ts +39 -0
- package/src/services/visualSearch.listingOptions.test.ts +79 -0
- package/src/services/visualSearch.ts +76 -7
- package/src/services/vizo.ts +17 -5
- package/src/stores/machinePartsListing/machinePartsListingStore.ts +35 -0
- package/src/stores/request/filter/filter.initialState.ts +1 -0
- package/src/stores/request/filter/filter.slice.ts +2 -0
- package/src/stores/result/prodcuts/products.initialState.ts +1 -0
- package/src/stores/result/prodcuts/products.slice.ts +4 -0
- package/src/stores/scope/scopeStore.ts +67 -4
- package/src/stores/types.ts +19 -14
- package/src/translations.ts +213 -0
- package/src/types.ts +40 -0
- package/src/utils/derivePostFilterFacets.ts +68 -0
- package/src/utils/fetchProductImage.ts +1 -0
- package/src/utils/normalizeChatAssistantResponse.ts +227 -0
- package/src/utils/nyrisApiKey.ts +115 -0
- package/src/utils/productCardImageUrl.test.ts +41 -0
- package/src/utils/productCardImageUrl.ts +49 -0
- package/src/utils/textSearchFilter.test.ts +41 -0
- package/src/utils/textSearchFilter.ts +13 -6
- package/build/static/css/main.7c7f5447.css +0 -4
- package/build/static/css/main.7c7f5447.css.map +0 -1
- package/build/static/js/main.9e7fb38a.js +0 -3
- /package/build/static/js/{main.9e7fb38a.js.LICENSE.txt → main.2339271b.js.LICENSE.txt} +0 -0
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nyris/nyris-webapp",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.99",
|
|
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.99",
|
|
9
|
+
"@nyris/nyris-react-components": "^0.3.99",
|
|
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",
|
|
@@ -23,6 +23,7 @@ var settings = {
|
|
|
23
23
|
},
|
|
24
24
|
],
|
|
25
25
|
// Nyris - visual search
|
|
26
|
+
// When auth0.enabled is true, leave apiKey empty — the app uses Auth0 claim
|
|
26
27
|
apiKey: '',
|
|
27
28
|
aiApiKey: '',
|
|
28
29
|
baseUrl: 'https://api.nyris.io',
|
|
@@ -104,6 +105,7 @@ var settings = {
|
|
|
104
105
|
},
|
|
105
106
|
showPoweredByNyris: '',
|
|
106
107
|
postFilterOption: '',
|
|
108
|
+
postFilterUseApi: false,
|
|
107
109
|
preFilterOption: '',
|
|
108
110
|
experienceVisualSearch: false,
|
|
109
111
|
experienceVisualSearchImages: [],
|
|
@@ -111,6 +113,45 @@ var settings = {
|
|
|
111
113
|
noSimilarSearch: false,
|
|
112
114
|
useSmartFilters: false,
|
|
113
115
|
showFeedback: false,
|
|
116
|
+
// Feedback API events that should never be sent. Use event types
|
|
117
|
+
// ('click', 'conversion', 'feedback', 'region') or subtypes:
|
|
118
|
+
// click: result_card, expand_details, similar_part_search, copy_details,
|
|
119
|
+
// raise_inquiry, filter_applied, pre_filter_applied, chat_with_vizo
|
|
120
|
+
// conversion: add_to_cart, purchase, download_cart, share_cart
|
|
121
|
+
// feedback: thumbs_up, thumbs_down
|
|
122
|
+
// region: crop, object_detection_click
|
|
123
|
+
// e.g. ['copy_details', 'filter_applied']
|
|
124
|
+
disabledFeedbackEvents: [],
|
|
125
|
+
requestQuality: false,
|
|
126
|
+
scopes: {
|
|
127
|
+
scopeID: false,
|
|
128
|
+
typePlateButtonLabel: 'Scan type plate',
|
|
129
|
+
typePlateStepOne: 'Identify machine',
|
|
130
|
+
typePlateStepTwo: 'Find part',
|
|
131
|
+
typePlatePopupTitle: 'Identify your machine',
|
|
132
|
+
typePlatePopupTitleSuccess: 'Machine identified',
|
|
133
|
+
typePlatePopupCtaButton: 'Search within this machine',
|
|
134
|
+
typePlatePopupCtaTextButton: 'Enter serial number manually',
|
|
135
|
+
typePlatePopupFindMachineButton: 'Find machine',
|
|
136
|
+
typePlatePopupSkipButton: 'Skip and search all parts',
|
|
137
|
+
typePlatePopupBackButton: 'Back to results',
|
|
138
|
+
typePlatePopupUploadButton: 'Upload image',
|
|
139
|
+
typePlatePopupUploadNewButton: 'Upload new type plate',
|
|
140
|
+
typePlatePopupTakePhotoButton: 'Take a photo',
|
|
141
|
+
typePlatePopupApplyingLabel: 'Applying...',
|
|
142
|
+
typePlateResultsScopeLabel:
|
|
143
|
+
'Searching within parts for {{scope_id}}. You can search with keyword or image to find the right item',
|
|
144
|
+
typePlateResultsNoScopeLabel:
|
|
145
|
+
'Scan your type plate to see only parts for your equipment.',
|
|
146
|
+
typePlateResultsClearScopeLabel: 'Search all products',
|
|
147
|
+
typePlatePopupErrorMessage: 'The serial number was not found. Please check again.',
|
|
148
|
+
typePlateManualInputLabel: "Can't scan? Enter serial number manually.",
|
|
149
|
+
typePlateManualInputPlaceholder: 'e.g. 2234GH',
|
|
150
|
+
typePlateWhereToFindTitle: 'Where to find it',
|
|
151
|
+
typePlateWhereToFindDescription:
|
|
152
|
+
'A metal or foil label on the machine housing, usually near the motor or the main panel. Make sure the serial number is sharp and readable.',
|
|
153
|
+
typePlateSerialNumberLabel: 'Serial number',
|
|
154
|
+
},
|
|
114
155
|
geoLocation: false,
|
|
115
156
|
geoLocationMessage: '',
|
|
116
157
|
showImageDetails: false,
|
|
Binary file
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { useAuth0 } from '@auth0/auth0-react';
|
|
2
|
+
import { useEffect } from 'react';
|
|
3
|
+
import {
|
|
4
|
+
applyAuthApiKey,
|
|
5
|
+
clearAuthApiKey,
|
|
6
|
+
readAuthApiKeyFromUser,
|
|
7
|
+
} from 'utils/nyrisApiKey';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Keeps window.settings.apiKey in sync with Auth0 claim
|
|
11
|
+
* `https://nyris.io/authApiKey`.
|
|
12
|
+
* Must only render inside Auth0Provider when auth0.enabled is true.
|
|
13
|
+
*/
|
|
14
|
+
export function AuthApiKeySync() {
|
|
15
|
+
const { user, isAuthenticated, isLoading } = useAuth0();
|
|
16
|
+
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
if (isLoading) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (!isAuthenticated || !user) {
|
|
23
|
+
clearAuthApiKey();
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const authApiKey = readAuthApiKeyFromUser(
|
|
28
|
+
user as Record<string, unknown>,
|
|
29
|
+
);
|
|
30
|
+
applyAuthApiKey(authApiKey);
|
|
31
|
+
}, [user, isAuthenticated, isLoading]);
|
|
32
|
+
|
|
33
|
+
return null;
|
|
34
|
+
}
|
package/src/components/Cart.tsx
CHANGED
|
@@ -8,6 +8,9 @@ import { Send } from 'lucide-react';
|
|
|
8
8
|
import emailjs from '@emailjs/browser';
|
|
9
9
|
import { useAuth0 } from '@auth0/auth0-react';
|
|
10
10
|
import NoImage from '../common/assets/images/no-image.svg';
|
|
11
|
+
import { trackConversion } from 'services/Feedback';
|
|
12
|
+
import { CONVERSION_SUBTYPE } from 'services/feedbackSubtypes';
|
|
13
|
+
import useResultStore from 'stores/result/resultStore';
|
|
11
14
|
|
|
12
15
|
const EMAILJS_SERVICE_ID = 'service_zfsxshi';
|
|
13
16
|
const EMAILJS_TEMPLATE_ID = window.settings.cart?.emailTemplateId || '';
|
|
@@ -102,6 +105,42 @@ const Cart = () => {
|
|
|
102
105
|
const getQuantityDisplayValue = (sku: string, quantity: number) =>
|
|
103
106
|
quantityEdits[sku] ?? String(quantity || 1);
|
|
104
107
|
|
|
108
|
+
/**
|
|
109
|
+
* Sends one conversion per originating search request. Items remember the
|
|
110
|
+
* request they were added from; items without one fall back to the current
|
|
111
|
+
* request id.
|
|
112
|
+
*/
|
|
113
|
+
const trackCartConversion = (subtype: string) => {
|
|
114
|
+
const current = useResultStore.getState();
|
|
115
|
+
const groups = new Map<string, { sessionId?: string; parts: any[] }>();
|
|
116
|
+
|
|
117
|
+
cartParts.forEach((part: any) => {
|
|
118
|
+
const requestId = part.feedbackRequestId || current.requestId;
|
|
119
|
+
if (!requestId) return;
|
|
120
|
+
const group = groups.get(requestId) || {
|
|
121
|
+
sessionId: part.feedbackSessionId || current.sessionId,
|
|
122
|
+
parts: [] as any[],
|
|
123
|
+
};
|
|
124
|
+
group.parts.push(part);
|
|
125
|
+
groups.set(requestId, group);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
groups.forEach(({ sessionId, parts }, requestId) => {
|
|
129
|
+
trackConversion(
|
|
130
|
+
subtype,
|
|
131
|
+
{
|
|
132
|
+
skus: parts.map(part => part.sku),
|
|
133
|
+
positions: parts.map(part => part.feedbackPosition),
|
|
134
|
+
quantity: parts.reduce(
|
|
135
|
+
(total, part) => total + (part.quantity || 1),
|
|
136
|
+
0,
|
|
137
|
+
),
|
|
138
|
+
},
|
|
139
|
+
{ requestId, sessionId },
|
|
140
|
+
);
|
|
141
|
+
});
|
|
142
|
+
};
|
|
143
|
+
|
|
105
144
|
const generateCSVContent = (): string => {
|
|
106
145
|
const rows = [['Product Sku', 'Product Name', 'Quantity']];
|
|
107
146
|
cartParts.forEach((part: any) => {
|
|
@@ -124,6 +163,7 @@ const Cart = () => {
|
|
|
124
163
|
// clearSessionProducts();
|
|
125
164
|
|
|
126
165
|
toast.success('Your data has been downloaded.');
|
|
166
|
+
trackCartConversion(CONVERSION_SUBTYPE.DOWNLOAD_CART);
|
|
127
167
|
};
|
|
128
168
|
|
|
129
169
|
const sendCSVByEmail = async (
|
|
@@ -159,6 +199,7 @@ const Cart = () => {
|
|
|
159
199
|
);
|
|
160
200
|
|
|
161
201
|
toast.success('Parts list has been sent by email.');
|
|
202
|
+
trackCartConversion(CONVERSION_SUBTYPE.SHARE_CART);
|
|
162
203
|
setIsEmailInputVisible(false);
|
|
163
204
|
setUserEmail('');
|
|
164
205
|
} catch (error) {
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
fetchProductImageBase64,
|
|
13
13
|
getProductImageUrl,
|
|
14
14
|
} from 'utils/fetchProductImage';
|
|
15
|
+
import { getEffectiveApiKey } from 'utils/nyrisApiKey';
|
|
15
16
|
|
|
16
17
|
export function useChatAssistantLogic() {
|
|
17
18
|
const [inputValue, setInputValue] = useState('');
|
|
@@ -482,7 +483,7 @@ Each result includes a unique SKU in brackets. Use the result thumbnails and met
|
|
|
482
483
|
setCatalogSearching(true);
|
|
483
484
|
try {
|
|
484
485
|
const settings = (window as any).settings;
|
|
485
|
-
const nyrisKey =
|
|
486
|
+
const nyrisKey = getEffectiveApiKey();
|
|
486
487
|
const baseUrl = settings?.baseUrl || 'https://api.nyris.io';
|
|
487
488
|
|
|
488
489
|
store.setPreCatalogResults([...activeProducts]);
|
|
@@ -580,7 +581,7 @@ Each result includes a unique SKU in brackets. Use the result thumbnails and met
|
|
|
580
581
|
|
|
581
582
|
try {
|
|
582
583
|
const settings = (window as any).settings;
|
|
583
|
-
const nyrisKey =
|
|
584
|
+
const nyrisKey = getEffectiveApiKey();
|
|
584
585
|
const baseUrl = settings?.baseUrl || 'https://api.nyris.io';
|
|
585
586
|
|
|
586
587
|
let searchQuery = rawText;
|
|
@@ -7,8 +7,11 @@ import useResultStore from 'stores/result/resultStore';
|
|
|
7
7
|
import useRequestStore from 'stores/request/requestStore';
|
|
8
8
|
import { twMerge } from 'tailwind-merge';
|
|
9
9
|
import useUiStore from 'stores/ui/uiStore';
|
|
10
|
+
import useScopeStore from 'stores/scope/scopeStore';
|
|
11
|
+
import { useImageSearch } from 'hooks/useImageSearch';
|
|
10
12
|
|
|
11
13
|
export const GoBackButton = ({ className }: { className?: string }) => {
|
|
14
|
+
const { singleImageSearch } = useImageSearch();
|
|
12
15
|
const firstSearchResults = useResultStore(state => state.firstSearchResults);
|
|
13
16
|
const setFindApiProducts = useResultStore(state => state.setFindApiProducts);
|
|
14
17
|
const setImageAnalysis = useResultStore(state => state.setImageAnalysis);
|
|
@@ -27,12 +30,35 @@ export const GoBackButton = ({ className }: { className?: string }) => {
|
|
|
27
30
|
const setAlgoliaFilter = useRequestStore(state => state.setAlgoliaFilter);
|
|
28
31
|
|
|
29
32
|
const isFindApiLoading = useUiStore(state => state.isFindApiLoading);
|
|
33
|
+
const scopeId = useScopeStore(state => state.scopeId);
|
|
34
|
+
const scopeItemKeyCount = useScopeStore(state => state.itemKeys.length);
|
|
35
|
+
const machineScopeActive =
|
|
36
|
+
Boolean(window.settings.scopes?.scopeID) &&
|
|
37
|
+
Boolean(scopeId) &&
|
|
38
|
+
scopeItemKeyCount > 0;
|
|
30
39
|
|
|
31
40
|
const { refine: clearPostFilters } = useClearRefinements();
|
|
32
41
|
|
|
33
42
|
const { t } = useTranslation();
|
|
34
43
|
|
|
35
44
|
const onGoBack = () => {
|
|
45
|
+
// The saved snapshot may predate the machine scope, so search the original
|
|
46
|
+
// image again; find() applies the active scope.
|
|
47
|
+
if (machineScopeActive && firstSearchImage) {
|
|
48
|
+
setPreFilter(firstSearchPreFilter);
|
|
49
|
+
resetRegions();
|
|
50
|
+
clearPostFilters();
|
|
51
|
+
void singleImageSearch({
|
|
52
|
+
image: firstSearchImage,
|
|
53
|
+
settings: window.settings,
|
|
54
|
+
showFeedback: true,
|
|
55
|
+
newSearch: false,
|
|
56
|
+
compress: false,
|
|
57
|
+
clearPostFilter: true,
|
|
58
|
+
});
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
36
62
|
setFindApiProducts(firstSearchResults);
|
|
37
63
|
if (firstSearchImage) {
|
|
38
64
|
setRequestImages([firstSearchImage]);
|
|
@@ -58,9 +84,10 @@ export const GoBackButton = ({ className }: { className?: string }) => {
|
|
|
58
84
|
};
|
|
59
85
|
|
|
60
86
|
if (
|
|
61
|
-
|
|
87
|
+
!firstSearchImage ||
|
|
62
88
|
requestImages[0] === firstSearchImage ||
|
|
63
|
-
isFindApiLoading
|
|
89
|
+
isFindApiLoading ||
|
|
90
|
+
(!machineScopeActive && firstSearchResults.length === 0)
|
|
64
91
|
)
|
|
65
92
|
return <></>;
|
|
66
93
|
|
|
@@ -12,6 +12,7 @@ import LogoutModal from './LogoutModal';
|
|
|
12
12
|
import useResultStore from 'stores/result/resultStore';
|
|
13
13
|
import Cart from './Cart';
|
|
14
14
|
import useUiStore from 'stores/ui/uiStore';
|
|
15
|
+
import useScopeStore from 'stores/scope/scopeStore';
|
|
15
16
|
|
|
16
17
|
function Header() {
|
|
17
18
|
const { theme, auth0 } = window.settings;
|
|
@@ -20,6 +21,7 @@ function Header() {
|
|
|
20
21
|
|
|
21
22
|
const reset = useRequestStore(state => state.reset);
|
|
22
23
|
const resetResultStore = useResultStore(state => state.reset);
|
|
24
|
+
const resetScope = useScopeStore(state => state.resetScope);
|
|
23
25
|
const setSpecifications = useRequestStore(state => state.setSpecifications);
|
|
24
26
|
const showSidePanel = useUiStore(state => state.showSidePanel);
|
|
25
27
|
const setShowSidePanel = useUiStore(state => state.setShowSidePanel);
|
|
@@ -84,6 +86,7 @@ function Header() {
|
|
|
84
86
|
onClick={() => {
|
|
85
87
|
reset();
|
|
86
88
|
resetResultStore();
|
|
89
|
+
resetScope();
|
|
87
90
|
setSpecifications(null);
|
|
88
91
|
}}
|
|
89
92
|
className="pl-6"
|
|
@@ -2,13 +2,31 @@ import { useTranslation } from 'react-i18next';
|
|
|
2
2
|
import { useHitsPerPage, UseHitsPerPageProps } from 'react-instantsearch';
|
|
3
3
|
import PoweredBy from './PoweredBy';
|
|
4
4
|
import { twMerge } from 'tailwind-merge';
|
|
5
|
+
import useMachinePartsListingStore from 'stores/machinePartsListing/machinePartsListingStore';
|
|
5
6
|
|
|
6
|
-
|
|
7
|
+
interface Props extends UseHitsPerPageProps {
|
|
8
|
+
onFindApiPageSizeChange?: (pageSize: number) => void;
|
|
9
|
+
useFindApiPageSize?: boolean;
|
|
10
|
+
showPageSize?: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const HitsPerPage = ({
|
|
14
|
+
onFindApiPageSizeChange,
|
|
15
|
+
useFindApiPageSize = false,
|
|
16
|
+
showPageSize = true,
|
|
17
|
+
...props
|
|
18
|
+
}: Props) => {
|
|
7
19
|
const { items, refine } = useHitsPerPage(props);
|
|
8
20
|
const { value: currentValue } =
|
|
9
21
|
items.find(({ isRefined }) => isRefined)! || {};
|
|
10
|
-
|
|
22
|
+
const pageSize = useMachinePartsListingStore(state => state.pageSize);
|
|
11
23
|
const { t } = useTranslation();
|
|
24
|
+
const isAlgoliaEnabled = window.settings.algolia.enabled;
|
|
25
|
+
const controlAlgoliaPageSize = isAlgoliaEnabled && !useFindApiPageSize;
|
|
26
|
+
const options = controlAlgoliaPageSize ? items : props.items || items;
|
|
27
|
+
const selectedValue = controlAlgoliaPageSize
|
|
28
|
+
? String(currentValue)
|
|
29
|
+
: String(pageSize);
|
|
12
30
|
|
|
13
31
|
return (
|
|
14
32
|
<>
|
|
@@ -18,25 +36,44 @@ export const HitsPerPage = (props: UseHitsPerPageProps) => {
|
|
|
18
36
|
'shadow-ds-1',
|
|
19
37
|
)}
|
|
20
38
|
>
|
|
21
|
-
|
|
22
|
-
<div className="
|
|
23
|
-
|
|
39
|
+
{showPageSize && (
|
|
40
|
+
<div className="self-stretch pl-4 pr-6 border-r border-solid border-[#DDDEE7] justify-center items-center gap-1 flex">
|
|
41
|
+
<div className="text-[#3B3E5F] text-[13px] font-normal font-['Source Sans 3'] leading-none tracking-tight">
|
|
42
|
+
{t('Items per page')}:
|
|
43
|
+
</div>
|
|
44
|
+
<select
|
|
45
|
+
className="bg-transparent text-[13px] outline-none"
|
|
46
|
+
onChange={event => {
|
|
47
|
+
const nextPageSize = Number(event.target.value);
|
|
48
|
+
if (controlAlgoliaPageSize) {
|
|
49
|
+
refine(nextPageSize);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
onFindApiPageSizeChange?.(nextPageSize);
|
|
53
|
+
}}
|
|
54
|
+
value={selectedValue}
|
|
55
|
+
>
|
|
56
|
+
{options.map(item => (
|
|
57
|
+
<option key={item.value} value={item.value}>
|
|
58
|
+
{item.label}
|
|
59
|
+
</option>
|
|
60
|
+
))}
|
|
61
|
+
</select>
|
|
24
62
|
</div>
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
63
|
+
)}
|
|
64
|
+
{!isAlgoliaEnabled || !showPageSize ? (
|
|
65
|
+
<div
|
|
66
|
+
style={{
|
|
67
|
+
display: 'flex',
|
|
68
|
+
justifyContent: 'end',
|
|
69
|
+
marginLeft: 'auto',
|
|
29
70
|
}}
|
|
30
|
-
value={String(currentValue)}
|
|
31
71
|
>
|
|
32
|
-
{
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
</select>
|
|
38
|
-
</div>
|
|
39
|
-
{window.settings.showPoweredByNyris && <PoweredBy />}
|
|
72
|
+
{window.settings.showPoweredByNyris && <PoweredBy />}
|
|
73
|
+
</div>
|
|
74
|
+
) : (
|
|
75
|
+
window.settings.showPoweredByNyris && <PoweredBy />
|
|
76
|
+
)}
|
|
40
77
|
</div>
|
|
41
78
|
</>
|
|
42
79
|
);
|
|
@@ -12,6 +12,8 @@ import { DEFAULT_REGION } from '../constants';
|
|
|
12
12
|
import { useImageSearch } from 'hooks/useImageSearch';
|
|
13
13
|
import useRequestStore from 'stores/request/requestStore';
|
|
14
14
|
import useResultStore from 'stores/result/resultStore';
|
|
15
|
+
import { hasActiveMachineScope } from 'stores/scope/scopeStore';
|
|
16
|
+
import { firstMachinePartsListingText } from 'stores/machinePartsListing/machinePartsListingStore';
|
|
15
17
|
|
|
16
18
|
function ImagePreviewComponent({
|
|
17
19
|
showAdjustInfo = false,
|
|
@@ -52,7 +54,7 @@ function ImagePreviewComponent({
|
|
|
52
54
|
const detectedRegions = useResultStore(state => state.detectedRegions);
|
|
53
55
|
const resetResultStore = useResultStore(state => state.reset);
|
|
54
56
|
|
|
55
|
-
const { singleImageSearch } = useImageSearch();
|
|
57
|
+
const { singleImageSearch, searchMachinePartsListing } = useImageSearch();
|
|
56
58
|
|
|
57
59
|
const currentIndex = requestImages.length - 1;
|
|
58
60
|
|
|
@@ -86,6 +88,33 @@ function ImagePreviewComponent({
|
|
|
86
88
|
}, []);
|
|
87
89
|
|
|
88
90
|
const onImageRemove = () => {
|
|
91
|
+
if (hasActiveMachineScope()) {
|
|
92
|
+
const keyword = (useRequestStore.getState().query || '').trim();
|
|
93
|
+
resetRegions();
|
|
94
|
+
setRequestImages([]);
|
|
95
|
+
if (keyword) {
|
|
96
|
+
void singleImageSearch({
|
|
97
|
+
text: keyword,
|
|
98
|
+
settings,
|
|
99
|
+
showFeedback: false,
|
|
100
|
+
newSearch: false,
|
|
101
|
+
});
|
|
102
|
+
} else {
|
|
103
|
+
const text = firstMachinePartsListingText();
|
|
104
|
+
useRequestStore.getState().setQuery('');
|
|
105
|
+
useRequestStore.getState().setValueInput('');
|
|
106
|
+
if (text) {
|
|
107
|
+
void searchMachinePartsListing({
|
|
108
|
+
text,
|
|
109
|
+
showFeedback: false,
|
|
110
|
+
clearPostFilters: true,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
navigate('/result');
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
89
118
|
resetRegions();
|
|
90
119
|
setRequestImages([]);
|
|
91
120
|
|
|
@@ -106,6 +135,7 @@ function ImagePreviewComponent({
|
|
|
106
135
|
imageRegion: r,
|
|
107
136
|
showFeedback: true,
|
|
108
137
|
compress: false,
|
|
138
|
+
regionSelectedByUser: true,
|
|
109
139
|
}).then((res: any) => {
|
|
110
140
|
const specificationPrefilter =
|
|
111
141
|
res?.image_analysis?.specification?.prefilter_value || null;
|
|
@@ -12,9 +12,11 @@ import { useUploadSearch } from 'hooks/useUploadSearch';
|
|
|
12
12
|
function ImageUpload({
|
|
13
13
|
onCameraClick,
|
|
14
14
|
disableDisclaimer,
|
|
15
|
+
guideOpen,
|
|
15
16
|
}: {
|
|
16
17
|
onCameraClick?: () => void;
|
|
17
18
|
disableDisclaimer?: boolean;
|
|
19
|
+
guideOpen?: boolean;
|
|
18
20
|
}) {
|
|
19
21
|
const settings = window.settings;
|
|
20
22
|
const user = useAuth0().user;
|
|
@@ -31,13 +33,13 @@ function ImageUpload({
|
|
|
31
33
|
|
|
32
34
|
const showPreFilter = useMemo(() => {
|
|
33
35
|
if (settings.shouldUseUserMetadata && user) {
|
|
34
|
-
if (user['/user_metadata']
|
|
36
|
+
if (user['/user_metadata']?.value) {
|
|
35
37
|
setMetaFilter(user['/user_metadata'].value);
|
|
36
38
|
}
|
|
37
39
|
}
|
|
38
40
|
|
|
39
41
|
if (settings.shouldUseUserMetadata && user) {
|
|
40
|
-
if (settings.preFilterOption && !user['/user_metadata']
|
|
42
|
+
if (settings.preFilterOption && !user['/user_metadata']?.value) {
|
|
41
43
|
return true;
|
|
42
44
|
}
|
|
43
45
|
return false;
|
|
@@ -91,7 +93,16 @@ function ImageUpload({
|
|
|
91
93
|
e.target.value = '';
|
|
92
94
|
}}
|
|
93
95
|
/>
|
|
94
|
-
<Tooltip
|
|
96
|
+
<Tooltip
|
|
97
|
+
content={
|
|
98
|
+
guideOpen
|
|
99
|
+
? t('You can now upload an image to search.')
|
|
100
|
+
: t('Search with an image')
|
|
101
|
+
}
|
|
102
|
+
open={guideOpen ? true : undefined}
|
|
103
|
+
side="top"
|
|
104
|
+
sideOffset={10}
|
|
105
|
+
>
|
|
95
106
|
<label
|
|
96
107
|
className={twMerge(
|
|
97
108
|
'min-w-10 min-h-10 flex justify-center items-center cursor-pointer rounded-full bg-gray-100 desktop:bg-transparent hover:bg-gray-100',
|
|
@@ -14,6 +14,8 @@ import Tooltip from 'components/Tooltip/TooltipComponent';
|
|
|
14
14
|
|
|
15
15
|
import { useTranslation } from 'react-i18next';
|
|
16
16
|
import { createImage } from '../../services/visualSearch';
|
|
17
|
+
import { trackClick } from 'services/Feedback';
|
|
18
|
+
import { CLICK_SUBTYPE } from 'services/feedbackSubtypes';
|
|
17
19
|
interface Props {
|
|
18
20
|
requestImage: any;
|
|
19
21
|
selectedRegion: any;
|
|
@@ -123,6 +125,7 @@ export default function InquiryModal({
|
|
|
123
125
|
try {
|
|
124
126
|
await emailjs.send(serviceId, templateId, body);
|
|
125
127
|
ToastHelper.success(t('Request sent successfully'));
|
|
128
|
+
trackClick(CLICK_SUBTYPE.RAISE_INQUIRY);
|
|
126
129
|
} catch (error) {
|
|
127
130
|
toast(
|
|
128
131
|
() => {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useAuth0 } from '@auth0/auth0-react';
|
|
2
2
|
import { Dialog, DialogContent } from './Modal/Dialog';
|
|
3
|
+
import { clearAuthApiKey } from 'utils/nyrisApiKey';
|
|
3
4
|
|
|
4
5
|
function LogoutModal({
|
|
5
6
|
setShowLogoutModal,
|
|
@@ -41,6 +42,7 @@ function LogoutModal({
|
|
|
41
42
|
cursor: 'pointer',
|
|
42
43
|
}}
|
|
43
44
|
onClick={() => {
|
|
45
|
+
clearAuthApiKey();
|
|
44
46
|
logout({
|
|
45
47
|
logoutParams: { returnTo: window.location.origin },
|
|
46
48
|
});
|
|
@@ -1,19 +1,46 @@
|
|
|
1
1
|
import { usePagination } from 'react-instantsearch';
|
|
2
2
|
import { Icon } from '@nyris/nyris-react-components';
|
|
3
3
|
import { twMerge } from 'tailwind-merge';
|
|
4
|
+
import { visibleMachinePartsPageIndexes } from 'services/machinePartsPage';
|
|
4
5
|
|
|
5
6
|
export const Pagination = ({
|
|
6
7
|
isLoading,
|
|
7
8
|
className,
|
|
9
|
+
pageCount,
|
|
10
|
+
pageIndex = 0,
|
|
11
|
+
onPageChange,
|
|
8
12
|
}: {
|
|
9
13
|
isLoading: boolean;
|
|
10
14
|
className?: string;
|
|
15
|
+
pageCount?: number;
|
|
16
|
+
pageIndex?: number;
|
|
17
|
+
onPageChange?: (pageIndex: number) => void;
|
|
11
18
|
}) => {
|
|
12
|
-
const
|
|
13
|
-
|
|
19
|
+
const algoliaPagination = usePagination();
|
|
20
|
+
const isControlled = onPageChange !== undefined;
|
|
21
|
+
const currentRefinement = isControlled
|
|
22
|
+
? pageIndex
|
|
23
|
+
: algoliaPagination.currentRefinement;
|
|
24
|
+
const isFirstPage = isControlled
|
|
25
|
+
? currentRefinement <= 0
|
|
26
|
+
: algoliaPagination.isFirstPage;
|
|
27
|
+
const isLastPage = isControlled
|
|
28
|
+
? currentRefinement >= (pageCount ?? 1) - 1
|
|
29
|
+
: algoliaPagination.isLastPage;
|
|
30
|
+
const pages = isControlled
|
|
31
|
+
? visibleMachinePartsPageIndexes(currentRefinement, pageCount ?? 0)
|
|
32
|
+
: algoliaPagination.pages;
|
|
14
33
|
const previousPageIndex = currentRefinement - 1;
|
|
15
34
|
const nextPageIndex = currentRefinement + 1;
|
|
16
35
|
|
|
36
|
+
const selectPage = (page: number) => {
|
|
37
|
+
if (onPageChange) {
|
|
38
|
+
onPageChange(page);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
algoliaPagination.refine(page);
|
|
42
|
+
};
|
|
43
|
+
|
|
17
44
|
return (
|
|
18
45
|
<div
|
|
19
46
|
className={twMerge(
|
|
@@ -22,13 +49,14 @@ export const Pagination = ({
|
|
|
22
49
|
}`,
|
|
23
50
|
className,
|
|
24
51
|
)}
|
|
52
|
+
data-page-count={isControlled ? pageCount : undefined}
|
|
25
53
|
>
|
|
26
54
|
<div className="w-12 h-12 p-3.5 justify-center items-center flex">
|
|
27
55
|
<div
|
|
28
56
|
className="w-5 h-5 relative flex-col justify-start items-start flex cursor-pointer"
|
|
29
57
|
onClick={() => {
|
|
30
58
|
if (isFirstPage) return;
|
|
31
|
-
|
|
59
|
+
selectPage(previousPageIndex);
|
|
32
60
|
}}
|
|
33
61
|
>
|
|
34
62
|
<Icon name="caret_left" className="w-5 h-5" />
|
|
@@ -43,8 +71,7 @@ export const Pagination = ({
|
|
|
43
71
|
className="w-12 px-4 pt-[15px] flex-col justify-end items-center gap-[11px] inline-flex cursor-pointer"
|
|
44
72
|
onClick={event => {
|
|
45
73
|
event.preventDefault();
|
|
46
|
-
|
|
47
|
-
refine(page);
|
|
74
|
+
selectPage(page);
|
|
48
75
|
}}
|
|
49
76
|
>
|
|
50
77
|
<div className="text-center text-sm font-normal leading-[18px] tracking-tight">
|
|
@@ -62,7 +89,7 @@ export const Pagination = ({
|
|
|
62
89
|
className="w-5 h-5 relative flex-col justify-start items-start flex cursor-pointer"
|
|
63
90
|
onClick={() => {
|
|
64
91
|
if (isLastPage) return;
|
|
65
|
-
|
|
92
|
+
selectPage(nextPageIndex);
|
|
66
93
|
}}
|
|
67
94
|
>
|
|
68
95
|
<Icon name="caret_right" className="w-5 h-5" />
|
|
@@ -8,6 +8,8 @@ import { useRefinementList } from 'react-instantsearch';
|
|
|
8
8
|
import { Icon } from '@nyris/nyris-react-components';
|
|
9
9
|
import { useTranslation } from 'react-i18next';
|
|
10
10
|
import useScopeStore from 'stores/scope/scopeStore';
|
|
11
|
+
import { trackClick } from 'services/Feedback';
|
|
12
|
+
import { CLICK_SUBTYPE } from 'services/feedbackSubtypes';
|
|
11
13
|
|
|
12
14
|
function PostFilter({
|
|
13
15
|
attribute,
|
|
@@ -145,7 +147,12 @@ function PostFilter({
|
|
|
145
147
|
<input
|
|
146
148
|
type="checkbox"
|
|
147
149
|
checked={item.isRefined}
|
|
148
|
-
onChange={() =>
|
|
150
|
+
onChange={() => {
|
|
151
|
+
if (!item.isRefined) {
|
|
152
|
+
trackClick(CLICK_SUBTYPE.FILTER_APPLIED);
|
|
153
|
+
}
|
|
154
|
+
refine(item.value);
|
|
155
|
+
}}
|
|
149
156
|
className="cursor-pointer"
|
|
150
157
|
/>
|
|
151
158
|
<span className="text-xs font-normal pl-2 pr-1">
|