Skip to content

Commit c463b80

Browse files
authored
Merge pull request #87 from codeit-2team/feat/84
Feat/84 헤더 마이페이지, 로그아웃 드롭다운 추가 및 로그인 성공 UI 적용
2 parents d9b554a + ffc4242 commit c463b80

File tree

5 files changed

+108
-24
lines changed

5 files changed

+108
-24
lines changed

src/app/api/experiences/getExperiences.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ interface ExperienceResponse {
1414
cursorId: number;
1515
}
1616

17-
const teamId = process.env.NEXT_PUBLIC_TEAM_ID;
18-
const url = `/${teamId}/activities`;
17+
const baseUrl = process.env.NEXT_PUBLIC_API_SERVER_URL;
18+
const url = `${baseUrl}/activities`;
1919
const validSorts = ['price_asc', 'price_desc'];
2020

2121
export const getExperiences = async ({ page, category, sort, keyword }: Params) => {

src/app/api/experiences/getPopularExperiences.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ interface ResponseData {
77
activities: Experience[];
88
}
99

10-
const teamId = process.env.NEXT_PUBLIC_TEAM_ID;
11-
const url = `/${teamId}/activities`;
10+
const baseUrl = process.env.NEXT_PUBLIC_API_SERVER_URL;
11+
const url = `${baseUrl}/activities`;
1212

1313
export const getPopularExperiences = async (): Promise<ResponseData> => {
1414
const res = await instance.get<ResponseData>(url, {

src/components/Header.tsx

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,26 @@
11
'use client';
22

33
import Link from 'next/link';
4-
import Image from 'next/image';
5-
import { useState } from 'react';
64
import IconLogo from '@assets/svg/logo';
75
import IconBell from '@assets/svg/bell';
6+
import useUserStore from '@/stores/authStore';
7+
import { useRouter } from 'next/navigation';
8+
import ProfileDropdown from '@/components/ProfileDropdown';
89

910
export default function Header() {
10-
// 실제로는 로그인 여부를 전역 상태나 context로 받아와야 함
11-
// test하려면 로그인 상태를 true로 변경
12-
// 빌드 에러로 인해 setIsLoggedIn 변수 앞에 _를 붙여 의도적으로 사용하지 않음을 표시
13-
const [isLoggedIn, _setIsLoggedIn] = useState(false);
11+
const router = useRouter();
12+
const user = useUserStore((state) => state.user);
13+
const setUser = useUserStore((state) => state.setUser);
14+
const isLoggedIn = !!user;
15+
16+
// 로그아웃 처리
17+
const handleLogout = () => {
18+
setUser(null);
19+
router.push('/');
20+
};
1421

1522
return (
16-
<header className='fixed z-10 w-full border-b border-gray-300 bg-white'>
23+
<header className='fixed z-100 w-full border-b border-gray-300 bg-white'>
1724
<div className='mx-auto flex min-h-70 max-w-1200 items-center justify-between px-20 py-20'>
1825
{/* 로고 */}
1926
<Link
@@ -24,28 +31,23 @@ export default function Header() {
2431
</Link>
2532

2633
{/* 우측 메뉴 */}
27-
<div className='text-md flex items-center gap-24 text-black'>
34+
<div className='text-md relative flex items-center gap-24 text-black'>
2835
{isLoggedIn ? (
2936
<>
3037
{/* 알림 아이콘 */}
3138
<button aria-label='알림' className='hover:text-primary'>
3239
<IconBell />
3340
</button>
3441

35-
{/* 세로 구분선 */}
42+
{/* 구분선 */}
3643
<div className='mx-12 h-22 w-px bg-gray-300' />
3744

38-
{/* 유저 프로필 */}
39-
<div className='flex items-center gap-2'>
40-
<Image
41-
src='/img/sample-user.png' // 사용자 프로필 이미지
42-
alt='프로필 이미지'
43-
width={32}
44-
height={32}
45-
className='rounded-full border border-gray-300'
46-
/>
47-
<span>김보경</span>
48-
</div>
45+
{/* 프로필 드롭다운 */}
46+
<ProfileDropdown
47+
nickname={user.nickname}
48+
profileImageUrl={user.profileImageUrl}
49+
onLogout={handleLogout}
50+
/>
4951
</>
5052
) : (
5153
<>

src/components/ProfileDropdown.tsx

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
'use client';
2+
3+
import Image from 'next/image';
4+
import Link from 'next/link';
5+
import { useEffect, useRef, useState } from 'react';
6+
import { usePathname } from 'next/navigation';
7+
import ProfileDefaultIcon from '@assets/svg/profile-default';
8+
9+
import { ProfileDropdownProps } from '@/types/profileDropdownTypes';
10+
11+
export default function ProfileDropdown({ nickname, profileImageUrl, onLogout }: ProfileDropdownProps) {
12+
const [isOpen, setIsOpen] = useState(false);
13+
const dropdownRef = useRef<HTMLDivElement>(null);
14+
const pathname = usePathname();
15+
16+
// 외부 클릭 시 드롭다운 닫기
17+
useEffect(() => {
18+
const handleClickOutside = (e: MouseEvent) => {
19+
if (dropdownRef.current && !dropdownRef.current.contains(e.target as Node)) {
20+
setIsOpen(false);
21+
}
22+
};
23+
document.addEventListener('mousedown', handleClickOutside);
24+
return () => document.removeEventListener('mousedown', handleClickOutside);
25+
}, []);
26+
27+
// 경로 변경 시 드롭다운 닫기
28+
useEffect(() => {
29+
setIsOpen(false);
30+
}, [pathname]);
31+
32+
return (
33+
<div className='relative' ref={dropdownRef}>
34+
{/* 프로필 영역 (드롭다운 토글) */}
35+
<div
36+
className='flex items-center gap-8 cursor-pointer'
37+
onClick={() => setIsOpen((prev) => !prev)}
38+
>
39+
{profileImageUrl ? (
40+
<Image
41+
src={profileImageUrl}
42+
alt='프로필 이미지'
43+
width={32}
44+
height={32}
45+
className='rounded-full border border-gray-300'
46+
/>
47+
) : (
48+
<div className='size-32 rounded-full border border-gray-300 overflow-hidden'>
49+
<ProfileDefaultIcon size={32} />
50+
</div>
51+
)}
52+
<span>{nickname || '사용자'}</span>
53+
</div>
54+
55+
{/* 드롭다운 메뉴 */}
56+
{isOpen && (
57+
<div
58+
className='absolute top-50 right-0 z-50 mt-12 w-140 rounded-md border border-gray-200 bg-white shadow-md'
59+
onClick={(e) => e.stopPropagation()}
60+
>
61+
<Link
62+
href='/mypage'
63+
className='block w-full px-16 py-12 text-left hover:bg-gray-50'
64+
>
65+
마이페이지
66+
</Link>
67+
<button
68+
onClick={onLogout}
69+
className='w-full px-16 py-12 text-left hover:bg-gray-50'
70+
>
71+
로그아웃
72+
</button>
73+
</div>
74+
)}
75+
</div>
76+
);
77+
}

src/types/profileDropdownTypes.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export interface ProfileDropdownProps {
2+
nickname: string;
3+
profileImageUrl: string | null;
4+
onLogout: () => void;
5+
}

0 commit comments

Comments
 (0)