@antscorp/antsomi-ui 2.0.121 → 2.0.123
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/CHANGELOG.md +92 -92
- package/es/components/organism/BasicLogin/components/AuthenticateResult/AuthenticateResult.js +1 -1
- package/es/components/organism/BasicLogin/components/ChooseMethod/ChooseMethod.js +37 -19
- package/es/components/organism/BasicLogin/components/ChooseMethod/Method.d.ts +1 -0
- package/es/components/organism/BasicLogin/components/ChooseMethod/Method.js +4 -5
- package/es/components/organism/BasicLogin/components/ChooseMethod/styled.d.ts +10 -3
- package/es/components/organism/BasicLogin/components/ChooseMethod/styled.js +54 -6
- package/es/components/organism/BasicLogin/components/LoginWithEmail/LoginWithEmail.js +2 -2
- package/es/components/organism/BasicLogin/components/LoginWithGG/LoginWithGG.js +3 -3
- package/es/components/organism/Login/components/ChooseMethod/ChooseMethod.js +36 -19
- package/es/components/organism/Login/components/ChooseMethod/ChooseMethod.spec.d.ts +1 -0
- package/es/components/organism/Login/components/ChooseMethod/ChooseMethod.spec.js +50 -0
- package/es/components/organism/Login/components/ChooseMethod/Method.d.ts +1 -0
- package/es/components/organism/Login/components/ChooseMethod/Method.js +4 -5
- package/es/components/organism/Login/components/ChooseMethod/styled.d.ts +10 -3
- package/es/components/organism/Login/components/ChooseMethod/styled.js +54 -6
- package/es/components/organism/Login/components/LoginSelectPortal/LoginSelectPortal.js +1 -1
- package/es/components/organism/Login/components/LoginWithEmail/LoginWithEmail.js +1 -1
- package/es/components/organism/Login/components/LoginWithGG/LoginWithGG.js +1 -1
- package/es/components/organism/Login/components/WidgetLayout/styled.d.ts +3 -0
- package/es/components/organism/Login/components/WidgetLayout/styled.js +6 -0
- package/es/components/organism/Login/styled.d.ts +1 -0
- package/es/components/organism/Login/styled.js +1 -0
- package/es/components/organism/TicketEditor/Content.js +74 -35
- package/es/components/organism/TicketEditor/util.d.ts +1 -0
- package/es/components/organism/TicketEditor/util.js +1 -0
- package/es/components/organism/TicketEditorV2/Content.js +62 -28
- package/es/components/organism/TicketEditorV2/util.d.ts +1 -0
- package/es/components/organism/TicketEditorV2/util.js +1 -0
- package/es/utils/uploadEmbeddedImages.d.ts +7 -0
- package/es/utils/uploadEmbeddedImages.js +65 -0
- package/package.json +1 -1
|
@@ -1,10 +1,17 @@
|
|
|
1
|
-
/// <reference types="react" />
|
|
2
1
|
export declare const TextInfo: import("styled-components").StyledComponent<"span", any, {
|
|
3
2
|
isMobile: boolean;
|
|
4
3
|
}, never>;
|
|
5
4
|
export declare const SubTitle: import("styled-components").StyledComponent<"span", any, {
|
|
6
5
|
isMobile: boolean;
|
|
7
6
|
}, never>;
|
|
8
|
-
export declare const BlockMethod: import("styled-components").StyledComponent<
|
|
9
|
-
disable?: boolean | undefined;
|
|
7
|
+
export declare const BlockMethod: import("styled-components").StyledComponent<"button", any, {
|
|
8
|
+
$disable?: boolean | undefined;
|
|
9
|
+
}, never>;
|
|
10
|
+
export declare const MethodIcon: import("styled-components").StyledComponent<"span", any, {}, never>;
|
|
11
|
+
export declare const MethodContent: import("styled-components").StyledComponent<"span", any, {}, never>;
|
|
12
|
+
export declare const MethodLabel: import("styled-components").StyledComponent<"span", any, {
|
|
13
|
+
$disable?: boolean | undefined;
|
|
14
|
+
}, never>;
|
|
15
|
+
export declare const MethodDescription: import("styled-components").StyledComponent<"span", any, {
|
|
16
|
+
$disable?: boolean | undefined;
|
|
10
17
|
}, never>;
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { Flex } from 'antd';
|
|
2
1
|
import styled from 'styled-components';
|
|
3
2
|
export const TextInfo = styled.span `
|
|
4
3
|
color: #7f7f7f;
|
|
@@ -9,16 +8,65 @@ export const SubTitle = styled.span `
|
|
|
9
8
|
font-size: ${(props) => (props.isMobile ? ' 16px ' : '14px')};
|
|
10
9
|
font-weight: 500;
|
|
11
10
|
`;
|
|
12
|
-
export const BlockMethod = styled
|
|
11
|
+
export const BlockMethod = styled.button `
|
|
12
|
+
display: flex;
|
|
13
13
|
align-items: center;
|
|
14
14
|
gap: 10px;
|
|
15
|
+
width: 100%;
|
|
16
|
+
min-height: 64px;
|
|
15
17
|
padding: 10px;
|
|
16
|
-
|
|
18
|
+
box-sizing: border-box;
|
|
19
|
+
cursor: ${props => (props.$disable ? 'not-allowed' : 'pointer')};
|
|
20
|
+
background-color: #ffffff;
|
|
21
|
+
border: 1px solid #b8cfe6;
|
|
17
22
|
border-radius: 10px;
|
|
23
|
+
font-family: inherit;
|
|
24
|
+
text-align: left;
|
|
25
|
+
opacity: ${props => (props.$disable ? '0.5' : '1')};
|
|
18
26
|
|
|
19
|
-
|
|
27
|
+
&:hover:not(:disabled) {
|
|
28
|
+
background-color: #f2f9ff;
|
|
29
|
+
border-color: #81bcf4;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
&:focus-visible {
|
|
33
|
+
outline: 2px solid #005eb8;
|
|
34
|
+
outline-offset: 2px;
|
|
35
|
+
}
|
|
36
|
+
`;
|
|
37
|
+
export const MethodIcon = styled.span `
|
|
38
|
+
display: flex;
|
|
39
|
+
flex: 0 0 24px;
|
|
40
|
+
align-self: flex-start;
|
|
41
|
+
align-items: flex-start;
|
|
42
|
+
justify-content: flex-start;
|
|
43
|
+
margin-top: 2px;
|
|
44
|
+
|
|
45
|
+
svg {
|
|
46
|
+
width: 24px;
|
|
47
|
+
height: 24px;
|
|
48
|
+
}
|
|
49
|
+
`;
|
|
50
|
+
export const MethodContent = styled.span `
|
|
51
|
+
display: flex;
|
|
52
|
+
flex: 1;
|
|
53
|
+
min-width: 0;
|
|
54
|
+
flex-direction: column;
|
|
55
|
+
gap: 5px;
|
|
56
|
+
`;
|
|
57
|
+
export const MethodLabel = styled.span `
|
|
58
|
+
color: ${props => (props.$disable ? '#9c9b9b' : '#000000')};
|
|
59
|
+
font-size: 14px;
|
|
60
|
+
|
|
61
|
+
@media (max-width: 768px) {
|
|
62
|
+
font-size: 16px;
|
|
63
|
+
}
|
|
64
|
+
`;
|
|
65
|
+
export const MethodDescription = styled.span `
|
|
66
|
+
color: ${props => (props.$disable ? '#9c9b9b' : '#595959')};
|
|
67
|
+
font-size: 12px;
|
|
20
68
|
|
|
21
|
-
|
|
22
|
-
|
|
69
|
+
@media (max-width: 768px) {
|
|
70
|
+
font-size: 13px;
|
|
23
71
|
}
|
|
24
72
|
`;
|
|
@@ -177,7 +177,7 @@ export const LoginSelectPortal = props => {
|
|
|
177
177
|
return draftListNetwork.filter(netWork => netWork.networkName.toLocaleLowerCase().indexOf(state.portalSearch.toLocaleLowerCase()) !==
|
|
178
178
|
-1);
|
|
179
179
|
}, [listNetworks, state.portalSearch]);
|
|
180
|
-
return (_jsx(WidgetLayout, { header: _jsx(Avatar, { image: get(loginData, 'personal.avatar'), name: get(loginData, 'personal.full_name'), email: get(loginData, 'personal.email') }), gapContent:
|
|
180
|
+
return (_jsx(WidgetLayout, { header: _jsx(Avatar, { image: get(loginData, 'personal.avatar'), name: get(loginData, 'personal.full_name'), email: get(loginData, 'personal.email') }), gapContent: 20, isShowLogo: false, title: "Select Portal", content: _jsxs(Flex, { vertical: true, gap: 30, children: [_jsxs(Flex, { vertical: true, gap: 20, children: [_jsx(StyleInput, { isMobile: isMobile, placeholder: "Search", suffix: _jsx(Icon, { type: "icon-ants-search-2", style: {
|
|
181
181
|
fontSize: 24,
|
|
182
182
|
}, color: "#595959" }), onChange: e => setPortalSearch(e.target.value) }), isEmpty(filterListNetworks) ? (_jsxs(Flex, { vertical: true, align: "center", justify: "center", style: { height: 514 }, gap: 15, children: [!isMobile && _jsx(Image, { src: NoData, alt: "", width: 80, height: 80 }), _jsx(Text, { style: {
|
|
183
183
|
color: '#595959',
|
|
@@ -205,7 +205,7 @@ export const LoginWithEmail = ({ loginData, domainLogin, codeKey = 'code', token
|
|
|
205
205
|
const baseText = `Please enter the 6-digit code send to ${maskedEmail} Haven't received it? `;
|
|
206
206
|
return (_jsx(Flex, { gap: 5, align: "center", children: _jsxs(Text, { isMobile: isMobile, color: "#595959", children: [baseText, state.isShowResend ? (_jsx(Text, { color: state.isDisableResend ? '#a2a2a2' : '#005eb8', onClick: state.isDisableResend ? undefined : onResendCode, isMobile: isMobile, style: { cursor: state.isDisableResend ? 'not-allowed' : 'pointer' }, children: "Resend." })) : (`Resend code in ${seconds}s.`)] }) }));
|
|
207
207
|
};
|
|
208
|
-
return (_jsx(WidgetLayout, { header: _jsx(Avatar, { image: userAvatar, name: userFullName, email: userEmail }), isShowLogo: false, title: _jsx(Title, { isMobile: isMobile, style: { marginTop: 20 }, children: "Identity Verification" }), gapContent:
|
|
208
|
+
return (_jsx(WidgetLayout, { header: _jsx(Avatar, { image: userAvatar, name: userFullName, email: userEmail }), isShowLogo: false, title: _jsx(Title, { isMobile: isMobile, style: { marginTop: 20 }, children: "Identity Verification" }), gapContent: 20, content: _jsxs(Flex, { vertical: true, gap: 50, children: [_jsxs(Flex, { vertical: true, gap: 20, align: "center", children: [_jsx("img", { src: loginStep2Image, alt: "Identity Verification" }), _jsx(Text, { isMobile: isMobile, color: "#595959", isCenter: true, children: "For your security, we need to verify your identity before continuing." })] }), _jsxs(Flex, { vertical: true, gap: isMobile ? 10 : 15, children: [renderResendSection(), _jsxs(Flex, { vertical: true, gap: isMobile ? 20 : 11, children: [_jsxs(Flex, { vertical: true, gap: 10, children: [_jsx(StyleInput, { isMobile: isMobile, maxLength: CODE_LENGTH, placeholder: "Input your code", value: state.code, onChange: handleCodeChange, status: errorRef.current && state.codeError ? 'error' : undefined, disabled: state.isTooManyMistakes }), renderErrorMessages()] }), _jsx(Checkbox, { checked: state.isTrust, onChange: handleTrustChange, disabled: state.isTooManyMistakes, children: _jsx(TextInfo, { isMobile: isMobile, children: "Remember this browser for future logins" }) })] }), _jsxs(Flex, { vertical: true, gap: isMobile ? 30 : 15, style: { marginTop: isMobile ? 20 : 15 }, children: [_jsx(StyleButton, { isMobile: isMobile, type: "primary", onClick: signIn, loading: state.isLoading, disabled: state.isTooManyMistakes, children: "Verify" }), _jsx(StyleButton, { isMobile: isMobile, onClick: handleBack, type: "text", children: "Back to login" })] })] })] }), footer: _jsx(Flex, { vertical: true, gap: 115, children: _jsx(Flex, { justify: "center", children: _jsx(Button, { type: "text", onClick: handleBack, style: {
|
|
209
209
|
fontSize: isMobile ? '16px' : '14px',
|
|
210
210
|
}, children: "Sign in with a different account" }) }) }) }));
|
|
211
211
|
};
|
|
@@ -98,7 +98,7 @@ export const LoginWithGG = props => {
|
|
|
98
98
|
});
|
|
99
99
|
}
|
|
100
100
|
}, [state.code]);
|
|
101
|
-
return (_jsx(WidgetLayout, { header: _jsx(Avatar, { image: get(loginData, 'personal.avatar'), name: get(loginData, 'personal.full_name'), email: get(loginData, 'personal.email') }), isShowLogo: false, title: _jsx(Title, { isMobile: isMobile, style: { marginTop: 20 }, children: "Identity Verification" }), gapContent:
|
|
101
|
+
return (_jsx(WidgetLayout, { header: _jsx(Avatar, { image: get(loginData, 'personal.avatar'), name: get(loginData, 'personal.full_name'), email: get(loginData, 'personal.email') }), isShowLogo: false, title: _jsx(Title, { isMobile: isMobile, style: { marginTop: 20 }, children: "Identity Verification" }), gapContent: 20, content: _jsxs(Flex, { vertical: true, gap: 50, children: [_jsxs(Flex, { vertical: true, gap: 20, align: "center", children: [_jsx("img", { src: loginStep2Image, alt: "" }), _jsx(Text, { isMobile: isMobile, color: "#595959", isCenter: true, children: "For your security, we need to verify your identity before continuing." })] }), _jsxs(Flex, { vertical: true, gap: isMobile ? 10 : 15, children: [_jsxs(Flex, { gap: 5, align: "center", children: [_jsx(Text, { isMobile: isMobile, color: "#595959", children: "Please enter the 6-digit code registered to your device." }), _jsx(IconInfo, { image: step2, title: _jsx(TextAntsomiUI, { style: {
|
|
102
102
|
marginTop: 20,
|
|
103
103
|
textAlign: 'center',
|
|
104
104
|
}, children: _jsxs(Text, { isMobile: isMobile, children: ["Open Google Authenticator and enter the code from ", _jsx("b", { children: "antsomi.com" })] }) }) })] }), _jsxs(Flex, { vertical: true, gap: isMobile ? 20 : 11, children: [_jsxs(Flex, { vertical: true, gap: 10, children: [_jsx(StyleInput, { isMobile: isMobile, maxLength: 6, placeholder: "Input your code", value: state.code, onChange: e => {
|
|
@@ -2,4 +2,7 @@
|
|
|
2
2
|
export declare const Title: import("styled-components").StyledComponent<"div", any, {
|
|
3
3
|
isMobile: boolean;
|
|
4
4
|
}, never>;
|
|
5
|
+
export declare const Label: import("styled-components").StyledComponent<"div", any, {
|
|
6
|
+
isMobile: boolean;
|
|
7
|
+
}, never>;
|
|
5
8
|
export declare const WidgetLayoutMain: import("styled-components").StyledComponent<import("react").ForwardRefExoticComponent<import("antd").FlexProps<import("antd/es/_util/type").AnyObject> & import("react").RefAttributes<HTMLElement>>, any, {}, never>;
|
|
@@ -6,6 +6,12 @@ export const Title = styled.div `
|
|
|
6
6
|
color: #005eb8;
|
|
7
7
|
text-align: center;
|
|
8
8
|
`;
|
|
9
|
+
export const Label = styled.div `
|
|
10
|
+
font-size: ${props => (props.isMobile ? '16px' : '14px')};
|
|
11
|
+
font-weight: 500;
|
|
12
|
+
color: #005eb8;
|
|
13
|
+
text-align: center;
|
|
14
|
+
`;
|
|
9
15
|
export const WidgetLayoutMain = styled(Flex) `
|
|
10
16
|
position: relative;
|
|
11
17
|
|
|
@@ -126,4 +126,5 @@ export const Text = styled.div `
|
|
|
126
126
|
color: ${props => (props.color ? props.color : ' #000000')};
|
|
127
127
|
font-size: ${props => (props.isMobile ? ' 14px !important' : '12px')};
|
|
128
128
|
font-weight: ${props => (props.isBold ? 'bold' : 'normal')};
|
|
129
|
+
text-align: ${props => (props.isCenter ? 'center' : 'left')};
|
|
129
130
|
`;
|
|
@@ -10,7 +10,7 @@ import { Spin, Tooltip, Icon, Button, EditableName, SelectAccount, QuillEditor,
|
|
|
10
10
|
import Service from './Service';
|
|
11
11
|
import { MESSAGE_TYPE, TICKET_CUSTOM_MESSAGE_KEY } from './constant';
|
|
12
12
|
import { WrapperContent, WrapperContentInput, WrapperEditor, WrapperIconEditor, WrapperInputFile, WrapperLable, WrapperLeftContent, WrapperLinkItemFiles, WrapperMessageContent, WrapperRightContent, WrapperTextEdit, WrapperTextInput, DrawerHeader, } from './styled';
|
|
13
|
-
import { formatAccountId, formatDatarender, formatParams, handleValidateContent, postCustomEvent, } from './util';
|
|
13
|
+
import { formatAccountId, formatDatarender, formatParams, handleValidateContent, postCustomEvent, uploadEmbeddedImages, } from './util';
|
|
14
14
|
import { TCallBack } from '../../molecules/QuillEditor/QuillEditor';
|
|
15
15
|
import { isBoolean } from 'lodash';
|
|
16
16
|
const initValueInput = {
|
|
@@ -49,6 +49,7 @@ const Content = ({ portalId, token, action, ticketId, listUsers, domainTicket, d
|
|
|
49
49
|
isError: false,
|
|
50
50
|
message: '',
|
|
51
51
|
});
|
|
52
|
+
const [embeddedImageError, setEmbeddedImageError] = useState('');
|
|
52
53
|
const isUpdate = action === 'edit' && ticketId;
|
|
53
54
|
const valueInputRef = useRef(valueInput);
|
|
54
55
|
valueInputRef.current = valueInput;
|
|
@@ -94,20 +95,54 @@ const Content = ({ portalId, token, action, ticketId, listUsers, domainTicket, d
|
|
|
94
95
|
setIsLoading(false);
|
|
95
96
|
});
|
|
96
97
|
};
|
|
97
|
-
const
|
|
98
|
+
const uploadImagesBeforeSubmit = async (message) => {
|
|
99
|
+
setIsLoadingUpload(true);
|
|
100
|
+
setEmbeddedImageError('');
|
|
101
|
+
try {
|
|
102
|
+
const uploadedMessage = await uploadEmbeddedImages(message, Service.tickets.callApi.uploadImg({
|
|
103
|
+
domain: domainUpload,
|
|
104
|
+
token,
|
|
105
|
+
userId,
|
|
106
|
+
}));
|
|
107
|
+
if (uploadedMessage !== message) {
|
|
108
|
+
setTextValue(uploadedMessage);
|
|
109
|
+
updateValueInput({ message: uploadedMessage });
|
|
110
|
+
}
|
|
111
|
+
return uploadedMessage;
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
setEmbeddedImageError(error instanceof Error && error.message === 'FILE_TOO_LARGE'
|
|
115
|
+
? '*Maximum upload file size: 10MB'
|
|
116
|
+
: '*Image upload failed. Please try again.');
|
|
117
|
+
throw error;
|
|
118
|
+
}
|
|
119
|
+
finally {
|
|
120
|
+
setIsLoadingUpload(false);
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
const handleSubmit = async () => {
|
|
98
124
|
const { title, ownerId, message } = valueInputRef.current;
|
|
99
125
|
if (!title || !ownerId.length || !message) {
|
|
100
126
|
setIsEmptyField(true);
|
|
101
127
|
}
|
|
102
128
|
else {
|
|
103
129
|
if (!errFile.isError) {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
130
|
+
try {
|
|
131
|
+
const uploadedMessage = await uploadImagesBeforeSubmit(message);
|
|
132
|
+
const params = formatParams({
|
|
133
|
+
...valueInputRef.current,
|
|
134
|
+
message: uploadedMessage,
|
|
135
|
+
});
|
|
136
|
+
const submitterEmail = listUsers?.find(user => user.userId === params.ownerId)?.email;
|
|
137
|
+
fetchCreateTicket({
|
|
138
|
+
...params,
|
|
139
|
+
submitterId: Number(userId),
|
|
140
|
+
submitterEmail,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
// The upload error is shown below the editor.
|
|
145
|
+
}
|
|
111
146
|
}
|
|
112
147
|
// handleSendData(valueInput)
|
|
113
148
|
}
|
|
@@ -231,6 +266,7 @@ const Content = ({ portalId, token, action, ticketId, listUsers, domainTicket, d
|
|
|
231
266
|
}
|
|
232
267
|
}, [isUpdate, openDrawer]);
|
|
233
268
|
const handleEditorChange = content => {
|
|
269
|
+
setEmbeddedImageError('');
|
|
234
270
|
setTextValue(content);
|
|
235
271
|
updateValueInput({ message: content });
|
|
236
272
|
// setIsLoadingUpload(content.includes('<img src="data:image/png;base64'));
|
|
@@ -244,32 +280,34 @@ const Content = ({ portalId, token, action, ticketId, listUsers, domainTicket, d
|
|
|
244
280
|
}
|
|
245
281
|
fetchUpdateFollwers(params);
|
|
246
282
|
};
|
|
247
|
-
const handleUpdateComment = () => {
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
params.followers = formatAccountId(valueInput?.followers);
|
|
260
|
-
}
|
|
261
|
-
if (valueInput?.files?.length > 0) {
|
|
262
|
-
params.properties = {
|
|
263
|
-
attachments: valueInput?.files,
|
|
283
|
+
const handleUpdateComment = async () => {
|
|
284
|
+
const currentValueInput = valueInputRef.current;
|
|
285
|
+
try {
|
|
286
|
+
const uploadedMessage = await uploadImagesBeforeSubmit(currentValueInput.message);
|
|
287
|
+
const params = {
|
|
288
|
+
followers: [],
|
|
289
|
+
submitterId: userId,
|
|
290
|
+
ownerId: Number(currentValueInput?.ownerId[0]?.userId),
|
|
291
|
+
submitterEmail: listUsers?.find(user => +user.userId === +userId)?.email,
|
|
292
|
+
properties: {},
|
|
293
|
+
message: uploadedMessage,
|
|
294
|
+
attachmentsZendesk: currentValueInput.fileZendesk.map(file => file.token),
|
|
264
295
|
};
|
|
296
|
+
if (currentValueInput?.followers?.length) {
|
|
297
|
+
params.followers = formatAccountId(currentValueInput.followers);
|
|
298
|
+
}
|
|
299
|
+
if (currentValueInput?.files?.length > 0) {
|
|
300
|
+
params.properties = {
|
|
301
|
+
attachments: currentValueInput.files,
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
setIsLoading(true);
|
|
305
|
+
fetchUpdateComment(params);
|
|
306
|
+
handleUpdateFollowers(currentValueInput?.followers?.map((fol) => fol.userId));
|
|
307
|
+
}
|
|
308
|
+
catch (error) {
|
|
309
|
+
// The upload error is shown below the editor.
|
|
265
310
|
}
|
|
266
|
-
params.submitterId = userId;
|
|
267
|
-
params.submitterEmail = listUsers?.find(user => +user.userId === +userId)?.email;
|
|
268
|
-
params.ownerId = Number(valueInput?.ownerId[0]?.userId);
|
|
269
|
-
params.message = valueInput?.message;
|
|
270
|
-
params.attachmentsZendesk = valueInput.fileZendesk.map(file => file.token);
|
|
271
|
-
fetchUpdateComment(params);
|
|
272
|
-
handleUpdateFollowers(valueInput?.followers?.map((fol) => fol.userId));
|
|
273
311
|
};
|
|
274
312
|
const handleUploadFile = async (file) => {
|
|
275
313
|
setIsLoading(true);
|
|
@@ -387,7 +425,8 @@ const Content = ({ portalId, token, action, ticketId, listUsers, domainTicket, d
|
|
|
387
425
|
isLoadingDetails ||
|
|
388
426
|
isLoadingDataSouce ||
|
|
389
427
|
isLoadingFollower ||
|
|
390
|
-
isLoadingSelectedAccount
|
|
428
|
+
isLoadingSelectedAccount ||
|
|
429
|
+
isLoadingUpload, children: [_jsxs(DrawerHeader, { children: [_jsx(EditableName, { value: valueInput.title, onChange: handleOnchangeTitle, readonly: isUpdate, error: isEmptyField && (!valueInput.title || !valueInput.title.trim())
|
|
391
430
|
? "Title can't be empty"
|
|
392
431
|
: '' }), _jsx("div", { id: "header-journey-right", className: "right-content", children: !isUpdate && (_jsx(Button, { disabled: isLoadingUpload, type: "primary", onClick: handleSubmit, children: "Save" })) })] }), _jsxs(WrapperContent, { style: { height: '100%' }, children: [_jsxs(WrapperLeftContent, { children: [_jsxs(WrapperContentInput, { style: { alignItems: 'flex-start' }, children: [_jsxs(WrapperLable, { children: ["Owner", _jsx("span", { style: { color: '#ff0000' }, children: "*" })] }), _jsxs("div", { children: [_jsx(SelectAccount, { type: "default", initData: (valueInput?.ownerId || []).map((item) => item.userId), nameKey: "userName", userIdKey: "userId", users: listUsers, isViewMode: isUpdate || !isAllAccount || accountEdit !== 4, onChange: (_, users) => updateValueInput({ ownerId: users }), onlyOne: true }), isEmptyField && !valueInput?.ownerId?.length && (_jsx("div", { className: "error-message", children: "*This field can't be empty" }))] })] }), _jsxs(WrapperContentInput, { style: { marginTop: '15px', alignItems: 'flex-start' }, children: [_jsx(WrapperLable, { children: "Follower(s)" }), _jsx(SelectAccount, { type: "default", initData: [
|
|
393
432
|
...accountManage,
|
|
@@ -404,7 +443,7 @@ const Content = ({ portalId, token, action, ticketId, listUsers, domainTicket, d
|
|
|
404
443
|
background: `${!handleValidateContent(textValue) || isLoadingUpload
|
|
405
444
|
? '#ccc'
|
|
406
445
|
: '#1f5fac'}`,
|
|
407
|
-
}, onClick: handleUpdateComment, children: "Reply" }))] })] })] }), errFile.isError ? (_jsx("div", { className: "error-message", children: errFile.message })) : (isEmptyField &&
|
|
446
|
+
}, onClick: handleUpdateComment, children: "Reply" }))] })] })] }), embeddedImageError ? (_jsx("div", { className: "error-message", children: embeddedImageError })) : errFile.isError ? (_jsx("div", { className: "error-message", children: errFile.message })) : (isEmptyField &&
|
|
408
447
|
!valueInput?.message && (_jsx("div", { className: "error-message", children: "*This field can't be empty" })))] }), isUpdate && (_jsx(Spin, { spinning: isLoadingComment, children: _jsx(WrapperMessageContent, { children: listComment?.map(comment => (_jsx(MessageComponent, { toUser: comment?.toUser, fromUser: comment?.fromUser, followers: comment?.followers, message: comment?.message, date: comment?.createdDate, attachments: comment?.attachments, timeZone: timeZone, submitterEmail: comment?.submitterEmail }, comment?.id))) }) }))] })] })] })] }));
|
|
409
448
|
};
|
|
410
449
|
export default Content;
|
|
@@ -27,6 +27,7 @@ export declare function getEntriesV2(res: any, def: any): {
|
|
|
27
27
|
messageAPI?: undefined;
|
|
28
28
|
};
|
|
29
29
|
export declare const formatAccountId: (data: any) => any;
|
|
30
|
+
export { dataUrlToFile, uploadEmbeddedImages } from '../../../utils/uploadEmbeddedImages';
|
|
30
31
|
export declare const formatParams: (data: any) => {
|
|
31
32
|
title: string;
|
|
32
33
|
ownerId: undefined;
|
|
@@ -294,6 +294,7 @@ export const formatAccountId = data => {
|
|
|
294
294
|
const results = data?.map(item => item?.userId);
|
|
295
295
|
return results;
|
|
296
296
|
};
|
|
297
|
+
export { dataUrlToFile, uploadEmbeddedImages } from '../../../utils/uploadEmbeddedImages';
|
|
297
298
|
export const formatParams = data => {
|
|
298
299
|
const params = {
|
|
299
300
|
title: '',
|
|
@@ -8,7 +8,7 @@ import MessageComponent from './components/MessageComponent';
|
|
|
8
8
|
import { Spin, Tooltip, Icon, Button, SelectAccount, QuillEditor, } from '@antscorp/antsomi-ui/es/components';
|
|
9
9
|
import Service from './Service';
|
|
10
10
|
import { WrapperContent, WrapperContentInput, WrapperEditor, WrapperIconEditor, WrapperInputFile, WrapperLable, WrapperLeftContent, WrapperLinkItemFiles, WrapperMessageContent, WrapperRightContent, WrapperTextEdit, WrapperTextInput, } from './styled';
|
|
11
|
-
import { formatDatarender, handleValidateContent } from './util';
|
|
11
|
+
import { formatDatarender, handleValidateContent, uploadEmbeddedImages, } from './util';
|
|
12
12
|
import { get, keyBy } from 'lodash';
|
|
13
13
|
import { TCallBack } from '../../molecules/QuillEditor/QuillEditor';
|
|
14
14
|
const initValueInput = {
|
|
@@ -44,6 +44,7 @@ const Content = ({ apiKey, domain, portalId, token, action, ticketId, listUsers,
|
|
|
44
44
|
isError: false,
|
|
45
45
|
message: '',
|
|
46
46
|
});
|
|
47
|
+
const [embeddedImageError, setEmbeddedImageError] = useState('');
|
|
47
48
|
const isUpdate = action === 'edit' && ticketId;
|
|
48
49
|
const valueInputRef = useRef(valueInput);
|
|
49
50
|
valueInputRef.current = valueInput;
|
|
@@ -136,6 +137,7 @@ const Content = ({ apiKey, domain, portalId, token, action, ticketId, listUsers,
|
|
|
136
137
|
}, 1000);
|
|
137
138
|
}, []);
|
|
138
139
|
const handleEditorChange = content => {
|
|
140
|
+
setEmbeddedImageError('');
|
|
139
141
|
setTextValue(content);
|
|
140
142
|
updateValueInput({ message: content });
|
|
141
143
|
// setIsLoadingUpload(content.includes('<img src="data:image/png;base64'));
|
|
@@ -147,33 +149,61 @@ const Content = ({ apiKey, domain, portalId, token, action, ticketId, listUsers,
|
|
|
147
149
|
params.followers = arrFolowers;
|
|
148
150
|
fetchUpdateFollwers(params);
|
|
149
151
|
};
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
|
|
152
|
+
const uploadImagesBeforeSubmit = async (message) => {
|
|
153
|
+
setIsLoadingUpload(true);
|
|
154
|
+
setEmbeddedImageError('');
|
|
155
|
+
try {
|
|
156
|
+
const uploadedMessage = await uploadEmbeddedImages(message, Service.tickets.callApi.uploadFile({
|
|
157
|
+
domain: domainUpload,
|
|
158
|
+
}));
|
|
159
|
+
if (uploadedMessage !== message) {
|
|
160
|
+
setTextValue(uploadedMessage);
|
|
161
|
+
updateValueInput({ message: uploadedMessage });
|
|
162
|
+
}
|
|
163
|
+
return uploadedMessage;
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
setEmbeddedImageError(error instanceof Error && error.message === 'FILE_TOO_LARGE'
|
|
167
|
+
? '*Maximum upload file size: 10MB'
|
|
168
|
+
: '*Image upload failed. Please try again.');
|
|
169
|
+
throw error;
|
|
170
|
+
}
|
|
171
|
+
finally {
|
|
172
|
+
setIsLoadingUpload(false);
|
|
165
173
|
}
|
|
166
|
-
|
|
167
|
-
|
|
174
|
+
};
|
|
175
|
+
const handleUpdateComment = async () => {
|
|
176
|
+
const currentValueInput = valueInputRef.current;
|
|
177
|
+
try {
|
|
178
|
+
const uploadedMessage = await uploadImagesBeforeSubmit(currentValueInput.message);
|
|
179
|
+
const params = {
|
|
180
|
+
followers: [],
|
|
181
|
+
submitterId: null,
|
|
182
|
+
ownerId: null,
|
|
183
|
+
message: uploadedMessage,
|
|
184
|
+
properties: {
|
|
185
|
+
attachments: [],
|
|
186
|
+
},
|
|
187
|
+
submitterEmail: '',
|
|
188
|
+
attachmentsZendesk: [],
|
|
189
|
+
};
|
|
190
|
+
if (currentValueInput?.followers?.length) {
|
|
191
|
+
params.followers = currentValueInput.followers;
|
|
192
|
+
}
|
|
193
|
+
if (currentValueInput?.files?.length > 0) {
|
|
194
|
+
params.properties.attachments = currentValueInput.files;
|
|
195
|
+
}
|
|
196
|
+
const mapUsers = keyBy(listUsers, 'email');
|
|
197
|
+
params.submitterId = +mapUsers[email]?.userId || 0;
|
|
198
|
+
params.submitterEmail = email;
|
|
199
|
+
params.attachmentsZendesk = currentValueInput.fileZendesk?.map(file => file?.token);
|
|
200
|
+
setIsLoading(true);
|
|
201
|
+
fetchUpdateComment(params);
|
|
202
|
+
handleUpdateFollowers(currentValueInput.followers?.map((fol) => fol.userId));
|
|
203
|
+
}
|
|
204
|
+
catch (error) {
|
|
205
|
+
// The upload error is shown below the editor.
|
|
168
206
|
}
|
|
169
|
-
const mapUsers = keyBy(listUsers, 'email');
|
|
170
|
-
params.submitterId = +mapUsers[email]?.userId || 0;
|
|
171
|
-
params.submitterEmail = email;
|
|
172
|
-
params.ownerId = null;
|
|
173
|
-
params.message = valueInput?.message;
|
|
174
|
-
params.attachmentsZendesk = valueInput?.fileZendesk?.map(file => file?.token);
|
|
175
|
-
fetchUpdateComment(params);
|
|
176
|
-
handleUpdateFollowers(valueInput?.followers?.map((fol) => fol.userId));
|
|
177
207
|
};
|
|
178
208
|
const onChangeFollowers = arrFolowers => {
|
|
179
209
|
updateValueInput({ followers: arrFolowers });
|
|
@@ -301,7 +331,11 @@ const Content = ({ apiKey, domain, portalId, token, action, ticketId, listUsers,
|
|
|
301
331
|
break;
|
|
302
332
|
}
|
|
303
333
|
};
|
|
304
|
-
return (_jsxs("div", { style: { height: '100%' }, children: [_jsxs(Helmet, { children: [_jsx("meta", { charSet: "utf-8" }), _jsx("title", { children: browserTitle })] }), _jsx(Spin, { style: { height: '100vh' }, spinning: isLoading ||
|
|
334
|
+
return (_jsxs("div", { style: { height: '100%' }, children: [_jsxs(Helmet, { children: [_jsx("meta", { charSet: "utf-8" }), _jsx("title", { children: browserTitle })] }), _jsx(Spin, { style: { height: '100vh' }, spinning: isLoading ||
|
|
335
|
+
isLoadingDetails ||
|
|
336
|
+
isLoadingDataSouce ||
|
|
337
|
+
isLoadingFollower ||
|
|
338
|
+
isLoadingUpload, children: _jsxs(WrapperContent, { style: { height: '100%' }, children: [_jsxs(WrapperLeftContent, { children: [_jsxs(WrapperContentInput, { style: { alignItems: 'flex-start' }, children: [_jsxs(WrapperLable, { children: ["Owner", _jsx("span", { style: { color: '#ff0000' }, children: "*" })] }), _jsx("div", { children: ownerEmail })] }), _jsxs(WrapperContentInput, { style: { marginTop: '15px', alignItems: 'flex-start' }, children: [_jsx(WrapperLable, { children: "Follower(s)" }), _jsx(SelectAccount, { type: "default", initData: ticketDetails?.followers || [], nameKey: "userName", userIdKey: "userId", users: listUsers, onChange: (_, followers) => onChangeFollowers(followers) })] }), dataSelects?.map((data) => _jsx(_Fragment, { children: renderOptions(data) })), _jsxs(WrapperContentInput, { style: { marginTop: '15px' }, children: [_jsx(WrapperLable, { htmlFor: "referenceUrl", children: "Reference URL" }), isUpdate ? (ticketDetails.referenceUrl ? (_jsx(Tooltip, { title: ticketDetails.referenceUrl, placement: "top", children: _jsx(WrapperTextEdit, { color: "#005fb8", href: ticketDetails.referenceUrl, target: "_blank", children: ticketDetails.referenceUrl }) })) : (_jsx(WrapperTextEdit, { color: "#005fb8", href: ticketDetails.referenceUrl, target: "_blank", children: ticketDetails.referenceUrl }))) : (_jsx(WrapperTextInput, { placeholder: "Reference URL", id: "referenceUrl",
|
|
305
339
|
// width="300px"
|
|
306
340
|
onChange: handleOnchangeInput, name: "referenceUrl", value: valueInput.referenceUrl }))] })] }), _jsxs(WrapperRightContent, { children: [_jsxs(WrapperEditor, { children: [_jsxs(_Fragment, { children: [_jsx(QuillEditor, { value: textValue, uploadService: Service.tickets.callApi.uploadFile({
|
|
307
341
|
domain: domainUpload,
|
|
@@ -309,7 +343,7 @@ const Content = ({ apiKey, domain, portalId, token, action, ticketId, listUsers,
|
|
|
309
343
|
background: `${!handleValidateContent(textValue) || isLoadingUpload
|
|
310
344
|
? '#ccc'
|
|
311
345
|
: '#1f5fac'}`,
|
|
312
|
-
}, onClick: handleUpdateComment, children: "Reply" }))] })] })] }), errFile.isError ? (_jsx("div", { className: "error-message", children: errFile.message })) : (isEmptyField &&
|
|
346
|
+
}, onClick: handleUpdateComment, children: "Reply" }))] })] })] }), embeddedImageError ? (_jsx("div", { className: "error-message", children: embeddedImageError })) : errFile.isError ? (_jsx("div", { className: "error-message", children: errFile.message })) : (isEmptyField &&
|
|
313
347
|
!valueInput?.message && (_jsx("div", { className: "error-message", children: "* This field can't be empty" })))] }), isUpdate && (_jsx(Spin, { spinning: isLoadingComment, children: _jsx(WrapperMessageContent, { children: listComment?.map(comment => (_jsx(MessageComponent, { toUser: comment?.toUser, fromUser: comment?.fromUser, followers: comment?.followers,
|
|
314
348
|
// mailFollower={comment?.mailFollower}
|
|
315
349
|
message: comment?.message, date: comment?.createdDate, attachments: comment?.attachments, timeZone: timeZone, submitterEmail: comment?.submitterEmail }, comment?.id))) }) }))] })] }) })] }));
|
|
@@ -28,6 +28,7 @@ export declare function getEntriesV2(res: any, def: any): {
|
|
|
28
28
|
messageAPI?: undefined;
|
|
29
29
|
};
|
|
30
30
|
export declare const formatAccountId: (data: any) => any;
|
|
31
|
+
export { dataUrlToFile, uploadEmbeddedImages } from '../../../utils/uploadEmbeddedImages';
|
|
31
32
|
export declare const formatParams: (data: any) => {
|
|
32
33
|
title: string;
|
|
33
34
|
ownerId: undefined;
|
|
@@ -287,6 +287,7 @@ export const formatAccountId = data => {
|
|
|
287
287
|
const results = data?.map(item => item?.userId);
|
|
288
288
|
return results;
|
|
289
289
|
};
|
|
290
|
+
export { dataUrlToFile, uploadEmbeddedImages } from '../../../utils/uploadEmbeddedImages';
|
|
290
291
|
export const formatParams = data => {
|
|
291
292
|
const params = {
|
|
292
293
|
title: '',
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
type TUploadEmbeddedImages = (params: {
|
|
2
|
+
files: File[];
|
|
3
|
+
mode: 'file';
|
|
4
|
+
}) => Promise<unknown>;
|
|
5
|
+
export declare const dataUrlToFile: (dataUrl: string, index: number) => File;
|
|
6
|
+
export declare const uploadEmbeddedImages: (message: string, uploadService: TUploadEmbeddedImages) => Promise<string>;
|
|
7
|
+
export {};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
const DATA_IMAGE_PREFIX = /^\s*data:image\//i;
|
|
2
|
+
const DATA_IMAGE_URL = /^data:(image\/[^;,]+);base64,(.+)$/i;
|
|
3
|
+
const getUploadedImageUrl = (uploadedImage) => {
|
|
4
|
+
if (!uploadedImage || typeof uploadedImage !== 'object' || !('url' in uploadedImage)) {
|
|
5
|
+
return null;
|
|
6
|
+
}
|
|
7
|
+
const { url } = uploadedImage;
|
|
8
|
+
if (typeof url !== 'string' || !url.trim() || /^\s*data:/i.test(url)) {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
try {
|
|
12
|
+
const parsedUrl = new URL(url.trim());
|
|
13
|
+
return parsedUrl.protocol === 'http:' || parsedUrl.protocol === 'https:'
|
|
14
|
+
? parsedUrl.href
|
|
15
|
+
: null;
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
export const dataUrlToFile = (dataUrl, index) => {
|
|
22
|
+
const matches = dataUrl.trim().match(DATA_IMAGE_URL);
|
|
23
|
+
if (!matches) {
|
|
24
|
+
throw new Error('INVALID_IMAGE_DATA');
|
|
25
|
+
}
|
|
26
|
+
const [, rawMimeType, base64] = matches;
|
|
27
|
+
const mimeType = rawMimeType.toLowerCase();
|
|
28
|
+
const padding = base64.endsWith('==') ? 2 : base64.endsWith('=') ? 1 : 0;
|
|
29
|
+
const fileSize = (base64.length * 3) / 4 - padding;
|
|
30
|
+
if (fileSize > 10 * 1024 * 1024) {
|
|
31
|
+
throw new Error('FILE_TOO_LARGE');
|
|
32
|
+
}
|
|
33
|
+
const binary = atob(base64);
|
|
34
|
+
const bytes = new Uint8Array(binary.length);
|
|
35
|
+
for (let byteIndex = 0; byteIndex < binary.length; byteIndex += 1) {
|
|
36
|
+
bytes[byteIndex] = binary.charCodeAt(byteIndex);
|
|
37
|
+
}
|
|
38
|
+
const extension = mimeType.split('/')[1].replace('jpeg', 'jpg');
|
|
39
|
+
return new File([bytes], `ticket-image-${index + 1}.${extension}`, { type: mimeType });
|
|
40
|
+
};
|
|
41
|
+
export const uploadEmbeddedImages = async (message, uploadService) => {
|
|
42
|
+
const container = document.createElement('div');
|
|
43
|
+
container.innerHTML = message;
|
|
44
|
+
const images = Array.from(container.querySelectorAll('img')).filter(image => DATA_IMAGE_PREFIX.test(image.getAttribute('src') || ''));
|
|
45
|
+
if (!images.length) {
|
|
46
|
+
return message;
|
|
47
|
+
}
|
|
48
|
+
const files = images.map((image, index) => dataUrlToFile(image.getAttribute('src') || '', index));
|
|
49
|
+
const response = await uploadService({ files, mode: 'file' });
|
|
50
|
+
const uploadedImages = response && typeof response === 'object' && 'data' in response && Array.isArray(response.data)
|
|
51
|
+
? response.data
|
|
52
|
+
: [];
|
|
53
|
+
const uploadedUrls = uploadedImages.map(getUploadedImageUrl);
|
|
54
|
+
if (uploadedUrls.length !== images.length || uploadedUrls.some(uploadedUrl => !uploadedUrl)) {
|
|
55
|
+
throw new Error('UPLOAD_FAILED');
|
|
56
|
+
}
|
|
57
|
+
images.forEach((image, index) => {
|
|
58
|
+
image.setAttribute('src', uploadedUrls[index]);
|
|
59
|
+
});
|
|
60
|
+
const hasEmbeddedImage = Array.from(container.querySelectorAll('img')).some(image => DATA_IMAGE_PREFIX.test(image.getAttribute('src') || ''));
|
|
61
|
+
if (hasEmbeddedImage) {
|
|
62
|
+
throw new Error('UPLOAD_FAILED');
|
|
63
|
+
}
|
|
64
|
+
return container.innerHTML;
|
|
65
|
+
};
|