@nyris/nyris-webapp 0.3.35 → 0.3.37
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 +13 -13
- package/build/index.html +1 -1
- package/build/{precache-manifest.375ac411683570ee1df5aea6de03409d.js → precache-manifest.c69fca63f2d97ff08892d42e2b903f20.js} +14 -14
- package/build/service-worker.js +1 -1
- package/build/static/css/main.41fb4769.chunk.css +2 -0
- package/build/static/css/main.41fb4769.chunk.css.map +1 -0
- package/build/static/js/2.00161cbb.chunk.js +3 -0
- package/build/static/js/2.00161cbb.chunk.js.map +1 -0
- package/build/static/js/main.09a533bd.chunk.js +3 -0
- package/build/static/js/main.09a533bd.chunk.js.map +1 -0
- package/build/static/media/error.48b946a9.svg +3 -0
- package/package.json +3 -3
- package/public/index.html +13 -0
- package/src/Store/search/Search.ts +4 -4
- package/src/Store/search/search.initialState.ts +1 -1
- package/src/Store/search/types.ts +1 -1
- package/src/common/assets/icons/arrow_left.svg +3 -0
- package/src/common/assets/icons/arrow_right.svg +3 -0
- package/src/common/assets/icons/error.svg +3 -0
- package/src/components/AppMobile.tsx +111 -0
- package/src/components/DragDropFile.tsx +5 -4
- package/src/components/FooterMobile.tsx +8 -2
- package/src/components/Header.tsx +2 -1
- package/src/components/HeaderMobile.tsx +5 -3
- package/src/components/ImageCaptureHelpModal.tsx +7 -1
- package/src/components/ImagePreviewMobile.tsx +87 -0
- package/src/components/Layout.tsx +32 -92
- package/src/components/ProductList/index.tsx +4 -1
- package/src/components/RfqBanner.tsx +120 -0
- package/src/components/SidePanel.tsx +147 -0
- package/src/components/appMobile.scss +147 -142
- package/src/components/drawer/cameraCustom.tsx +18 -5
- package/src/components/input/inputSearch.tsx +12 -7
- package/src/components/modal/DefaultModal.tsx +1 -1
- package/src/components/pre-filter/index.tsx +108 -51
- package/src/components/results/ItemResult.tsx +1 -1
- package/src/components/rfq/RfqModal.tsx +262 -0
- package/src/helpers/ToastHelper.ts +10 -0
- package/src/helpers/getCroppedCanvas.ts +26 -0
- package/src/hooks/useFilteredRegions.ts +37 -0
- package/src/index.css +0 -4
- package/src/page/landingPage/AppMD.tsx +0 -11
- package/src/page/landingPage/AppMobile.tsx +1 -0
- package/src/page/landingPage/common.scss +41 -58
- package/src/page/result/index.tsx +256 -309
- package/src/translations.ts +2 -2
- package/src/types.ts +1 -0
- package/src/utils.ts +0 -1
- package/build/static/css/main.f5a1c730.chunk.css +0 -2
- package/build/static/css/main.f5a1c730.chunk.css.map +0 -1
- package/build/static/js/2.724f4cba.chunk.js +0 -3
- package/build/static/js/2.724f4cba.chunk.js.map +0 -1
- package/build/static/js/main.f579fa9f.chunk.js +0 -3
- package/build/static/js/main.f579fa9f.chunk.js.map +0 -1
- package/build/static/media/support3.4a17f96e.svg +0 -3
- /package/build/static/js/{2.724f4cba.chunk.js.LICENSE.txt → 2.00161cbb.chunk.js.LICENSE.txt} +0 -0
- /package/build/static/js/{main.f579fa9f.chunk.js.LICENSE.txt → main.09a533bd.chunk.js.LICENSE.txt} +0 -0
|
@@ -35,7 +35,7 @@ const SearchBox = (props: any) => {
|
|
|
35
35
|
// const containerRefInputMobile = useRef<HTMLDivElement>(null);
|
|
36
36
|
const stateGlobal = useAppSelector(state => state);
|
|
37
37
|
const { search, settings } = stateGlobal;
|
|
38
|
-
const { imageThumbSearchInput,
|
|
38
|
+
const { imageThumbSearchInput, preFilter } = search;
|
|
39
39
|
const focusInp: any = useRef<HTMLDivElement | null>(null);
|
|
40
40
|
const history = useHistory();
|
|
41
41
|
const [valueInput, setValueInput] = useState<string>('');
|
|
@@ -96,13 +96,14 @@ const SearchBox = (props: any) => {
|
|
|
96
96
|
let payload: any;
|
|
97
97
|
let filters: any[] = [];
|
|
98
98
|
let region: RectCoords | undefined;
|
|
99
|
+
|
|
99
100
|
dispatch(setImageSearchInput(URL.createObjectURL(fs[0])));
|
|
100
101
|
let image = await createImage(fs[0]);
|
|
101
102
|
dispatch(setRequestImage(image));
|
|
102
|
-
const
|
|
103
|
+
const preFilterValues = [
|
|
103
104
|
{
|
|
104
105
|
key: settings.visualSearchFilterKey,
|
|
105
|
-
values: [
|
|
106
|
+
values: Object.keys(preFilter) as string[],
|
|
106
107
|
},
|
|
107
108
|
];
|
|
108
109
|
|
|
@@ -116,7 +117,7 @@ const SearchBox = (props: any) => {
|
|
|
116
117
|
return findByImage({
|
|
117
118
|
image,
|
|
118
119
|
settings,
|
|
119
|
-
filters:
|
|
120
|
+
filters: !isEmpty(preFilter) ? preFilterValues : undefined,
|
|
120
121
|
region,
|
|
121
122
|
})
|
|
122
123
|
.then((res: any) => {
|
|
@@ -156,7 +157,11 @@ const SearchBox = (props: any) => {
|
|
|
156
157
|
<form noValidate action="" role="search">
|
|
157
158
|
<Box className="box-inp">
|
|
158
159
|
<Tooltip
|
|
159
|
-
title={
|
|
160
|
+
title={
|
|
161
|
+
!isEmpty(preFilter)
|
|
162
|
+
? Object.keys(preFilter).join(', ')
|
|
163
|
+
: 'Add pre-filter'
|
|
164
|
+
}
|
|
160
165
|
placement="top"
|
|
161
166
|
arrow={true}
|
|
162
167
|
disableHoverListener={!settings.preFilterOption}
|
|
@@ -176,7 +181,7 @@ const SearchBox = (props: any) => {
|
|
|
176
181
|
<div
|
|
177
182
|
className="icon-hover"
|
|
178
183
|
style={{
|
|
179
|
-
...(
|
|
184
|
+
...(!isEmpty(preFilter)
|
|
180
185
|
? {
|
|
181
186
|
backgroundColor: `${settings.theme?.secondaryColor}26`,
|
|
182
187
|
}
|
|
@@ -189,7 +194,7 @@ const SearchBox = (props: any) => {
|
|
|
189
194
|
{!settings.preFilterOption && (
|
|
190
195
|
<IconSearch width={16} height={16} />
|
|
191
196
|
)}
|
|
192
|
-
{
|
|
197
|
+
{!isEmpty(preFilter) && (
|
|
193
198
|
<div
|
|
194
199
|
style={{
|
|
195
200
|
position: 'absolute',
|
|
@@ -37,7 +37,7 @@ function DefaultModal(props: Props): JSX.Element {
|
|
|
37
37
|
>
|
|
38
38
|
<div
|
|
39
39
|
className={classNameComponentChild}
|
|
40
|
-
style={{ overflowY: '
|
|
40
|
+
style={{ overflowY: 'auto', maxHeight: '95vh', borderRadius: 12 }}
|
|
41
41
|
>
|
|
42
42
|
{children}
|
|
43
43
|
</div>
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { Box, Button, Tooltip, Typography } from '@material-ui/core';
|
|
2
|
-
import React, { useEffect, useState } from 'react';
|
|
2
|
+
import React, { useEffect, useMemo, useState } from 'react';
|
|
3
3
|
import CloseIcon from '@material-ui/icons/Close';
|
|
4
4
|
import IconSearch from 'common/assets/icons/icon_search.svg';
|
|
5
5
|
import { getFilters, searchFilters } from 'services/filter';
|
|
6
6
|
import { useAppDispatch, useAppSelector } from 'Store/Store';
|
|
7
|
-
import {
|
|
7
|
+
import { setPreFilter } from 'Store/search/Search';
|
|
8
8
|
import { useMediaQuery } from 'react-responsive';
|
|
9
|
-
import { isEmpty } from 'lodash';
|
|
9
|
+
import { isEmpty, pickBy } from 'lodash';
|
|
10
10
|
import { Skeleton } from '@material-ui/lab';
|
|
11
11
|
import { truncateString } from 'helpers/truncateString';
|
|
12
12
|
|
|
@@ -14,21 +14,35 @@ interface Props {
|
|
|
14
14
|
handleClose?: any;
|
|
15
15
|
// onChangeKeyFilter?: any;
|
|
16
16
|
}
|
|
17
|
-
|
|
17
|
+
const maxFilter = 10;
|
|
18
18
|
function PreFilterComponent(props: Props) {
|
|
19
19
|
const { handleClose } = props;
|
|
20
20
|
const dispatch = useAppDispatch();
|
|
21
21
|
const stateGlobal = useAppSelector(state => state);
|
|
22
22
|
const {
|
|
23
23
|
settings,
|
|
24
|
-
search: {
|
|
24
|
+
search: { preFilter: keyFilterState },
|
|
25
25
|
} = stateGlobal;
|
|
26
26
|
const [resultFilter, setResultFilter] = useState<any>([]);
|
|
27
|
-
const [keyFilter, setKeyFilter] = useState<string
|
|
27
|
+
const [keyFilter, setKeyFilter] = useState<Record<string, boolean>>(
|
|
28
|
+
keyFilterState || {},
|
|
29
|
+
);
|
|
30
|
+
|
|
28
31
|
const [isLoading, setLoading] = useState<boolean>(false);
|
|
29
32
|
const [columns, setColumns] = useState<number>(0);
|
|
30
33
|
const isMobile = useMediaQuery({ query: '(max-width: 776px)' });
|
|
31
34
|
|
|
35
|
+
const selectedFilter = useMemo(
|
|
36
|
+
() =>
|
|
37
|
+
Object.keys(keyFilter).reduce((count, key) => {
|
|
38
|
+
if (keyFilter[key] === true) {
|
|
39
|
+
return count + 1;
|
|
40
|
+
}
|
|
41
|
+
return count;
|
|
42
|
+
}, 0),
|
|
43
|
+
[keyFilter],
|
|
44
|
+
);
|
|
45
|
+
|
|
32
46
|
const getDataFilterDesktop = async () => {
|
|
33
47
|
setLoading(true);
|
|
34
48
|
const dataResultFilter = getFilters(1000, settings)
|
|
@@ -73,7 +87,6 @@ function PreFilterComponent(props: Props) {
|
|
|
73
87
|
settings,
|
|
74
88
|
)
|
|
75
89
|
.then(res => {
|
|
76
|
-
// console.log("res", res);
|
|
77
90
|
if (res.length > 0) {
|
|
78
91
|
setResultFilter({ [res[0][0].toLocaleUpperCase()]: res });
|
|
79
92
|
if (res.length <= 20) setColumns(1);
|
|
@@ -92,7 +105,7 @@ function PreFilterComponent(props: Props) {
|
|
|
92
105
|
};
|
|
93
106
|
|
|
94
107
|
const onHandlerSubmitData = () => {
|
|
95
|
-
dispatch(
|
|
108
|
+
dispatch(setPreFilter(pickBy(keyFilter, value => !!value)));
|
|
96
109
|
handleClose();
|
|
97
110
|
};
|
|
98
111
|
|
|
@@ -119,7 +132,7 @@ function PreFilterComponent(props: Props) {
|
|
|
119
132
|
color: '#000',
|
|
120
133
|
fontSize: '24px',
|
|
121
134
|
fontWeight: 700,
|
|
122
|
-
paddingLeft:
|
|
135
|
+
paddingLeft: '14px',
|
|
123
136
|
marginBottom: isMobile ? '0px' : '-8px',
|
|
124
137
|
marginTop: isMobile ? '0px' : '24px',
|
|
125
138
|
}}
|
|
@@ -133,7 +146,6 @@ function PreFilterComponent(props: Props) {
|
|
|
133
146
|
</div>
|
|
134
147
|
<Box
|
|
135
148
|
className="box-top"
|
|
136
|
-
style={isMobile ? { padding: 0, marginTop: '16px' } : undefined}
|
|
137
149
|
display={'flex'}
|
|
138
150
|
justifyContent={'space-between'}
|
|
139
151
|
alignItems={'center'}
|
|
@@ -144,34 +156,21 @@ function PreFilterComponent(props: Props) {
|
|
|
144
156
|
justifyItems={'center'}
|
|
145
157
|
style={isMobile ? { width: '100%' } : undefined}
|
|
146
158
|
>
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
</Box>
|
|
163
|
-
)}
|
|
164
|
-
|
|
165
|
-
{keyFilter && !isMobile && (
|
|
166
|
-
<Box display={'flex'} className="box-keyFilter">
|
|
167
|
-
<Typography className="keyFilter max-line-1">
|
|
168
|
-
{keyFilter}
|
|
169
|
-
</Typography>
|
|
170
|
-
<Button style={{ padding: 0 }} onClick={() => setKeyFilter('')}>
|
|
171
|
-
<CloseIcon style={{ fontSize: 12, color: '#2B2C46' }} />
|
|
172
|
-
</Button>
|
|
173
|
-
</Box>
|
|
174
|
-
)}
|
|
159
|
+
<Box
|
|
160
|
+
className="icon-search"
|
|
161
|
+
style={{ marginRight: 11 }}
|
|
162
|
+
display={'flex'}
|
|
163
|
+
justifyContent={'center'}
|
|
164
|
+
alignItems={'center'}
|
|
165
|
+
>
|
|
166
|
+
<img
|
|
167
|
+
style={{ maxWidth: 'fit-content' }}
|
|
168
|
+
src={IconSearch}
|
|
169
|
+
alt=""
|
|
170
|
+
width={18}
|
|
171
|
+
height={18}
|
|
172
|
+
/>
|
|
173
|
+
</Box>
|
|
175
174
|
|
|
176
175
|
<input
|
|
177
176
|
className="input-search-filter"
|
|
@@ -183,19 +182,59 @@ function PreFilterComponent(props: Props) {
|
|
|
183
182
|
</Box>
|
|
184
183
|
</Box>
|
|
185
184
|
|
|
186
|
-
{keyFilter &&
|
|
187
|
-
<Box
|
|
185
|
+
{!isEmpty(keyFilter) && selectedFilter > 0 && (
|
|
186
|
+
<Box
|
|
187
|
+
style={{
|
|
188
|
+
margin: '10px 16px 10px 16px',
|
|
189
|
+
display: 'flex',
|
|
190
|
+
justifyContent: 'space-between',
|
|
191
|
+
}}
|
|
192
|
+
>
|
|
188
193
|
<Box
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
194
|
+
style={{
|
|
195
|
+
display: 'flex',
|
|
196
|
+
flexWrap: 'wrap',
|
|
197
|
+
rowGap: '8px',
|
|
198
|
+
columnGap: '8px',
|
|
199
|
+
alignItems: 'baseline',
|
|
200
|
+
fontSize: '12px',
|
|
201
|
+
marginBottom: '4px',
|
|
202
|
+
}}
|
|
192
203
|
>
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
204
|
+
{Object.keys(keyFilter).map((key, index) => {
|
|
205
|
+
if (!keyFilter[key]) return <></>;
|
|
206
|
+
return (
|
|
207
|
+
<Box
|
|
208
|
+
key={index}
|
|
209
|
+
className="box-keyFilter"
|
|
210
|
+
style={{ display: 'flex', height: 'fit-content' }}
|
|
211
|
+
>
|
|
212
|
+
<Typography className="keyFilter">{key}</Typography>
|
|
213
|
+
<Button
|
|
214
|
+
style={{ padding: 0 }}
|
|
215
|
+
onClick={() => setKeyFilter({ ...keyFilter, [key]: false })}
|
|
216
|
+
>
|
|
217
|
+
<CloseIcon style={{ fontSize: 12, color: '#2B2C46' }} />
|
|
218
|
+
</Button>
|
|
219
|
+
</Box>
|
|
220
|
+
);
|
|
221
|
+
})}
|
|
222
|
+
<p
|
|
223
|
+
style={{ fontWeight: 'bold', color: '#000' }}
|
|
224
|
+
>{`${selectedFilter}/${maxFilter}`}</p>
|
|
225
|
+
<Box
|
|
226
|
+
style={{
|
|
227
|
+
color: '#E31B5D',
|
|
228
|
+
fontSize: '12px',
|
|
229
|
+
cursor: 'pointer',
|
|
230
|
+
marginLeft: '12px',
|
|
231
|
+
}}
|
|
232
|
+
onClick={() => {
|
|
233
|
+
setKeyFilter({});
|
|
234
|
+
}}
|
|
235
|
+
>
|
|
236
|
+
Clear all
|
|
237
|
+
</Box>
|
|
199
238
|
</Box>
|
|
200
239
|
</Box>
|
|
201
240
|
)}
|
|
@@ -204,7 +243,10 @@ function PreFilterComponent(props: Props) {
|
|
|
204
243
|
height={'100%'}
|
|
205
244
|
style={
|
|
206
245
|
isMobile
|
|
207
|
-
? {
|
|
246
|
+
? {
|
|
247
|
+
columnCount: 1,
|
|
248
|
+
marginBottom: keyFilter ? '50px' : '0px',
|
|
249
|
+
}
|
|
208
250
|
: columns <= 4
|
|
209
251
|
? { columnCount: columns, height: '100%', paddingBottom: 20 }
|
|
210
252
|
: { columnCount: 4, paddingBottom: 20 }
|
|
@@ -248,12 +290,22 @@ function PreFilterComponent(props: Props) {
|
|
|
248
290
|
minHeight: '20px',
|
|
249
291
|
color: '#2B2C46',
|
|
250
292
|
width: '100%',
|
|
293
|
+
maxWidth: 'fit-content',
|
|
251
294
|
overflow: 'hidden',
|
|
252
295
|
textOverflow: 'ellipsis',
|
|
253
296
|
whiteSpace: 'nowrap',
|
|
297
|
+
backgroundColor: keyFilter[item] ? '#E9E9EC' : '',
|
|
298
|
+
borderRadius: 8,
|
|
299
|
+
paddingLeft: '8px',
|
|
300
|
+
paddingRight: '8px',
|
|
254
301
|
}}
|
|
255
302
|
onClick={() => {
|
|
256
|
-
|
|
303
|
+
if (selectedFilter < maxFilter) {
|
|
304
|
+
setKeyFilter({
|
|
305
|
+
...keyFilter,
|
|
306
|
+
[item]: !keyFilter[item],
|
|
307
|
+
});
|
|
308
|
+
}
|
|
257
309
|
}}
|
|
258
310
|
>
|
|
259
311
|
{truncateString(item, !isMobile ? 35 : 35)}
|
|
@@ -297,10 +349,13 @@ function PreFilterComponent(props: Props) {
|
|
|
297
349
|
className="button-left"
|
|
298
350
|
style={{
|
|
299
351
|
width: '50%',
|
|
352
|
+
height: '64px',
|
|
300
353
|
backgroundColor: '#000000',
|
|
301
354
|
color: '#fff',
|
|
302
355
|
borderRadius: 0,
|
|
303
356
|
justifyContent: 'flex-start',
|
|
357
|
+
textTransform: 'none',
|
|
358
|
+
paddingLeft: '16px',
|
|
304
359
|
}}
|
|
305
360
|
onClick={() => handleClose()}
|
|
306
361
|
>
|
|
@@ -314,6 +369,8 @@ function PreFilterComponent(props: Props) {
|
|
|
314
369
|
color: '#fff',
|
|
315
370
|
borderRadius: 0,
|
|
316
371
|
justifyContent: 'flex-start',
|
|
372
|
+
textTransform: 'none',
|
|
373
|
+
paddingLeft: '16px',
|
|
317
374
|
}}
|
|
318
375
|
onClick={() => onHandlerSubmitData()}
|
|
319
376
|
>
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import DefaultModal from 'components/modal/DefaultModal';
|
|
2
|
+
import React, { useEffect, useState } from 'react';
|
|
3
|
+
import CloseIcon from '@material-ui/icons/Close';
|
|
4
|
+
import { getCroppedCanvas } from 'helpers/getCroppedCanvas';
|
|
5
|
+
import emailjs from '@emailjs/browser';
|
|
6
|
+
import { ToastHelper } from 'helpers/ToastHelper';
|
|
7
|
+
import { isUndefined } from 'lodash';
|
|
8
|
+
import { TextareaAutosize } from '@material-ui/core';
|
|
9
|
+
import toast from 'react-hot-toast';
|
|
10
|
+
import { ReactComponent as ErrorIcon } from 'common/assets/icons/error.svg';
|
|
11
|
+
interface Props {
|
|
12
|
+
requestImage: any;
|
|
13
|
+
selectedRegion: any;
|
|
14
|
+
setIsRfqModalOpen: any;
|
|
15
|
+
isRfqModalOpen?: any;
|
|
16
|
+
setRfqStatus: any;
|
|
17
|
+
}
|
|
18
|
+
// eslint-disable-next-line
|
|
19
|
+
const emailRegex = /.+\@.+\..+$/;
|
|
20
|
+
|
|
21
|
+
const getErrorMessage = (error: any) => {
|
|
22
|
+
switch (error.status) {
|
|
23
|
+
case 400:
|
|
24
|
+
return 'Your email could not be sent, please try again or send an email to support@nyris.io';
|
|
25
|
+
case 421:
|
|
26
|
+
case 450:
|
|
27
|
+
case 451:
|
|
28
|
+
case 452:
|
|
29
|
+
return "Email delivery failed. Rest assured, we're continuously attempting to send it for you. Alternatively, you can forward the email to support@nyris.io";
|
|
30
|
+
default:
|
|
31
|
+
return 'Your email could not be sent, please try again or send an email to support@nyris.io';
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export default function RfqModal({
|
|
36
|
+
requestImage,
|
|
37
|
+
selectedRegion,
|
|
38
|
+
setIsRfqModalOpen,
|
|
39
|
+
isRfqModalOpen,
|
|
40
|
+
setRfqStatus,
|
|
41
|
+
}: Props) {
|
|
42
|
+
const [email, setEmail] = useState('');
|
|
43
|
+
const [emailValid, setEmailValid] = useState<boolean | undefined>(undefined);
|
|
44
|
+
|
|
45
|
+
const [information, setInformation] = useState('');
|
|
46
|
+
useEffect(() => emailjs.init('SMGihPnuEGcYLm0V4'), []);
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
if (email)
|
|
49
|
+
emailRegex.test(email) ? setEmailValid(true) : setEmailValid(false);
|
|
50
|
+
}, [email]);
|
|
51
|
+
|
|
52
|
+
const handleRfq = async (e: { preventDefault: () => void }) => {
|
|
53
|
+
e.preventDefault();
|
|
54
|
+
const { canvas }: any = requestImage;
|
|
55
|
+
const croppedImage = getCroppedCanvas(canvas, selectedRegion);
|
|
56
|
+
const serviceId = 'service_zfsxshi';
|
|
57
|
+
const templateId = 'template_jlgc9le';
|
|
58
|
+
setIsRfqModalOpen(false);
|
|
59
|
+
try {
|
|
60
|
+
setRfqStatus('loading');
|
|
61
|
+
await emailjs.send(serviceId, templateId, {
|
|
62
|
+
email_id: email.trim(),
|
|
63
|
+
information_text: information,
|
|
64
|
+
request_image: croppedImage?.toDataURL(),
|
|
65
|
+
});
|
|
66
|
+
setRfqStatus('sent');
|
|
67
|
+
ToastHelper.success('Request sent successfully');
|
|
68
|
+
} catch (error) {
|
|
69
|
+
setRfqStatus('inactive');
|
|
70
|
+
|
|
71
|
+
toast(
|
|
72
|
+
t => {
|
|
73
|
+
return (
|
|
74
|
+
<div
|
|
75
|
+
style={{
|
|
76
|
+
display: 'flex',
|
|
77
|
+
flexDirection: 'column',
|
|
78
|
+
fontSize: '14px',
|
|
79
|
+
width: '294px',
|
|
80
|
+
}}
|
|
81
|
+
>
|
|
82
|
+
<span style={{ fontWeight: 'bold' }}>Email not sent</span>
|
|
83
|
+
<span>{getErrorMessage(error)}</span>
|
|
84
|
+
<a
|
|
85
|
+
href={`mailto:support@nyris.io?subject=Request for quotation&body=${information}`}
|
|
86
|
+
style={{
|
|
87
|
+
padding: '8px 16px 8px 16px',
|
|
88
|
+
border: '1px solid #000',
|
|
89
|
+
marginTop: '16px',
|
|
90
|
+
backgroundColor: 'transparent',
|
|
91
|
+
color: '#000',
|
|
92
|
+
cursor: 'pointer',
|
|
93
|
+
width: 'fit-content',
|
|
94
|
+
}}
|
|
95
|
+
>
|
|
96
|
+
support@nyris.io
|
|
97
|
+
</a>
|
|
98
|
+
</div>
|
|
99
|
+
);
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
duration: 5000,
|
|
103
|
+
style: {
|
|
104
|
+
background: '#FFE5EF',
|
|
105
|
+
color: '#000000',
|
|
106
|
+
maxWidth: '400px',
|
|
107
|
+
},
|
|
108
|
+
icon: (
|
|
109
|
+
<div style={{ minWidth: '20px', minHeight: '20px' }}>
|
|
110
|
+
<ErrorIcon />
|
|
111
|
+
</div>
|
|
112
|
+
),
|
|
113
|
+
},
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
setIsRfqModalOpen(false);
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
return (
|
|
120
|
+
<DefaultModal
|
|
121
|
+
openModal={isRfqModalOpen}
|
|
122
|
+
handleClose={(e: any) => {
|
|
123
|
+
setIsRfqModalOpen(false);
|
|
124
|
+
}}
|
|
125
|
+
>
|
|
126
|
+
<div
|
|
127
|
+
style={{
|
|
128
|
+
display: 'flex',
|
|
129
|
+
width: '378px',
|
|
130
|
+
flexDirection: 'column',
|
|
131
|
+
backgroundColor: '#fff',
|
|
132
|
+
}}
|
|
133
|
+
>
|
|
134
|
+
<div
|
|
135
|
+
style={{
|
|
136
|
+
padding: '16px',
|
|
137
|
+
display: 'flex',
|
|
138
|
+
flexDirection: 'column',
|
|
139
|
+
}}
|
|
140
|
+
>
|
|
141
|
+
<CloseIcon
|
|
142
|
+
style={{
|
|
143
|
+
fontSize: 16,
|
|
144
|
+
color: 'black',
|
|
145
|
+
alignSelf: 'flex-end',
|
|
146
|
+
cursor: 'pointer',
|
|
147
|
+
}}
|
|
148
|
+
onClick={() => setIsRfqModalOpen(false)}
|
|
149
|
+
/>
|
|
150
|
+
<p
|
|
151
|
+
style={{
|
|
152
|
+
color: '#2B2C46',
|
|
153
|
+
fontSize: '20px',
|
|
154
|
+
fontWeight: 'bold',
|
|
155
|
+
}}
|
|
156
|
+
>
|
|
157
|
+
Submit your image for quotation
|
|
158
|
+
</p>
|
|
159
|
+
</div>
|
|
160
|
+
<div
|
|
161
|
+
style={{
|
|
162
|
+
padding: '16px',
|
|
163
|
+
backgroundColor: '#F3F3F5',
|
|
164
|
+
display: 'flex',
|
|
165
|
+
justifyContent: 'center',
|
|
166
|
+
}}
|
|
167
|
+
>
|
|
168
|
+
<img
|
|
169
|
+
src={getCroppedCanvas(
|
|
170
|
+
requestImage?.canvas,
|
|
171
|
+
selectedRegion,
|
|
172
|
+
)?.toDataURL()}
|
|
173
|
+
alt="request_image"
|
|
174
|
+
style={{ maxHeight: '200px' }}
|
|
175
|
+
/>
|
|
176
|
+
</div>
|
|
177
|
+
<div
|
|
178
|
+
style={{
|
|
179
|
+
padding: '0px 16px 16px 16px',
|
|
180
|
+
backgroundColor: '#F3F3F5',
|
|
181
|
+
display: 'flex',
|
|
182
|
+
flexDirection: 'column',
|
|
183
|
+
rowGap: '16px',
|
|
184
|
+
}}
|
|
185
|
+
>
|
|
186
|
+
<div>
|
|
187
|
+
<p style={{ fontSize: '12px', color: '#2B2C46' }}>
|
|
188
|
+
Your email (required)
|
|
189
|
+
</p>
|
|
190
|
+
<input
|
|
191
|
+
value={email}
|
|
192
|
+
onChange={e => setEmail(e.currentTarget.value.trim())}
|
|
193
|
+
style={{
|
|
194
|
+
width: '100%',
|
|
195
|
+
border: 'none',
|
|
196
|
+
height: '32px',
|
|
197
|
+
padding: '8px 16px 8px 16px',
|
|
198
|
+
}}
|
|
199
|
+
/>
|
|
200
|
+
{!emailValid && !isUndefined(emailValid) && (
|
|
201
|
+
<p style={{ color: 'red', fontSize: '12px', paddingTop: '8px' }}>
|
|
202
|
+
Please enter a valid email.
|
|
203
|
+
</p>
|
|
204
|
+
)}
|
|
205
|
+
</div>
|
|
206
|
+
<div>
|
|
207
|
+
<p style={{ fontSize: '12px', color: '#2B2C46' }}>
|
|
208
|
+
Additional information
|
|
209
|
+
</p>
|
|
210
|
+
<TextareaAutosize
|
|
211
|
+
value={information}
|
|
212
|
+
onChange={e => setInformation(e.currentTarget.value)}
|
|
213
|
+
style={{
|
|
214
|
+
width: '100%',
|
|
215
|
+
border: 'none',
|
|
216
|
+
maxWidth: '346px',
|
|
217
|
+
padding: '8px 16px 8px 16px',
|
|
218
|
+
}}
|
|
219
|
+
/>
|
|
220
|
+
</div>
|
|
221
|
+
</div>
|
|
222
|
+
<div style={{ display: 'flex' }}>
|
|
223
|
+
<button
|
|
224
|
+
style={{
|
|
225
|
+
height: '66px',
|
|
226
|
+
display: 'flex',
|
|
227
|
+
alignItems: 'center',
|
|
228
|
+
width: '50%',
|
|
229
|
+
backgroundColor: '#4B4B4A',
|
|
230
|
+
color: 'white',
|
|
231
|
+
fontSize: '14px',
|
|
232
|
+
paddingLeft: '16px',
|
|
233
|
+
border: 'none',
|
|
234
|
+
cursor: 'pointer',
|
|
235
|
+
}}
|
|
236
|
+
onClick={() => setIsRfqModalOpen(false)}
|
|
237
|
+
>
|
|
238
|
+
Cancel
|
|
239
|
+
</button>
|
|
240
|
+
<button
|
|
241
|
+
style={{
|
|
242
|
+
height: '66px',
|
|
243
|
+
display: 'flex',
|
|
244
|
+
alignItems: 'center',
|
|
245
|
+
width: '50%',
|
|
246
|
+
backgroundColor: emailValid ? '#4DBE51' : '#E9E9EC',
|
|
247
|
+
color: emailValid ? '#fff' : '#AAABB5',
|
|
248
|
+
fontSize: '14px',
|
|
249
|
+
paddingLeft: '16px',
|
|
250
|
+
border: 'none',
|
|
251
|
+
cursor: emailValid ? 'pointer' : 'normal',
|
|
252
|
+
}}
|
|
253
|
+
disabled={!emailValid}
|
|
254
|
+
onClick={handleRfq}
|
|
255
|
+
>
|
|
256
|
+
Send
|
|
257
|
+
</button>
|
|
258
|
+
</div>
|
|
259
|
+
</div>
|
|
260
|
+
</DefaultModal>
|
|
261
|
+
);
|
|
262
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import {
|
|
2
|
+
RectCoords,
|
|
3
|
+
getElementSize,
|
|
4
|
+
getRectAspectRatio,
|
|
5
|
+
getThumbSizeArea,
|
|
6
|
+
elementToCanvas,
|
|
7
|
+
} from '@nyris/nyris-api';
|
|
8
|
+
|
|
9
|
+
export const getCroppedCanvas = (
|
|
10
|
+
canvas: HTMLCanvasElement | HTMLImageElement | HTMLVideoElement,
|
|
11
|
+
cropRect?: RectCoords,
|
|
12
|
+
) => {
|
|
13
|
+
let crop = cropRect || {
|
|
14
|
+
x1: 0,
|
|
15
|
+
x2: 1,
|
|
16
|
+
y1: 0,
|
|
17
|
+
y2: 1,
|
|
18
|
+
};
|
|
19
|
+
if (!canvas) return null;
|
|
20
|
+
|
|
21
|
+
const originalSize = getElementSize(canvas);
|
|
22
|
+
const aspectRatio = getRectAspectRatio(crop, originalSize);
|
|
23
|
+
let scaledSize = getThumbSizeArea(600, 600, aspectRatio);
|
|
24
|
+
let resizedCroppedCanvas = elementToCanvas(canvas, scaledSize, crop);
|
|
25
|
+
return resizedCroppedCanvas;
|
|
26
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
|
|
3
|
+
const useFilteredRegions = (regions: any, imageSelection: any) => {
|
|
4
|
+
const filteredRegions = useMemo(
|
|
5
|
+
() =>
|
|
6
|
+
regions.map(
|
|
7
|
+
(region: {
|
|
8
|
+
normalizedRect: { x1: any; x2: any; y1: any; y2: any };
|
|
9
|
+
}) => {
|
|
10
|
+
if (
|
|
11
|
+
region.normalizedRect?.x1 === imageSelection?.x1 &&
|
|
12
|
+
region.normalizedRect?.x2 === imageSelection?.x2 &&
|
|
13
|
+
region.normalizedRect?.y1 === imageSelection?.y1 &&
|
|
14
|
+
region.normalizedRect?.y2 === imageSelection?.y2
|
|
15
|
+
) {
|
|
16
|
+
return { ...region, show: false };
|
|
17
|
+
}
|
|
18
|
+
if (
|
|
19
|
+
imageSelection?.x1 === 0 &&
|
|
20
|
+
imageSelection?.x2 === 1 &&
|
|
21
|
+
imageSelection?.y1 === 0 &&
|
|
22
|
+
imageSelection?.y2 === 1
|
|
23
|
+
) {
|
|
24
|
+
return { ...region, show: false };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return { ...region, show: true };
|
|
28
|
+
},
|
|
29
|
+
),
|
|
30
|
+
|
|
31
|
+
[regions, imageSelection],
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
return filteredRegions;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export default useFilteredRegions;
|