Skip to content

Commit 8905884

Browse files
author
jyn
committed
Merge branch 'develop' into feature/jeon
2 parents d337c73 + 1fd7c52 commit 8905884

File tree

10 files changed

+112
-17
lines changed

10 files changed

+112
-17
lines changed

src/api/postUpload.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { api } from './api';
22

3-
export default async function uploadImage(file) {
3+
export default async function uploadImage(file, onProgress) {
44
const formData = new FormData();
55
formData.append('file', file);
66
formData.append('upload_preset', 'profile_upload');
@@ -14,6 +14,12 @@ export default async function uploadImage(file) {
1414
headers: {
1515
'Content-Type': 'multipart/form-data',
1616
},
17+
onUploadProgress: (e) => {
18+
if (onProgress) {
19+
const percent = Math.round((e.loaded * 100) / e.total);
20+
onProgress(percent);
21+
}
22+
},
1723
},
1824
);
1925
return res.data.secure_url;

src/components/BackgroundCard/BackgroundCard.jsx

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import styles from './BackgroundCard.module.scss';
33
import uploadImage from '../../api/postUpload';
44
import checked from '../../assets/images/checked.svg';
55
import upload from '../../assets/images/upload.svg';
6+
import UploadProgressBar from '../common/UploadProgressBar';
67

78
export default function BackgroundCard({
89
type, // 'color' 또는 'image' 또는 'upload'
@@ -13,7 +14,10 @@ export default function BackgroundCard({
1314
isLoading,
1415
onLoad,
1516
onSelect,
17+
isUploading,
18+
setIsUploading,
1619
}) {
20+
const [uploadProgress, setUploadProgress] = useState(0);
1721
const [imageUrl, setImageUrl] = useState(url);
1822

1923
const getClassName = () => {
@@ -33,12 +37,21 @@ export default function BackgroundCard({
3337
const file = e.target.files[0];
3438
if (!file) return;
3539

40+
e.target.value = null;
41+
42+
setIsUploading(true);
43+
3644
try {
37-
const uploadedUrl = await uploadImage(file);
45+
const uploadedUrl = await uploadImage(file, (percent) => {
46+
setUploadProgress(percent);
47+
});
3848
setImageUrl(uploadedUrl);
3949
onSelect?.(uploadedUrl);
4050
} catch {
4151
alert('이미지 업로드 실패했습니다.');
52+
} finally {
53+
setIsUploading(false);
54+
setUploadProgress(0);
4255
}
4356
};
4457

@@ -115,6 +128,7 @@ export default function BackgroundCard({
115128
className={styles['background-card__check-icon']}
116129
/>
117130
)}
131+
{isUploading && <UploadProgressBar progress={uploadProgress} />}
118132
</li>
119133
);
120134
}

src/components/BackgroundCard/BackgroundCard.module.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@
8181
display: flex;
8282
justify-content: center;
8383
align-items: center;
84+
position: relative;
8485
}
8586
.background-card__upload-btn {
8687
display: flex;

src/components/UserProfileSelector/UserProfileSelector.jsx

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,18 @@ import { useEffect, useState, useRef } from 'react';
22
import getProfileImages from '../../api/getProfileImages.js';
33
import uploadImage from '../../api/postUpload.js';
44
import DEFAULT_PROFILE_IMAGE from '../../constants/image.js';
5+
import UploadProgressBar from '../common/UploadProgressBar.jsx';
56
import styles from './UserProfileSelector.module.scss';
67

78
export default function UserProfileSelector({
89
value = DEFAULT_PROFILE_IMAGE,
910
onSelect,
11+
isUploading,
12+
setIsUploading,
1013
}) {
1114
const [profileImages, setProfileImages] = useState([]);
1215
const [loadedImages, setLoadedImages] = useState({});
16+
const [uploadProgress, setUploadProgress] = useState(0);
1317
const fileInput = useRef(null);
1418

1519
useEffect(() => {
@@ -28,11 +32,21 @@ export default function UserProfileSelector({
2832
const file = e.target.files[0];
2933
if (!file) return;
3034

35+
e.target.value = null;
36+
37+
setIsUploading(true);
38+
3139
try {
32-
const uploadedUrl = await uploadImage(file);
40+
const uploadedUrl = await uploadImage(file, (percent) => {
41+
setUploadProgress(percent);
42+
});
43+
3344
onSelect?.(uploadedUrl);
3445
} catch (error) {
3546
alert('이미지 업로드 실패했습니다.');
47+
} finally {
48+
setIsUploading(false);
49+
setUploadProgress(0);
3650
}
3751
};
3852

@@ -44,12 +58,17 @@ export default function UserProfileSelector({
4458
<div className={styles['profile-select']}>
4559
<h2 className={styles['profile-select__title']}>프로필 이미지</h2>
4660
<div className={styles['profile-select__content']}>
47-
<img
48-
src={value}
49-
alt="선택된 프로필 및 업로드 이미지"
50-
className={styles['profile-select__selected-image']}
51-
onClick={triggerFileSeletor}
52-
/>
61+
<div className={styles['profile-select__selected-wrapper']}>
62+
<img
63+
src={value}
64+
alt="선택된 프로필 및 업로드 이미지"
65+
className={styles['profile-select__selected-image']}
66+
onClick={triggerFileSeletor}
67+
/>
68+
69+
{isUploading && <UploadProgressBar progress={uploadProgress} />}
70+
</div>
71+
5372
<input
5473
type="file"
5574
accept="image/*"
@@ -69,8 +88,11 @@ export default function UserProfileSelector({
6988
alt={`profile-${idx}`}
7089
className={`${styles['profile-select__image']} ${
7190
value === url ? styles['profile-select__image--selected'] : ''
72-
} ${!loadedImages[url] ? styles['profile-select__image--loading'] : ''}`}
73-
onClick={() => onSelect?.(url)}
91+
} ${!loadedImages[url] ? styles['profile-select__image--loading'] : ''}
92+
${isUploading ? styles['profile-select__image--disabled'] : ''}`}
93+
onClick={() => {
94+
if (!isUploading) onSelect?.(url);
95+
}}
7496
onLoad={() => handleImageLoad(url)}
7597
/>
7698
))}

src/components/UserProfileSelector/UserProfileSelector.module.scss

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@
2323
cursor: pointer;
2424
}
2525

26+
&__selected-wrapper {
27+
position: relative;
28+
}
29+
2630
&__content {
2731
display: flex;
2832
align-items: center;
@@ -49,6 +53,12 @@
4953
gap: 4px;
5054
}
5155

56+
&__image--disabled {
57+
pointer-events: none;
58+
opacity: 0.4;
59+
filter: grayscale(60%);
60+
}
61+
5262
&__image {
5363
width: 56px;
5464
border: 1px solid $gray-200;
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import styles from './UploadProgressBar.module.scss';
2+
3+
export default function UploadProgressBar({ progress = 0 }) {
4+
return (
5+
<div className={styles.overlay}>
6+
<div className={styles.bar}>
7+
<div className={styles.fill} style={{ width: `${progress}%` }} />
8+
</div>
9+
</div>
10+
);
11+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
@use '../../assets/styles/variables.scss' as *;
2+
3+
.overlay {
4+
display: flex;
5+
justify-content: center;
6+
align-items: center;
7+
position: absolute;
8+
inset: 0;
9+
border-radius: 50%;
10+
z-index: 10;
11+
background-color: rgba(255, 255, 255, 0.6);
12+
}
13+
14+
.bar {
15+
overflow: hidden;
16+
width: 60%;
17+
height: 8px;
18+
border-radius: 5px;
19+
background-color: $gray-200;
20+
}
21+
22+
.fill {
23+
width: 0%;
24+
height: 100%;
25+
background-color: $purple-700;
26+
transition: width 0.3s ease;
27+
}

src/pages/CreateRecipient/CreateRecipient.jsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ export default function CreateRecipient() {
1919
const [imageLoading, setImageLoading] = useState([]);
2020
const [uploadedImageUrl, setUploadedImageUrl] = useState(null);
2121
const [isCreating, setIsCreating] = useState(false);
22+
const [isUploading, setIsUploading] = useState(false);
23+
2224
const navigate = useNavigate();
2325

2426
useEffect(() => {
@@ -175,6 +177,8 @@ export default function CreateRecipient() {
175177
onClick={() => handleImageClick(data.length + 1)}
176178
isSelected={selectedImage === data.length + 1}
177179
onSelect={handleUploadImage}
180+
isUploading={isUploading}
181+
setIsUploading={setIsUploading}
178182
/>
179183
</ul>
180184
)}
@@ -184,7 +188,7 @@ export default function CreateRecipient() {
184188
<Button
185189
type="button"
186190
onClick={handleButtonClick}
187-
disabled={!value.trim() || isCreating}
191+
disabled={!value.trim() || isCreating || isUploading}
188192
>
189193
생성하기
190194
</Button>

src/pages/MessageForm/MessageForm.jsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export default function MessageForm() {
2121
const [font, setFont] = useState('Noto Sans');
2222
const [isRestored, setIsRestored] = useState(false);
2323
const [isCreating, setIsCreating] = useState(false);
24+
const [isUploading, setIsUploading] = useState(false);
2425

2526
const stripHtml = (html) => html.replace(/<[^>]+>/g, '').trim();
2627
const isValid = sender.trim() !== '' && stripHtml(message) !== '';
@@ -144,6 +145,8 @@ export default function MessageForm() {
144145
<UserProfileSelector
145146
value={profileImage}
146147
onSelect={setProfileImage}
148+
isUploading={isUploading}
149+
setIsUploading={setIsUploading}
147150
/>
148151
</div>
149152
<div className={styles['message-form__relationship-select']}>
@@ -165,7 +168,7 @@ export default function MessageForm() {
165168
<Button
166169
type="button"
167170
onClick={handleSubmit}
168-
disabled={!isValid || isCreating}
171+
disabled={!isValid || isCreating || isUploading}
169172
>
170173
생성하기
171174
</Button>

src/pages/Recipient/Recipient.jsx

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -144,10 +144,7 @@ export default function Recipient({ showDelete }) {
144144

145145
const selectedCard = messages.find((card) => card.id === selectedCardId);
146146

147-
if (!postData || messages.length < 0)
148-
return (
149-
<div className={styles['loading-message']}>페이지를 불러오는 중..</div>
150-
);
147+
if (!postData || messages.length < 0) return;
151148

152149
return (
153150
<>

0 commit comments

Comments
 (0)