Skip to content

Commit 6f5ba0c

Browse files
authored
Merge pull request #144 from part3-4team-Taskify/feature/Gnb
[Feat, Style] mypage: css & 반응형 수정, 정보 변경 시 toast 발생, toast 사라지면 새로고침 / Gnb: 멤버 목록 반응형 적용
2 parents 9f16b4f + 89c8194 commit 6f5ba0c

File tree

11 files changed

+245
-201
lines changed

11 files changed

+245
-201
lines changed
Lines changed: 69 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,16 @@
11
import { useState } from "react";
2+
import { useRouter } from "next/router";
23
import { changePassword } from "@/api/changepassword";
3-
import MypageModal from "../modal/MypageModal";
4-
import Input from "../input/Input";
5-
import Image from "next/image";
4+
import Input from "@/components/input/Input";
5+
import { toast } from "react-toastify";
66

77
export default function ChangePassword() {
8+
const router = useRouter();
89
const [password, setPassword] = useState("");
910
const [newPassword, setNewPassword] = useState("");
1011
const [checkNewpassword, setCheckNewPassword] = useState("");
1112
const [isSubmitting, setIsSubmitting] = useState(false);
12-
const [errorMessage, setErrorMessage] = useState("");
13-
const [successMessage, setSuccessMessage] = useState("");
14-
const [showCheckNewPassword, setShowCheckNewPassword] = useState(false);
15-
const [showSuccessModal, setShowSuccessModal] = useState(false);
16-
const [showErrorModal, setShowErrorModal] = useState(false);
1713

18-
const toggleCheckNewPasswordVisibility = () =>
19-
setShowCheckNewPassword(!showCheckNewPassword);
2014
const isPasswordMismatch =
2115
newPassword && checkNewpassword && newPassword !== checkNewpassword;
2216
const isDisabled =
@@ -30,8 +24,6 @@ export default function ChangePassword() {
3024
if (isDisabled) return;
3125

3226
setIsSubmitting(true);
33-
setErrorMessage("");
34-
setSuccessMessage("");
3527

3628
const result = await changePassword({ password, newPassword });
3729

@@ -40,124 +32,87 @@ export default function ChangePassword() {
4032
result.status === 400
4133
? result.message || "현재 비밀번호가 올바르지 않습니다."
4234
: "비밀번호 변경 중 오류가 발생했습니다.";
43-
44-
setErrorMessage(msg);
45-
setShowErrorModal(true);
35+
toast.error(msg);
4636
setIsSubmitting(false);
37+
4738
return;
4839
}
49-
50-
setSuccessMessage("비밀번호가 성공적으로 변경되었습니다.");
51-
setShowSuccessModal(true);
40+
toast.success("비밀번호가 변경되었습니다.");
5241
setPassword("");
5342
setNewPassword("");
5443
setCheckNewPassword("");
5544
setIsSubmitting(false);
45+
setTimeout(() => {
46+
router.reload();
47+
}, 1700);
5648
};
5749

5850
return (
59-
<div className="sm:w-[672px] sm:h-[466px] w-[284px] h-[454px] bg-white rounded-[16px] shadow-md p-[24px] flex flex-col">
60-
<h2 className="text-[18px] sm:text-[24px] font-bold mb-4">
51+
<div
52+
className="flex flex-col w-[284px] sm:w-[548px] md:w-[672px] min:h-[454px] sm:min-h-[466px]
53+
bg-white rounded-[16px] p-[24px]"
54+
>
55+
<h2 className="text-black3 text-[18px] sm:text-[24px] font-bold mb-4">
6156
비밀번호 변경
6257
</h2>
6358

64-
<div>
65-
<div className="-mt-2">
66-
<Input
67-
type="password"
68-
name="password"
69-
label="현재 비밀번호"
70-
labelClassName="font-16r"
71-
placeholder="현재 비밀번호 입력"
72-
value={password}
73-
onChange={setPassword}
74-
pattern=".{8,}"
75-
className="max-w-[620px]"
76-
/>
77-
<Input
78-
type="password"
79-
name="password"
80-
label="새 비밀번호"
81-
labelClassName="font-16r"
82-
placeholder="새 비밀번호 입력"
83-
value={newPassword}
84-
onChange={setNewPassword}
85-
pattern=".{8,}"
86-
invalidMessage="8자 이상 입력해주세요."
87-
className="max-w-[620px]"
88-
/>
59+
<div className="flex flex-col text-black3 my-3 gap-4">
60+
<Input
61+
type="password"
62+
name="password"
63+
label="현재 비밀번호"
64+
labelClassName="text-[14px] sm:text-base"
65+
placeholder="현재 비밀번호 입력"
66+
value={password}
67+
onChange={setPassword}
68+
pattern=".{8,}"
69+
className="max-w-[624px] "
70+
/>
71+
<Input
72+
type="password"
73+
name="password"
74+
label="새 비밀번호"
75+
labelClassName="text-[14px] sm:text-base"
76+
placeholder="새 비밀번호 입력"
77+
value={newPassword}
78+
onChange={setNewPassword}
79+
pattern=".{8,}"
80+
invalidMessage="8자 이상 입력해주세요."
81+
className="max-w-[624px]"
82+
/>
83+
<Input
84+
type="password"
85+
name="passwordCheck"
86+
label="새 비밀번호 확인"
87+
labelClassName="text-[14px] sm:text-base"
88+
placeholder="새 비밀번호 입력"
89+
value={checkNewpassword}
90+
onChange={(value) => setCheckNewPassword(value)}
91+
forceInvalid={!!checkNewpassword && checkNewpassword !== newPassword}
92+
invalidMessage="비밀번호가 일치하지 않습니다."
93+
className="max-w-[624px]"
94+
/>
8995

90-
<label className="mb-2 text-sm sm:text-base text-black mt-3">
91-
새 비밀번호 확인
92-
</label>
93-
<div className="relative w-full">
94-
<input
95-
type={showCheckNewPassword ? "text" : "password"}
96-
value={checkNewpassword}
97-
placeholder="새 비밀번호 입력"
98-
onChange={(e) => setCheckNewPassword(e.target.value)}
99-
className={`mt-3 sm:w-[624px] sm:h-[50px] w-[236px] h-[50px] px-[16px] pr-12 rounded-[8px] transition-colors focus:outline-none
100-
${
101-
checkNewpassword
102-
? checkNewpassword === newPassword
103-
? "border border-gray-300"
104-
: "border border-[var(--color-red)]"
105-
: "border border-gray-300 focus:border-purple-500"
106-
}`}
107-
/>
108-
<button
109-
type="button"
110-
onClick={toggleCheckNewPasswordVisibility}
111-
className="absolute right-4 top-6 flex size-6 items-center justify-center"
112-
>
113-
<Image
114-
src={
115-
showCheckNewPassword
116-
? "/svgs/eye-on.svg"
117-
: "/svgs/eye-off.svg"
118-
}
119-
alt="비밀번호 표시 토글"
120-
width={20}
121-
height={20}
122-
className=" w-5 h-5"
123-
/>
124-
</button>
96+
<button
97+
className={`w-full h-[54px]
98+
rounded-[8px] text-lg font-medium text-white
99+
${
100+
isDisabled
101+
? "bg-gray-300 cursor-not-allowed"
102+
: "bg-[var(--primary)] text-white cursor-pointer"
103+
}`}
104+
onClick={() => handleChangePassword()}
105+
disabled={isDisabled || isSubmitting}
106+
>
107+
변경
108+
</button>
125109

126-
{checkNewpassword && checkNewpassword !== newPassword && (
127-
<p className="mt-2 font-16m block text-[var(--color-red)]">
128-
비밀번호가 일치하지 않습니다.
129-
</p>
130-
)}
131-
</div>
132-
</div>
110+
{checkNewpassword && checkNewpassword !== newPassword && (
111+
<p className="font-14r block text-[var(--color-red)]">
112+
비밀번호가 일치하지 않습니다.
113+
</p>
114+
)}
133115
</div>
134-
135-
{errorMessage && (
136-
<p className="text-red-500 text-sm mt-4">{errorMessage}</p>
137-
)}
138-
{successMessage && (
139-
<p className="text-green-600 text-sm mt-4">{successMessage}</p>
140-
)}
141-
<MypageModal
142-
isOpen={showSuccessModal}
143-
onClose={() => setShowSuccessModal(false)}
144-
message="비밀번호 변경에 성공하였습니다."
145-
/>
146-
147-
<MypageModal
148-
isOpen={showErrorModal}
149-
onClose={() => setShowErrorModal(false)}
150-
message="비밀번호 변경에 실패하였습니다."
151-
/>
152-
<button
153-
className={`mt-2.5 cursor-pointer w-full sm:w-[624px] h-[54px]
154-
rounded-[8px] text-lg font-medium
155-
${isDisabled ? "bg-gray-300 cursor-not-allowed" : "bg-[#5A3FFF] text-white"}`}
156-
onClick={() => handleChangePassword()}
157-
disabled={isDisabled || isSubmitting}
158-
>
159-
변경
160-
</button>
161116
</div>
162117
);
163118
}

src/components/card/Profile.tsx

Lines changed: 22 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
import { useState, useEffect } from "react";
2-
import Input from "../input/Input";
2+
import { useRouter } from "next/router";
33
import Image from "next/image";
44
import { getUserInfo, updateProfile, uploadProfileImage } from "@/api/users";
5-
import MypageModal from "../modal/MypageModal";
5+
import Input from "@/components/input/Input";
6+
import { toast } from "react-toastify";
67

78
export default function ProfileCard() {
9+
const router = useRouter();
810
const [image, setImage] = useState<string | null>(null);
911
const [nickname, setNickname] = useState("");
1012
const [email, setEmail] = useState("");
1113
const [preview, setPreview] = useState<string | null>(null);
12-
const [showSuccessModal, setShowSuccessModal] = useState(false);
13-
const [showErrorModal, setShowErrorModal] = useState(false);
1414

1515
const fetchUserData = async () => {
1616
try {
@@ -38,7 +38,7 @@ export default function ProfileCard() {
3838
setImage(response.profileImageUrl); // 서버에서 받은 URL 저장
3939
} catch (error) {
4040
console.error("이미지 업로드 실패:", error);
41-
alert("이미지 업로드에 실패했습니다.");
41+
toast.error("이미지 업로드에 실패하였습니다.");
4242
}
4343
}
4444
};
@@ -52,10 +52,13 @@ export default function ProfileCard() {
5252

5353
try {
5454
await updateProfile(userProfile);
55-
setShowSuccessModal(true);
55+
toast.success("프로필 변경이 완료되었습니다.");
56+
setTimeout(() => {
57+
router.reload();
58+
}, 1700);
5659
} catch (error) {
57-
console.error("프로필 저장 실패:", error);
58-
setShowErrorModal(true);
60+
console.error("프로필 변경 실패:", error);
61+
toast.error("프로필 변경에 실패하였습니다.");
5962
}
6063
};
6164

@@ -64,13 +67,15 @@ export default function ProfileCard() {
6467
}, []);
6568

6669
return (
67-
<div className="sm:w-[672px] sm:h-[366px] w-[284px] h-[496px] bg-white rounded-[16px] shadow-md p-[24px] flex flex-col">
70+
<div className="flex flex-col w-[284px] sm:w-[548px] md:w-[672px] h-[496px] sm:h-[366px] bg-white rounded-[16px] p-[24px]">
6871
{/* 프로필 제목 */}
69-
<h2 className="text-[18px] sm:text-[24px] font-bold mb-4">프로필</h2>
72+
<h2 className="text-black3 text-[18px] sm:text-[24px] font-bold mb-4">
73+
프로필
74+
</h2>
7075
{/* 프로필 이미지 및 입력 폼 영역 */}
7176
<div className="flex flex-col sm:flex-row items-center sm:items-start">
7277
{/* 프로필 이미지 업로드 영역 */}
73-
<div className="sm:mr:0 mr-29 w-[120px] flex-shrink-0 mb-4 sm:mb-0">
78+
<div className="sm:mr:0 mr-[119px] w-[120px] flex-shrink-0 mb-4 sm:mb-0">
7479
<div className="sm:w-[182px] sm:h-[182px] w-[100px] h-[100px] rounded-md flex items-center justify-center cursor-pointer bg-[#F5F5F5] border-transparent">
7580
<label className="cursor-pointer w-full h-full flex items-center justify-center">
7681
{image ? (
@@ -95,38 +100,27 @@ export default function ProfileCard() {
95100
</div>
96101

97102
{/* 입력 폼 */}
98-
<div className="flex flex-col sm:ml-[-15px] w-full sm:mt-0 mt-5 sm:w-[400px]">
103+
<div className="flex flex-col sm:ml-[-15px] sm:mt-0 mt-5 w-[252px] sm:w-[276px] md:w-[400px] gap-4">
99104
<Input
100105
type="email"
101106
name="email"
102107
label="이메일"
103-
labelClassName="font-16r"
104-
value={email}
108+
placeholder={email}
109+
labelClassName="text-black3 text-[14px] sm:text-base"
105110
readOnly
106111
/>
107-
108112
<Input
109113
type="text"
110114
name="nickname"
111115
label="닉네임"
112-
labelClassName="font-16r"
116+
labelClassName="text-black3 text-[14px] sm:text-base"
113117
value={nickname}
114118
placeholder="닉네임을 입력하세요"
115119
onChange={(value: string) => setNickname(value)}
120+
className="text-black4"
116121
/>
117-
<MypageModal
118-
isOpen={showSuccessModal}
119-
onClose={() => setShowSuccessModal(false)}
120-
message="프로필 변경이 완료되었습니다."
121-
/>
122-
<MypageModal
123-
isOpen={showErrorModal}
124-
onClose={() => setShowErrorModal(false)}
125-
message="프로필 변경에 실패하였습니다"
126-
/>
127-
128122
<button
129-
className="cursor-pointer w-full sm:w-[400px] h-[54px] bg-[#5A3FFF] text-white rounded-[8px] text-lg font-medium mt-3"
123+
className="cursor-pointer w-[252px] sm:w-[276px] md:w-[400px] h-[54px] bg-[var(--primary)] text-white rounded-[8px] text-lg font-medium mt-3"
130124
onClick={handleSave}
131125
>
132126
저장

src/components/common/CustomToastContainer.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
"use client";
22
import "react-toastify/dist/ReactToastify.css";
3-
import { ToastContainer, Slide } from "react-toastify";
3+
import { ToastContainer, Slide, toast } from "react-toastify";
44

55
const CustomToastContainer = () => {
66
return (
77
<ToastContainer
88
position="top-center"
9-
autoClose={2500}
9+
autoClose={1700}
1010
hideProgressBar={true}
1111
closeButton={false}
1212
pauseOnHover={false}

0 commit comments

Comments
 (0)