@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.
Files changed (57) hide show
  1. package/build/asset-manifest.json +13 -13
  2. package/build/index.html +1 -1
  3. package/build/{precache-manifest.375ac411683570ee1df5aea6de03409d.js → precache-manifest.c69fca63f2d97ff08892d42e2b903f20.js} +14 -14
  4. package/build/service-worker.js +1 -1
  5. package/build/static/css/main.41fb4769.chunk.css +2 -0
  6. package/build/static/css/main.41fb4769.chunk.css.map +1 -0
  7. package/build/static/js/2.00161cbb.chunk.js +3 -0
  8. package/build/static/js/2.00161cbb.chunk.js.map +1 -0
  9. package/build/static/js/main.09a533bd.chunk.js +3 -0
  10. package/build/static/js/main.09a533bd.chunk.js.map +1 -0
  11. package/build/static/media/error.48b946a9.svg +3 -0
  12. package/package.json +3 -3
  13. package/public/index.html +13 -0
  14. package/src/Store/search/Search.ts +4 -4
  15. package/src/Store/search/search.initialState.ts +1 -1
  16. package/src/Store/search/types.ts +1 -1
  17. package/src/common/assets/icons/arrow_left.svg +3 -0
  18. package/src/common/assets/icons/arrow_right.svg +3 -0
  19. package/src/common/assets/icons/error.svg +3 -0
  20. package/src/components/AppMobile.tsx +111 -0
  21. package/src/components/DragDropFile.tsx +5 -4
  22. package/src/components/FooterMobile.tsx +8 -2
  23. package/src/components/Header.tsx +2 -1
  24. package/src/components/HeaderMobile.tsx +5 -3
  25. package/src/components/ImageCaptureHelpModal.tsx +7 -1
  26. package/src/components/ImagePreviewMobile.tsx +87 -0
  27. package/src/components/Layout.tsx +32 -92
  28. package/src/components/ProductList/index.tsx +4 -1
  29. package/src/components/RfqBanner.tsx +120 -0
  30. package/src/components/SidePanel.tsx +147 -0
  31. package/src/components/appMobile.scss +147 -142
  32. package/src/components/drawer/cameraCustom.tsx +18 -5
  33. package/src/components/input/inputSearch.tsx +12 -7
  34. package/src/components/modal/DefaultModal.tsx +1 -1
  35. package/src/components/pre-filter/index.tsx +108 -51
  36. package/src/components/results/ItemResult.tsx +1 -1
  37. package/src/components/rfq/RfqModal.tsx +262 -0
  38. package/src/helpers/ToastHelper.ts +10 -0
  39. package/src/helpers/getCroppedCanvas.ts +26 -0
  40. package/src/hooks/useFilteredRegions.ts +37 -0
  41. package/src/index.css +0 -4
  42. package/src/page/landingPage/AppMD.tsx +0 -11
  43. package/src/page/landingPage/AppMobile.tsx +1 -0
  44. package/src/page/landingPage/common.scss +41 -58
  45. package/src/page/result/index.tsx +256 -309
  46. package/src/translations.ts +2 -2
  47. package/src/types.ts +1 -0
  48. package/src/utils.ts +0 -1
  49. package/build/static/css/main.f5a1c730.chunk.css +0 -2
  50. package/build/static/css/main.f5a1c730.chunk.css.map +0 -1
  51. package/build/static/js/2.724f4cba.chunk.js +0 -3
  52. package/build/static/js/2.724f4cba.chunk.js.map +0 -1
  53. package/build/static/js/main.f579fa9f.chunk.js +0 -3
  54. package/build/static/js/main.f579fa9f.chunk.js.map +0 -1
  55. package/build/static/media/support3.4a17f96e.svg +0 -3
  56. /package/build/static/js/{2.724f4cba.chunk.js.LICENSE.txt → 2.00161cbb.chunk.js.LICENSE.txt} +0 -0
  57. /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, keyFilter } = search;
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 preFilter = [
103
+ const preFilterValues = [
103
104
  {
104
105
  key: settings.visualSearchFilterKey,
105
- values: [`${keyFilter}`],
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: keyFilter ? preFilter : undefined,
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={keyFilter ? keyFilter : 'Add pre-filter'}
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
- ...(keyFilter
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
- {keyFilter && (
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: 'hidden', maxHeight: '95vh', borderRadius: 12 }}
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 { setUpdateKeyFilterDesktop } from 'Store/search/Search';
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: { keyFilter: keyFilterState },
24
+ search: { preFilter: keyFilterState },
25
25
  } = stateGlobal;
26
26
  const [resultFilter, setResultFilter] = useState<any>([]);
27
- const [keyFilter, setKeyFilter] = useState<string>(keyFilterState || '');
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(setUpdateKeyFilterDesktop(keyFilter));
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: isMobile ? '0px' : '14px',
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
- {(!keyFilter || isMobile) && (
148
- <Box
149
- className="icon-search"
150
- style={{ marginRight: 11 }}
151
- display={'flex'}
152
- justifyContent={'center'}
153
- alignItems={'center'}
154
- >
155
- <img
156
- style={{ maxWidth: 'fit-content' }}
157
- src={IconSearch}
158
- alt=""
159
- width={18}
160
- height={18}
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 && isMobile && (
187
- <Box style={{ margin: '10px 16px' }}>
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
- display={'flex'}
190
- className="box-keyFilter"
191
- style={{ display: 'inline-flex' }}
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
- <Typography className="keyFilter max-line-1">
194
- {keyFilter}
195
- </Typography>
196
- <Button style={{ padding: 0 }} onClick={() => setKeyFilter('')}>
197
- <CloseIcon style={{ fontSize: 12, color: '#2B2C46' }} />
198
- </Button>
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
- ? { columnCount: 1, marginBottom: keyFilter ? '50px' : '0px' }
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
- setKeyFilter(item);
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
  >
@@ -217,7 +217,7 @@ function ItemResult(props: Props) {
217
217
  <span style={{ marginRight: 3 }}>
218
218
  {settings.itemIdLabel || 'SKU'}:
219
219
  </span>
220
- {truncateString(sku, 20)}
220
+ {truncateString(sku, isMobile ? 17 : 20)}
221
221
  </Typography>
222
222
  </Tooltip>
223
223
 
@@ -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
+ }
@@ -10,4 +10,14 @@ export class ToastHelper {
10
10
  },
11
11
  });
12
12
  }
13
+
14
+ static error(msg: string) {
15
+ toast.error(msg, {
16
+ duration: 3000,
17
+ style: {
18
+ background: '#1E1F31',
19
+ color: '#fff',
20
+ },
21
+ });
22
+ }
13
23
  }
@@ -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;