Skip to content

Commit 2150a62

Browse files
committed
feat: 프로필 아이콘 기능 추가
1 parent 9e9e5cc commit 2150a62

3 files changed

Lines changed: 87 additions & 8 deletions

File tree

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
function decodeJwt(token: string) {
2+
try {
3+
const payload = token.split('.')[1];
4+
const decoded = atob(payload.replace(/-/g, '+').replace(/_/g, '/'));
5+
return JSON.parse(decoded);
6+
} catch {
7+
return null;
8+
}
9+
}

frontend/src/widgets/header/Header.tsx

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import Logo from '@/assets/logo_medium.svg?react';
22
import LogoutButton from '@/features/auth/ui/LogoutButton';
33
import { ThemeToggleButton } from '@/shared/theme/ThemeToggleButton';
4-
import { User } from 'lucide-react';
54
import type { FC } from 'react';
65
import { Link } from 'react-router-dom';
6+
import ProfileButton from './ui/ProfileButton';
77

88
const Header: FC = () => {
99
return (
@@ -15,13 +15,7 @@ const Header: FC = () => {
1515
</div>
1616

1717
<div className='flex items-center gap-2'>
18-
<button
19-
type='button'
20-
aria-label='프로필'
21-
className='p-2 rounded-full cursor-pointer hover:bg-[var(--neutral-surface-bold)]'
22-
>
23-
<User className='w-6 h-6' />
24-
</button>
18+
<ProfileButton />
2519
<LogoutButton />
2620
<ThemeToggleButton />
2721
</div>
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import { User } from 'lucide-react';
2+
import { useState } from 'react';
3+
4+
// JWT 디코딩 함수
5+
function decodeJwt(token: string) {
6+
try {
7+
const payload = token.split('.')[1];
8+
const decoded = atob(payload.replace(/-/g, '+').replace(/_/g, '/'));
9+
return JSON.parse(decoded);
10+
} catch {
11+
return null;
12+
}
13+
}
14+
15+
// 타임스탬프 → 날짜 문자열 변환
16+
function formatDate(unix: number) {
17+
if (!unix) return '-';
18+
const date = new Date(unix * 1000);
19+
return date.toLocaleString();
20+
}
21+
22+
export function ProfileButton() {
23+
const [open, setOpen] = useState(false);
24+
const [decoded, setDecoded] = useState(null);
25+
26+
const handleClick = () => {
27+
const token = localStorage.getItem('token');
28+
if (token) {
29+
setDecoded(decodeJwt(token));
30+
} else {
31+
setDecoded(null);
32+
}
33+
setOpen(true);
34+
};
35+
36+
return (
37+
<div className='relative'>
38+
<button
39+
type='button'
40+
aria-label='프로필'
41+
className='p-2 rounded-full cursor-pointer hover:bg-[var(--neutral-surface-bold)]'
42+
onClick={handleClick}
43+
>
44+
<User className='w-6 h-6' />
45+
</button>
46+
{open && (
47+
<div className='absolute top-12 left-1/2 -translate-x-2/3 z-10 w-80 bg-white border border-[var(--neutral-border-default)] rounded-xl shadow-md p-5 flex flex-col items-center'>
48+
<div className='font-display-bold-16 mb-4'>내 프로필 정보</div>
49+
{decoded ? (
50+
<div className='w-full flex flex-col gap-2'>
51+
<div className='flex justify-between font-display-medium-16'>
52+
<span className='text-gray-500'>email</span>
53+
<span>{decoded.sub}</span>
54+
</div>
55+
<div className='flex justify-between font-display-medium-16'>
56+
<span className='text-gray-500'>로그인시각</span>
57+
<span>{formatDate(decoded.iat)}</span>
58+
</div>
59+
</div>
60+
) : (
61+
<div className='text-sm text-gray-400'>토큰이 없습니다.</div>
62+
)}
63+
<button
64+
type='button'
65+
className='mt-6 w-full py-2 rounded-md border font-display-medium-16 hover:bg-[var(--neutral-surface-bold)]'
66+
onClick={() => setOpen(false)}
67+
>
68+
닫기
69+
</button>
70+
</div>
71+
)}
72+
</div>
73+
);
74+
}
75+
76+
export default ProfileButton;

0 commit comments

Comments
 (0)