Skip to content

Commit 6ead7eb

Browse files
author
kim
committed
refactor: defer thumbnail saving on save button
1 parent b09d76d commit 6ead7eb

File tree

4 files changed

+73
-37
lines changed

4 files changed

+73
-37
lines changed

src/modules/settings/ChatbotSettings.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import ChatbotAvatar from '../common/ChatbotAvatar';
2424
import { useChatbotAvatar } from '../common/useChatbotAvatar';
2525
import { ChatbotEditionView } from './chatbot/ChatbotEditingView';
2626
import { ChatbotPromptDisplay } from './chatbot/ChatbotPromptDisplay';
27+
import { useSaveAvatar } from './useNewAvatar';
2728

2829
const useChatbotSetting = () => {
2930
const { mutateAsync: postSetting } = mutations.usePostAppSetting();
@@ -70,16 +71,21 @@ function ChatbotSettings() {
7071

7172
const { saveSetting, chatbotCue, chatbotName, initialPrompt, chatbotAvatar } =
7273
useChatbotSetting();
74+
const saveNewAvatar = useSaveAvatar();
7375

7476
const [isEditing, setIsEditing] = useState(false);
7577

7678
const doneEditing = (): void => {
7779
setIsEditing(false);
7880
};
7981

80-
const handleOnSave = async (data: ChatbotPromptSettings) => {
82+
const handleOnSave = async (data: ChatbotPromptSettings, avatar?: Blob) => {
8183
await saveSetting(data);
8284

85+
if (avatar) {
86+
await saveNewAvatar(avatar);
87+
}
88+
8389
// close the editing view
8490
doneEditing();
8591
};

src/modules/settings/chatbot/ChatbotAvatarEditor.tsx

Lines changed: 16 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@ import { Badge, IconButton, styled } from '@mui/material';
44

55
import { PenIcon } from 'lucide-react';
66

7-
import { CHATBOT_AVATAR_APP_SETTING_NAME } from '@/config/appSetting';
8-
import { mutations } from '@/config/queryClient';
97
import ChatbotAvatar from '@/modules/common/ChatbotAvatar';
108
import { useChatbotAvatar } from '@/modules/common/useChatbotAvatar';
119

@@ -30,33 +28,22 @@ const EditButton = styled(IconButton)(() => ({
3028
},
3129
}));
3230

33-
export function ChatbotAvatarEditor() {
31+
export function ChatbotAvatarEditor({
32+
onChange,
33+
}: Readonly<{ onChange: (avatar: Blob) => void }>) {
3434
const fileInput = useRef<HTMLInputElement>(null);
35-
const { mutateAsync: uploadThumbnail } = mutations.useUploadAppSettingFile();
36-
const { mutateAsync: deleteAvatar } = mutations.useDeleteAppSetting();
37-
const {
38-
avatar,
39-
avatarSetting,
40-
isLoading: isAvatarLoading,
41-
} = useChatbotAvatar();
42-
const [isUploading, setIsUploading] = useState(false);
4335

44-
const onChange: ChangeEventHandler<HTMLInputElement> = async (e) => {
36+
// currently saved avatar
37+
const { avatar, isLoading: isAvatarLoading } = useChatbotAvatar();
38+
39+
// local avatar to display
40+
const [newAvatar, setNewAvatar] = useState<Blob>();
41+
42+
const onSelect: ChangeEventHandler<HTMLInputElement> = (e) => {
4543
if (e.target.files) {
46-
setIsUploading(true);
47-
try {
48-
await uploadThumbnail({
49-
file: e.target.files[0],
50-
name: CHATBOT_AVATAR_APP_SETTING_NAME,
51-
});
52-
// delete previous avatar
53-
if (avatarSetting) {
54-
await deleteAvatar({ id: avatarSetting.id });
55-
}
56-
} catch (error) {
57-
console.error(error);
58-
}
59-
setIsUploading(false);
44+
const file = e.target.files[0];
45+
onChange(file);
46+
setNewAvatar(file);
6047
}
6148
};
6249

@@ -65,7 +52,6 @@ export function ChatbotAvatarEditor() {
6552
anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
6653
badgeContent={
6754
<EditButton
68-
disabled={isUploading}
6955
type="button"
7056
color="info"
7157
onClick={() => {
@@ -75,7 +61,7 @@ export function ChatbotAvatarEditor() {
7561
<PenIcon />
7662
<VisuallyHiddenInput
7763
ref={fileInput}
78-
onChange={onChange}
64+
onChange={onSelect}
7965
type="file"
8066
accept="image/png, image/jpeg, image/jpg"
8167
/>
@@ -85,8 +71,8 @@ export function ChatbotAvatarEditor() {
8571
>
8672
<ChatbotAvatar
8773
size="medium"
88-
avatar={avatar}
89-
isLoading={isAvatarLoading || isUploading}
74+
avatar={newAvatar || avatar}
75+
isLoading={isAvatarLoading}
9076
/>
9177
</Badge>
9278
);

src/modules/settings/chatbot/ChatbotEditingView.tsx

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { useTranslation } from 'react-i18next';
44

55
import { Box, Button, Stack, TextField } from '@mui/material';
66

7-
import type { ChatbotPromptSettings } from '@/config/appSetting';
7+
import { type ChatbotPromptSettings } from '@/config/appSetting';
88
import { TextArea } from '@/modules/common/TextArea';
99

1010
import { ChatbotAvatarEditor } from './ChatbotAvatarEditor';
@@ -16,7 +16,7 @@ type Props = {
1616
cue: string;
1717
prompt: string;
1818
};
19-
onSave: (data: ChatbotPromptSettings) => void;
19+
onSave: (data: ChatbotPromptSettings, avatar?: Blob) => Promise<void>;
2020
};
2121

2222
function ChatbotEditionView({
@@ -27,6 +27,8 @@ function ChatbotEditionView({
2727
const [prompt, setPrompt] = useState(initialValue.prompt);
2828
const [cue, setCue] = useState(initialValue.cue);
2929
const [name, setName] = useState(initialValue.name);
30+
const [newAvatar, setNewAvatar] = useState<Blob>();
31+
const [isSaving, setIsSaving] = useState(false);
3032

3133
const [unsavedChanges, setUnsavedChanges] = useState(false);
3234

@@ -45,14 +47,23 @@ function ChatbotEditionView({
4547
setUnsavedChanges(true);
4648
};
4749

48-
const handleSave = (): void => {
50+
const onChangeAvatar = (avatar: Blob) => {
51+
setNewAvatar(avatar);
52+
setUnsavedChanges(true);
53+
};
54+
55+
const handleSave = async () => {
4956
if (prompt) {
57+
setIsSaving(true);
58+
5059
const data: ChatbotPromptSettings = {
5160
initialPrompt: prompt,
5261
chatbotCue: cue,
5362
chatbotName: name,
5463
};
55-
onSave(data);
64+
await onSave(data, newAvatar);
65+
66+
setIsSaving(false);
5667
}
5768
};
5869

@@ -64,7 +75,7 @@ function ChatbotEditionView({
6475
alignItems="center"
6576
gap={2}
6677
>
67-
<ChatbotAvatarEditor />
78+
<ChatbotAvatarEditor onChange={onChangeAvatar} />
6879
<TextField
6980
name={t('CHATBOT_NAME_LABEL')}
7081
label={t('CHATBOT_NAME_LABEL')}
@@ -100,8 +111,9 @@ function ChatbotEditionView({
100111
<Box alignSelf="flex-end">
101112
<Button
102113
onClick={handleSave}
103-
disabled={!unsavedChanges}
114+
disabled={!unsavedChanges || isSaving}
104115
variant="outlined"
116+
loading={isSaving}
105117
>
106118
{unsavedChanges ? t('SAVE_LABEL') : t('SAVED_LABEL')}
107119
</Button>
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { useCallback } from 'react';
2+
3+
import { CHATBOT_AVATAR_APP_SETTING_NAME } from '@/config/appSetting';
4+
import { mutations } from '@/config/queryClient';
5+
6+
import { useChatbotAvatar } from '../common/useChatbotAvatar';
7+
8+
export const useSaveAvatar = () => {
9+
const { mutateAsync: uploadThumbnail } = mutations.useUploadAppSettingFile();
10+
const { mutateAsync: deleteAvatar } = mutations.useDeleteAppSetting();
11+
const { avatarSetting } = useChatbotAvatar();
12+
13+
const uploadNewAvatar = useCallback(
14+
async (newAvatar: Blob) => {
15+
try {
16+
await uploadThumbnail({
17+
file: newAvatar,
18+
name: CHATBOT_AVATAR_APP_SETTING_NAME,
19+
});
20+
// delete previous avatar
21+
if (avatarSetting) {
22+
await deleteAvatar({ id: avatarSetting.id });
23+
}
24+
} catch (error) {
25+
console.error(error);
26+
}
27+
},
28+
[avatarSetting, deleteAvatar, uploadThumbnail],
29+
);
30+
31+
return uploadNewAvatar;
32+
};

0 commit comments

Comments
 (0)