Skip to content

Commit 2ccf190

Browse files
feat: Added max length validation in file upload modal in file description (#1037)
* feat: added max length validation in file uplode modal in file description * fetch message limit from RC * fixed lint format * implemented review points * fix: resolve prettier formatting in AttachmentPreview * remove comments * fix: remove empty catch block to resolve no-empty lint error Made-with: Cursor
1 parent daf291d commit 2ccf190

File tree

3 files changed

+129
-61
lines changed

3 files changed

+129
-61
lines changed
Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
11
import { create } from 'zustand';
22

3+
const DEFAULT_MESSAGE_LIMIT = 5000;
4+
35
const useSettingsStore = create((set) => ({
4-
messageLimit: 5000,
5-
setMessageLimit: (messageLimit) => set(() => ({ messageLimit })),
6+
messageLimit: DEFAULT_MESSAGE_LIMIT,
7+
setMessageLimit: (messageLimit) =>
8+
set(() => ({
9+
messageLimit:
10+
typeof messageLimit === 'number' && Number.isFinite(messageLimit)
11+
? messageLimit
12+
: DEFAULT_MESSAGE_LIMIT,
13+
})),
614
}));
715

816
export default useSettingsStore;

packages/react/src/views/AttachmentPreview/AttachmentPreview.js

Lines changed: 112 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,18 @@
11
import React, { useContext, useState, useRef, useEffect } from 'react';
22
import { css } from '@emotion/react';
3-
import { Box, Icon, Button, Input, Modal } from '@embeddedchat/ui-elements';
3+
import {
4+
Box,
5+
Icon,
6+
Button,
7+
Input,
8+
Modal,
9+
useTheme,
10+
} from '@embeddedchat/ui-elements';
411
import useAttachmentWindowStore from '../../store/attachmentwindow';
512
import CheckPreviewType from './CheckPreviewType';
613
import RCContext from '../../context/RCInstance';
714
import { useMessageStore, useMemberStore } from '../../store';
15+
import useSettingsStore from '../../store/settingsStore';
816
import getAttachmentPreviewStyles from './AttachmentPreview.styles';
917
import { parseEmoji } from '../../lib/emoji';
1018
import MembersList from '../Mentions/MembersList';
@@ -13,29 +21,30 @@ import useSearchMentionUser from '../../hooks/useSearchMentionUser';
1321

1422
const AttachmentPreview = () => {
1523
const { RCInstance, ECOptions } = useContext(RCContext);
24+
const { theme } = useTheme();
1625
const styles = getAttachmentPreviewStyles();
1726

1827
const toggle = useAttachmentWindowStore((state) => state.toggle);
1928
const data = useAttachmentWindowStore((state) => state.data);
2029
const setData = useAttachmentWindowStore((state) => state.setData);
30+
2131
const [isPending, setIsPending] = useState(false);
2232
const messageRef = useRef(null);
2333
const [showMembersList, setShowMembersList] = useState(false);
2434
const [filteredMembers, setFilteredMembers] = useState([]);
2535
const [mentionIndex, setMentionIndex] = useState(-1);
2636
const [startReadMentionUser, setStartReadMentionUser] = useState(false);
27-
const [keyPressed, setKeyPressed] = useState(null);
2837

29-
const [fileName, setFileName] = useState(data?.name);
38+
const [fileName, setFileName] = useState(data?.name ?? '');
39+
useEffect(() => setFileName(data?.name ?? ''), [data?.name]);
3040

31-
const threadId = useMessageStore((state) => state.threadMainMessage?._id);
32-
const handleFileName = (e) => {
33-
setFileName(e.target.value);
34-
};
41+
const [description, setDescription] = useState('');
42+
const charCount = description.length;
43+
const msgMaxLength = useSettingsStore((s) => s?.messageLimit);
44+
const isOverLimit = msgMaxLength && charCount > msgMaxLength;
3545

36-
const { members } = useMemberStore((state) => ({
37-
members: state.members,
38-
}));
46+
const threadId = useMessageStore((state) => state.threadMainMessage?._id);
47+
const { members } = useMemberStore((state) => ({ members: state.members }));
3948

4049
const searchMentionUser = useSearchMentionUser(
4150
members,
@@ -46,47 +55,44 @@ const AttachmentPreview = () => {
4655
setShowMembersList
4756
);
4857

58+
const handleFileName = (e) => setFileName(e.target.value);
59+
4960
const handleFileDescription = (e) => {
50-
const description = e.target.value;
51-
messageRef.current.value = parseEmoji(description);
52-
searchMentionUser(description);
61+
const raw = e.target.value || '';
62+
setDescription(raw);
63+
64+
if (messageRef.current && typeof messageRef.current.value !== 'undefined') {
65+
messageRef.current.value = raw;
66+
}
67+
68+
searchMentionUser(raw);
5369
};
5470

5571
const submit = async () => {
72+
if (isPending) return;
73+
if (msgMaxLength && description.length > msgMaxLength) return;
74+
5675
setIsPending(true);
57-
await RCInstance.sendAttachment(
58-
data,
59-
fileName,
60-
messageRef.current.value,
61-
ECOptions?.enableThreads ? threadId : undefined
62-
);
63-
toggle();
64-
setData(null);
65-
if (isPending) {
76+
try {
77+
await RCInstance.sendAttachment(
78+
data,
79+
fileName,
80+
parseEmoji(description),
81+
ECOptions?.enableThreads ? threadId : undefined
82+
);
83+
toggle();
84+
setData(null);
85+
} finally {
6686
setIsPending(false);
6787
}
6888
};
6989

70-
useEffect(() => {
71-
const keyHandler = (e) => {
72-
if (e.key === 'Enter') {
73-
e.preventDefault();
74-
setKeyPressed('Enter');
75-
}
76-
};
77-
78-
document.addEventListener('keydown', keyHandler);
79-
return () => {
80-
document.removeEventListener('keydown', keyHandler);
81-
};
82-
}, []);
83-
84-
useEffect(() => {
85-
if (keyPressed === 'Enter') {
90+
const onDescKeyDown = (e) => {
91+
if (e.key === 'Enter' && !e.shiftKey) {
92+
e.preventDefault();
8693
submit();
87-
setKeyPressed(null);
8894
}
89-
}, [keyPressed, submit]);
95+
};
9096

9197
return (
9298
<Modal onClose={toggle}>
@@ -98,11 +104,12 @@ const AttachmentPreview = () => {
98104
css={css`
99105
margin-right: 0.5rem;
100106
`}
101-
/>{' '}
107+
/>
102108
File Upload
103109
</Modal.Title>
104110
<Modal.Close onClick={toggle} />
105111
</Modal.Header>
112+
106113
<Modal.Content>
107114
<Box css={styles.modalContent}>
108115
<Box
@@ -113,6 +120,7 @@ const AttachmentPreview = () => {
113120
>
114121
<CheckPreviewType data={data} />
115122
</Box>
123+
116124
<Box
117125
css={css`
118126
margin: 30px;
@@ -129,9 +137,7 @@ const AttachmentPreview = () => {
129137
File name
130138
</Box>
131139
<Input
132-
onChange={(e) => {
133-
handleFileName(e);
134-
}}
140+
onChange={handleFileName}
135141
value={fileName}
136142
type="text"
137143
css={styles.input}
@@ -150,6 +156,7 @@ const AttachmentPreview = () => {
150156
>
151157
File description
152158
</Box>
159+
153160
<Box css={styles.fileDescription}>
154161
<Box css={styles.mentionListContainer}>
155162
{showMembersList && (
@@ -161,21 +168,73 @@ const AttachmentPreview = () => {
161168
setFilteredMembers={setFilteredMembers}
162169
setStartReadMentionUser={setStartReadMentionUser}
163170
setShowMembersList={setShowMembersList}
164-
css={css`
165-
width: auto;
166-
`}
167171
/>
168172
)}
169173
</Box>
174+
170175
<Input
171-
onChange={(e) => {
172-
handleFileDescription(e);
173-
}}
176+
onChange={handleFileDescription}
177+
onKeyDown={onDescKeyDown}
174178
type="text"
175-
css={styles.input}
176179
placeholder="Description"
177180
ref={messageRef}
181+
value={description}
182+
css={css`
183+
${styles.input};
184+
border-color: ${isOverLimit
185+
? theme.colors.destructive
186+
: null};
187+
color: ${isOverLimit ? theme.colors.destructive : null};
188+
`}
178189
/>
190+
191+
{msgMaxLength && (
192+
<Box
193+
css={css`
194+
width: 100%;
195+
display: flex;
196+
justify-content: space-between;
197+
align-items: center;
198+
margin-top: 6px;
199+
gap: 12px;
200+
font-size: 0.875rem;
201+
`}
202+
>
203+
<Box
204+
css={css`
205+
color: ${isOverLimit
206+
? theme.colors.destructive
207+
: 'transparent'};
208+
font-weight: 500;
209+
text-align: left;
210+
flex: 1 1 auto;
211+
white-space: nowrap;
212+
overflow: hidden;
213+
text-overflow: ellipsis;
214+
`}
215+
aria-hidden={!isOverLimit}
216+
role={isOverLimit ? 'alert' : undefined}
217+
>
218+
{isOverLimit
219+
? `Cannot upload file, description is over the ${msgMaxLength} character limit`
220+
: ''}
221+
</Box>
222+
223+
<Box
224+
css={css`
225+
color: ${isOverLimit
226+
? theme.colors.destructive
227+
: '#6b7280'};
228+
min-width: 68px;
229+
text-align: right;
230+
flex: 0 0 auto;
231+
`}
232+
aria-hidden="true"
233+
>
234+
({charCount}/{msgMaxLength})
235+
</Box>
236+
</Box>
237+
)}
179238
</Box>
180239
</Box>
181240
</Box>
@@ -190,12 +249,8 @@ const AttachmentPreview = () => {
190249
<Button type="secondary" onClick={toggle}>
191250
Cancel
192251
</Button>
193-
<Button
194-
disabled={isPending}
195-
onClick={() => {
196-
submit();
197-
}}
198-
>
252+
253+
<Button disabled={isPending || isOverLimit} onClick={submit}>
199254
{isPending ? 'Sending...' : 'Send'}
200255
</Button>
201256
</Modal.Footer>

packages/react/src/views/ChatHeader/ChatHeader.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,8 +150,13 @@ const ChatHeader = ({
150150

151151
useEffect(() => {
152152
const getMessageLimit = async () => {
153-
const messageLimitObj = await RCInstance.getMessageLimit();
154-
setMessageLimit(messageLimitObj?.value);
153+
try {
154+
const messageLimitObj = await RCInstance.getMessageLimit();
155+
setMessageLimit(messageLimitObj?.value);
156+
} catch (e) {
157+
console.error('Failed to fetch message limit', e);
158+
setMessageLimit(undefined);
159+
}
155160
};
156161

157162
const setMessageAllowed = async () => {

0 commit comments

Comments
 (0)