Skip to content

Commit 372cb0c

Browse files
authored
Merge pull request #159 from codeit-sprint-part3-6team/#155_마이페이지-이슈-수정
#155 마이페이지 이슈 수정
2 parents 96e213c + 049121b commit 372cb0c

File tree

10 files changed

+197
-53
lines changed

10 files changed

+197
-53
lines changed

public/ic/ic_imageDelete.svg

Lines changed: 5 additions & 0 deletions
Loading

src/components/common/sidebar/Sidebar.module.css

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
padding: 20px 12px 0 8px;
66
position: fixed;
77
border-right: 1px solid var(--gray-medium);
8+
z-index: 1;
89
}
910

1011
.menu {
@@ -106,6 +107,7 @@
106107
height: 344px;
107108
padding: 32px;
108109
border-radius: 10px;
110+
z-index: 11;
109111
}
110112

111113
.modal h2 {

src/components/common/sidebar/Sidebar.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,18 @@ export default function Sidebar() {
8181
setDashboards(sidebarDashboards);
8282
}, [sidebarDashboards]);
8383

84+
useEffect(() => {
85+
// 모달 dim 부분 스크롤 막기
86+
if (showModal) {
87+
document.body.style.overflow = 'hidden'; // 스크롤 비활성화
88+
} else {
89+
document.body.style.overflow = ''; // 기본 스크롤 상태로 복구
90+
}
91+
return () => {
92+
document.body.style.overflow = ''; // 컴포넌트가 unmount 될 때도 스크롤 상태 복구
93+
};
94+
}, [showModal]);
95+
8496
return (
8597
<div className={styles.sidebar}>
8698
<Link href="/" className={styles.logo}>

src/components/product/mypage/ChangePassword.module.css

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,35 @@
3636
}
3737
}
3838

39+
.input-wrap {
40+
display: flex;
41+
align-items: center;
42+
position: relative;
43+
margin-bottom: 16px;
44+
}
45+
46+
.error-wrap {
47+
display: flex;
48+
align-items: center;
49+
position: relative;
50+
margin-bottom: 8px;
51+
}
52+
53+
.visible-button {
54+
width: 24px;
55+
height: 24px;
56+
border: none;
57+
position: absolute;
58+
right: 16px;
59+
background-color: var(--white);
60+
cursor: pointer;
61+
}
62+
3963
.input {
4064
width: 100%;
4165
height: 50px;
4266
padding: 15px 16px;
43-
margin-bottom: 16px;
67+
4468
border-radius: 8px;
4569
border: 1px solid var(--gray-medium);
4670
font-weight: 400;
@@ -59,19 +83,15 @@
5983
}
6084

6185
.bottom-input {
62-
margin-bottom: 24px;
86+
margin-bottom: 8px;
6387
}
6488

6589
.error-input {
6690
border: 1px solid red;
6791
}
6892

69-
.error-margin {
70-
margin-bottom: 2px;
71-
}
72-
7393
.error-message {
7494
color: red;
7595
font-size: 14px;
76-
margin-bottom: 16px;
96+
margin-bottom: 24px;
7797
}

src/components/product/mypage/ChangePassword.tsx

Lines changed: 79 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import clsx from 'clsx';
33
import changePassword from '@/lib/mypage/changePassword';
44
import CDSButton from '@/components/common/button/CDSButton';
55
import AuthModal from '@/components/common/modal/auth/AuthModal';
6+
import Visibility from 'public/ic/ic_visibility.svg';
7+
import InVisibility from 'public/ic/ic_invisibility.svg';
68
import styles from './ChangePassword.module.css';
79

810
interface CheangePasswordValue {
@@ -29,30 +31,40 @@ export default function ChangePassword({
2931
const [values, setValues] = useState(initialValue);
3032
const [errorMessage, setErrorMessage] = useState<string | null>(null);
3133
const [modal, setModal] = useState(false);
34+
const [passwordVisibility, setPasswordVisibility] = useState({
35+
current: false,
36+
new: false,
37+
confirm: false,
38+
});
39+
const [isLoading, setIsLoading] = useState(false);
40+
41+
const handleClick = (field: 'current' | 'new' | 'confirm') => {
42+
setPasswordVisibility((prevVisibility) => ({
43+
...prevVisibility,
44+
[field]: !prevVisibility[field],
45+
}));
46+
};
3247

3348
const handleCancelClick = () => {
3449
setModal(false);
3550
};
3651

3752
const handleChangePassword = async () => {
53+
setIsLoading(true);
3854
try {
3955
const { password, newPassword } = values;
4056
const putData = { password, newPassword };
4157
await changePassword(putData);
4258

43-
setValues({
44-
password: '',
45-
newPassword: '',
46-
confirmPassword: '',
47-
error: null,
48-
});
59+
setValues(initialValue);
4960
setModal(true);
5061
} catch (e) {
5162
setModal(true);
5263
setErrorMessage(e.message);
64+
} finally {
65+
setIsLoading(false);
5366
}
5467
};
55-
5668
const handleBlur = () => {
5769
const { newPassword, confirmPassword } = values;
5870
if (newPassword !== confirmPassword) {
@@ -79,40 +91,71 @@ export default function ChangePassword({
7991
<p className={styles.title}>비밀번호 변경</p>
8092
<div>
8193
<p className={styles[`sub-title`]}>현재 비밀번호</p>
82-
<input
83-
value={values.password}
84-
onChange={(e) => setValues({ ...values, password: e.target.value })}
85-
className={styles.input}
86-
placeholder="비밀번호 입력"
87-
/>
94+
<div className={styles[`input-wrap`]}>
95+
<input
96+
value={values.password}
97+
onChange={(e) => setValues({ ...values, password: e.target.value })}
98+
className={styles.input}
99+
placeholder="비밀번호 입력"
100+
type={passwordVisibility.current ? 'text' : 'password'}
101+
/>
102+
<button
103+
className={styles[`visible-button`]}
104+
type="button"
105+
onClick={() => handleClick('current')}
106+
>
107+
{passwordVisibility.current ? <Visibility /> : <InVisibility />}
108+
</button>
109+
</div>
88110
</div>
89111
<div>
90112
<p className={styles[`sub-title`]}>새 비밀번호</p>
91-
<input
92-
value={values.newPassword}
93-
onChange={(e) =>
94-
setValues({ ...values, newPassword: e.target.value })
95-
}
96-
className={styles.input}
97-
placeholder="새 비밀번호 입력"
98-
onBlur={handleBlur}
99-
/>
113+
<div className={styles[`input-wrap`]}>
114+
<input
115+
value={values.newPassword}
116+
onChange={(e) =>
117+
setValues({ ...values, newPassword: e.target.value })
118+
}
119+
className={styles.input}
120+
placeholder="새 비밀번호 입력"
121+
onBlur={handleBlur}
122+
type={passwordVisibility.new ? 'text' : 'password'}
123+
/>
124+
<button
125+
className={styles[`visible-button`]}
126+
type="button"
127+
onClick={() => handleClick('new')}
128+
>
129+
{passwordVisibility.new ? <Visibility /> : <InVisibility />}
130+
</button>
131+
</div>
100132
</div>
101133
<div>
102134
<p className={styles[`sub-title`]}>새 비밀번호 확인</p>
103-
<input
104-
value={values.confirmPassword}
105-
onChange={(e) =>
106-
setValues({ ...values, confirmPassword: e.target.value })
107-
}
108-
className={clsx(
109-
styles.input,
110-
values.error ? styles['error-input'] : styles[`bottom-input`],
111-
values.error ? styles['error-margin'] : '',
112-
)}
113-
placeholder="새 비밀번호 입력"
114-
onBlur={handleBlur}
115-
/>
135+
<div
136+
className={values.error ? styles[`error-wrap`] : styles[`input-wrap`]}
137+
>
138+
<input
139+
value={values.confirmPassword}
140+
onChange={(e) =>
141+
setValues({ ...values, confirmPassword: e.target.value })
142+
}
143+
className={clsx(
144+
styles.input,
145+
values.error ? styles['error-input'] : styles[`bottom-input`],
146+
)}
147+
placeholder="새 비밀번호 입력"
148+
onBlur={handleBlur}
149+
type={passwordVisibility.confirm ? 'text' : 'password'}
150+
/>
151+
<button
152+
className={styles[`visible-button`]}
153+
type="button"
154+
onClick={() => handleClick('confirm')}
155+
>
156+
{passwordVisibility.confirm ? <Visibility /> : <InVisibility />}
157+
</button>
158+
</div>
116159
{values.error && (
117160
<p className={styles['error-message']}>{values.error}</p>
118161
)}
@@ -121,7 +164,7 @@ export default function ChangePassword({
121164
<CDSButton
122165
onClick={handleChangePassword}
123166
btnType="profile_save"
124-
disabled={!isFormValid}
167+
disabled={!isFormValid || isLoading}
125168
>
126169
변경
127170
</CDSButton>

src/components/product/mypage/ModifyProfile.tsx

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import ProfileImageInput from './ProfileImageInput';
99
import ProfileInfoForm from './ProfileInfoForm';
1010
import ProfileModifyModal from './ProfileModifyModal';
1111
import styles from './ModifyProfile.module.css';
12+
import { toast } from 'react-toastify';
1213

1314
interface ModifyValue {
1415
nickname: string;
@@ -37,6 +38,10 @@ export default function ModifyProfile() {
3738
}
3839
}, [user]);
3940

41+
const handleImageDelete = () => {
42+
setPreview(null);
43+
};
44+
4045
const handleCancelClick = () => {
4146
setModal(false);
4247
};
@@ -55,6 +60,8 @@ export default function ModifyProfile() {
5560
let imageUrl = values.profileImageUrl;
5661
if (image) {
5762
imageUrl = await uploadImage(image);
63+
} else if (!preview) {
64+
imageUrl = null;
5865
}
5966
const putData = {
6067
nickname: values.nickname,
@@ -77,16 +84,32 @@ export default function ModifyProfile() {
7784
);
7885
setModal(true);
7986
} catch (error) {
80-
console.error(error);
87+
toast.error(error.message);
8188
}
8289
};
8390

8491
const handleImageChange = (e: React.ChangeEvent<HTMLInputElement>) => {
8592
const file = e.target.files?.[0];
8693
if (file) {
94+
const allowedExtensions = ['png', 'gif', 'jpg', 'jpeg'];
95+
const fileExtension = file.name.split('.').pop()?.toLowerCase();
96+
if (!fileExtension || !allowedExtensions.includes(fileExtension)) {
97+
toast.warning(
98+
<div>
99+
허용되지 않는 파일 형식입니다
100+
<br /> (png, gif, jpg만 등록 가능)
101+
</div>,
102+
);
103+
104+
return;
105+
}
106+
87107
setImage(file);
88108
const imgURL = URL.createObjectURL(file);
89109
setPreview(imgURL);
110+
} else {
111+
setImage(null);
112+
setPreview(null);
90113
}
91114
};
92115

@@ -97,6 +120,7 @@ export default function ModifyProfile() {
97120
<ProfileImageInput
98121
preview={preview}
99122
onImageChange={handleImageChange}
123+
onImageDelete={handleImageDelete}
100124
/>
101125
<div>
102126
<ProfileInfoForm

src/components/product/mypage/ProfileImageInput.module.css

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,13 @@
2222
object-fit: cover;
2323
}
2424

25+
.img-delete {
26+
position: absolute;
27+
top: 8px;
28+
right: 8px;
29+
cursor: pointer;
30+
}
31+
2532
.img-input {
2633
display: none;
2734
}

0 commit comments

Comments
 (0)