Skip to content

Commit e562171

Browse files
committed
feat(front): 마이페이지 추가 및 헤더 수정사항 반영, 로그인 실패 에러 메시지 수정
1 parent 54a8c17 commit e562171

File tree

8 files changed

+597
-19
lines changed

8 files changed

+597
-19
lines changed

front/src/app/(auth)/login/page.tsx

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,14 @@ export default function LoginPage() {
2020
const [email, setEmail] = useState('');
2121
const [password, setPassword] = useState('');
2222
const [loading, setLoading] = useState(false);
23+
const [errorMessage, setErrorMessage] = useState('');
2324

2425
async function submit(e: FormEvent) {
2526
e.preventDefault();
2627
const payload: LoginRequest = { email, password };
2728
try {
2829
setLoading(true);
30+
setErrorMessage(''); // 에러 메시지 초기화
2931
const res = await authApi.login(payload);
3032

3133
// 로그인 응답에서 사용자 정보 저장
@@ -36,7 +38,20 @@ export default function LoginPage() {
3638
// 페이지 리로드하여 상태 즉시 반영
3739
window.location.href = '/';
3840
} catch (e: any) {
39-
toast.push(`로그인 실패: ${e.message}`);
41+
console.error('로그인 에러:', e);
42+
43+
// 에러 메시지 파싱
44+
let errorMsg = '로그인에 실패했습니다.';
45+
46+
// ApiError 객체인 경우 원본 메시지 사용
47+
if (e.code === 'U001') {
48+
errorMsg = '아이디/비밀번호를 다시 확인해주세요!';
49+
} else if (e.message) {
50+
errorMsg = e.message;
51+
}
52+
53+
setErrorMessage(errorMsg);
54+
toast.push(`로그인 실패: ${errorMsg}`);
4055
} finally {
4156
setLoading(false);
4257
}
@@ -64,6 +79,14 @@ export default function LoginPage() {
6479
<Label htmlFor="password">비밀번호</Label>
6580
<Input id="password" type="password" value={password} onChange={(e) => setPassword(e.target.value)} required />
6681
</div>
82+
83+
{/* 에러 메시지 표시 */}
84+
{errorMessage && (
85+
<div className="p-3 text-sm text-destructive bg-destructive/10 border border-destructive/20 rounded-md">
86+
{errorMessage}
87+
</div>
88+
)}
89+
6790
<Button type="submit" className="w-full" size="lg" disabled={loading}>
6891
{loading ? '처리중...' : '로그인'}
6992
</Button>
Lines changed: 242 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,242 @@
1-
// 사용자 프로필 페이지
1+
"use client"
2+
3+
import { useState } from "react"
4+
import { useAuth } from "@/hooks/auth/useAuth"
5+
import { userApi } from "@/lib/api/user"
6+
import { Button } from "@/components/ui/Button"
7+
import { Input } from "@/components/ui/input"
8+
import { Label } from "@/components/ui/label"
9+
import { Card } from "@/components/ui/card"
10+
import { useToast } from "@/components/ui/Toast"
11+
import { User, Mail, Lock, Save } from "lucide-react"
12+
13+
export default function ProfilePage() {
14+
const { user, fetchUserInfo } = useAuth()
15+
const toast = useToast()
16+
17+
const [isEditingName, setIsEditingName] = useState(false)
18+
const [isEditingPassword, setIsEditingPassword] = useState(false)
19+
const [name, setName] = useState(user?.name || "")
20+
const [password, setPassword] = useState("")
21+
const [passwordCheck, setPasswordCheck] = useState("")
22+
const [isLoading, setIsLoading] = useState(false)
23+
24+
const handleNameUpdate = async () => {
25+
if (!name.trim()) {
26+
toast.push("이름을 입력해주세요.")
27+
return
28+
}
29+
30+
try {
31+
setIsLoading(true)
32+
await userApi.updateName({ name: name.trim() })
33+
toast.push("이름이 성공적으로 변경되었습니다.")
34+
setIsEditingName(false)
35+
await fetchUserInfo() // 사용자 정보 새로고침
36+
} catch (error) {
37+
console.error("이름 변경 실패:", error)
38+
toast.push("이름 변경에 실패했습니다.")
39+
} finally {
40+
setIsLoading(false)
41+
}
42+
}
43+
44+
const handlePasswordUpdate = async () => {
45+
if (!password.trim()) {
46+
toast.push("새 비밀번호를 입력해주세요.")
47+
return
48+
}
49+
50+
if (password !== passwordCheck) {
51+
toast.push("비밀번호가 일치하지 않습니다.")
52+
return
53+
}
54+
55+
if (password.length < 6) {
56+
toast.push("비밀번호는 6자 이상이어야 합니다.")
57+
return
58+
}
59+
60+
try {
61+
setIsLoading(true)
62+
await userApi.updatePassword({
63+
password: password.trim(),
64+
passwordCheck: passwordCheck.trim()
65+
})
66+
toast.push("비밀번호가 성공적으로 변경되었습니다.")
67+
setIsEditingPassword(false)
68+
setPassword("")
69+
setPasswordCheck("")
70+
} catch (error) {
71+
console.error("비밀번호 변경 실패:", error)
72+
toast.push("비밀번호 변경에 실패했습니다.")
73+
} finally {
74+
setIsLoading(false)
75+
}
76+
}
77+
78+
const cancelNameEdit = () => {
79+
setName(user?.name || "")
80+
setIsEditingName(false)
81+
}
82+
83+
const cancelPasswordEdit = () => {
84+
setPassword("")
85+
setPasswordCheck("")
86+
setIsEditingPassword(false)
87+
}
88+
89+
if (!user) {
90+
return (
91+
<div className="min-h-screen bg-background flex items-center justify-center">
92+
<div className="text-center">
93+
<h1 className="text-2xl font-bold mb-4">로그인이 필요합니다</h1>
94+
<p className="text-muted-foreground">마이페이지를 보려면 로그인해주세요.</p>
95+
</div>
96+
</div>
97+
)
98+
}
99+
100+
return (
101+
<div className="min-h-screen bg-background">
102+
<div className="container mx-auto px-4 sm:px-6 lg:px-8 py-16">
103+
<div className="mx-auto max-w-2xl">
104+
<div className="mb-8 text-center">
105+
<h1 className="text-3xl font-bold mb-2">마이페이지</h1>
106+
<p className="text-muted-foreground">개인정보를 수정할 수 있습니다.</p>
107+
</div>
108+
109+
<div className="space-y-6">
110+
{/* 이메일 정보 (수정 불가) */}
111+
<Card className="p-6">
112+
<div className="flex items-center gap-3 mb-4">
113+
<Mail className="h-5 w-5 text-muted-foreground" />
114+
<h2 className="text-lg font-semibold">이메일</h2>
115+
</div>
116+
<div className="flex items-center justify-between">
117+
<span className="text-muted-foreground">{user.email}</span>
118+
<span className="text-xs bg-muted px-2 py-1 rounded">고정</span>
119+
</div>
120+
</Card>
121+
122+
{/* 이름 수정 */}
123+
<Card className="p-6">
124+
<div className="flex items-center gap-3 mb-4">
125+
<User className="h-5 w-5 text-muted-foreground" />
126+
<h2 className="text-lg font-semibold">이름</h2>
127+
</div>
128+
129+
{isEditingName ? (
130+
<div className="space-y-4">
131+
<div>
132+
<Label htmlFor="name">새 이름</Label>
133+
<Input
134+
id="name"
135+
value={name}
136+
onChange={(e) => setName(e.target.value)}
137+
placeholder="이름을 입력하세요"
138+
disabled={isLoading}
139+
/>
140+
</div>
141+
<div className="flex gap-2">
142+
<Button
143+
onClick={handleNameUpdate}
144+
disabled={isLoading}
145+
size="sm"
146+
>
147+
<Save className="h-4 w-4 mr-2" />
148+
{isLoading ? "저장 중..." : "저장"}
149+
</Button>
150+
<Button
151+
variant="outline"
152+
onClick={cancelNameEdit}
153+
disabled={isLoading}
154+
size="sm"
155+
>
156+
취소
157+
</Button>
158+
</div>
159+
</div>
160+
) : (
161+
<div className="flex items-center justify-between">
162+
<span className="text-muted-foreground">{user.name}</span>
163+
<Button
164+
variant="outline"
165+
onClick={() => setIsEditingName(true)}
166+
size="sm"
167+
>
168+
수정
169+
</Button>
170+
</div>
171+
)}
172+
</Card>
173+
174+
{/* 비밀번호 수정 */}
175+
<Card className="p-6">
176+
<div className="flex items-center gap-3 mb-4">
177+
<Lock className="h-5 w-5 text-muted-foreground" />
178+
<h2 className="text-lg font-semibold">비밀번호</h2>
179+
</div>
180+
181+
{isEditingPassword ? (
182+
<div className="space-y-4">
183+
<div>
184+
<Label htmlFor="password">새 비밀번호</Label>
185+
<Input
186+
id="password"
187+
type="password"
188+
value={password}
189+
onChange={(e) => setPassword(e.target.value)}
190+
placeholder="새 비밀번호를 입력하세요"
191+
disabled={isLoading}
192+
/>
193+
</div>
194+
<div>
195+
<Label htmlFor="passwordCheck">비밀번호 확인</Label>
196+
<Input
197+
id="passwordCheck"
198+
type="password"
199+
value={passwordCheck}
200+
onChange={(e) => setPasswordCheck(e.target.value)}
201+
placeholder="비밀번호를 다시 입력하세요"
202+
disabled={isLoading}
203+
/>
204+
</div>
205+
<div className="flex gap-2">
206+
<Button
207+
onClick={handlePasswordUpdate}
208+
disabled={isLoading}
209+
size="sm"
210+
>
211+
<Save className="h-4 w-4 mr-2" />
212+
{isLoading ? "저장 중..." : "저장"}
213+
</Button>
214+
<Button
215+
variant="outline"
216+
onClick={cancelPasswordEdit}
217+
disabled={isLoading}
218+
size="sm"
219+
>
220+
취소
221+
</Button>
222+
</div>
223+
</div>
224+
) : (
225+
<div className="flex items-center justify-between">
226+
<span className="text-muted-foreground">••••••••</span>
227+
<Button
228+
variant="outline"
229+
onClick={() => setIsEditingPassword(true)}
230+
size="sm"
231+
>
232+
수정
233+
</Button>
234+
</div>
235+
)}
236+
</Card>
237+
</div>
238+
</div>
239+
</div>
240+
</div>
241+
)
242+
}

0 commit comments

Comments
 (0)