Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,11 @@ import type { NextConfig } from 'next';
const nextConfig: NextConfig = {
// Docker 배포를 위한 standalone 모드 활성화
// 해당 설정은 프로덕션 빌드 시 필요한 파일만 .next/standalone 폴더에 복사됨.
images: {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저도 이 부분 오류나서 수정했는데 ㅋㅋ 동일하시네용

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ㅋㅋㅋㅋ동일이슈,,

domains: ['sprint-fe-project.s3.ap-northeast-2.amazonaws.com'],
},
output: 'standalone',

// 외부 이미지 도메인 허용
images: {
domains: ['sprint-fe-project.s3.ap-northeast-2.amazonaws.com'],
remotePatterns: [
{
protocol: 'https',
Expand Down
4 changes: 2 additions & 2 deletions src/app/api/experiences/getExperiences.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ interface ExperienceResponse {
cursorId: number;
}

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

export const getExperiences = async ({ page, category, sort, keyword }: Params) => {
Expand Down
4 changes: 2 additions & 2 deletions src/app/api/experiences/getPopularExperiences.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ interface ResponseData {
activities: Experience[];
}

const teamId = process.env.NEXT_PUBLIC_TEAM_ID;
const url = `/${teamId}/activities`;
const baseUrl = process.env.NEXT_PUBLIC_API_SERVER_URL;
const url = `${baseUrl}/activities`;

export const getPopularExperiences = async (): Promise<ResponseData> => {
const res = await instance.get<ResponseData>(url, {
Expand Down
42 changes: 22 additions & 20 deletions src/components/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,26 @@
'use client';

import Link from 'next/link';
import Image from 'next/image';
import { useState } from 'react';
import IconLogo from '@assets/svg/logo';
import IconBell from '@assets/svg/bell';
import useUserStore from '@/stores/authStore';
import { useRouter } from 'next/navigation';
import ProfileDropdown from '@/components/ProfileDropdown';

export default function Header() {
// 실제로는 로그인 여부를 전역 상태나 context로 받아와야 함
// test하려면 로그인 상태를 true로 변경
// 빌드 에러로 인해 setIsLoggedIn 변수 앞에 _를 붙여 의도적으로 사용하지 않음을 표시
const [isLoggedIn, _setIsLoggedIn] = useState(false);
const router = useRouter();
const user = useUserStore((state) => state.user);
const setUser = useUserStore((state) => state.setUser);
const isLoggedIn = !!user;

// 로그아웃 처리
const handleLogout = () => {
setUser(null);
router.push('/');
};

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

{/* 우측 메뉴 */}
<div className='text-md flex items-center gap-24 text-black'>
<div className='text-md relative flex items-center gap-24 text-black'>
{isLoggedIn ? (
<>
{/* 알림 아이콘 */}
<button aria-label='알림' className='hover:text-primary'>
<IconBell />
</button>

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

{/* 유저 프로필 */}
<div className='flex items-center gap-2'>
<Image
src='/img/sample-user.png' // 사용자 프로필 이미지
alt='프로필 이미지'
width={32}
height={32}
className='rounded-full border border-gray-300'
/>
<span>김보경</span>
</div>
{/* 프로필 드롭다운 */}
<ProfileDropdown
nickname={user.nickname}
profileImageUrl={user.profileImageUrl}
onLogout={handleLogout}
/>
</>
) : (
<>
Expand Down
77 changes: 77 additions & 0 deletions src/components/ProfileDropdown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
'use client';

import Image from 'next/image';
import Link from 'next/link';
import { useEffect, useRef, useState } from 'react';
import { usePathname } from 'next/navigation';
import ProfileDefaultIcon from '@assets/svg/profile-default';

import { ProfileDropdownProps } from '@/types/profileDropdownTypes';

export default function ProfileDropdown({ nickname, profileImageUrl, onLogout }: ProfileDropdownProps) {
const [isOpen, setIsOpen] = useState(false);
const dropdownRef = useRef<HTMLDivElement>(null);
const pathname = usePathname();

// 외부 클릭 시 드롭다운 닫기
useEffect(() => {
const handleClickOutside = (e: MouseEvent) => {
if (dropdownRef.current && !dropdownRef.current.contains(e.target as Node)) {
setIsOpen(false);
}
};
document.addEventListener('mousedown', handleClickOutside);
return () => document.removeEventListener('mousedown', handleClickOutside);
}, []);

// 경로 변경 시 드롭다운 닫기
useEffect(() => {
setIsOpen(false);
}, [pathname]);

return (
<div className='relative' ref={dropdownRef}>
{/* 프로필 영역 (드롭다운 토글) */}
<div
className='flex items-center gap-8 cursor-pointer'
onClick={() => setIsOpen((prev) => !prev)}
Comment on lines +35 to +37
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

접근성 개선을 위한 키보드 이벤트 처리 추가를 권장합니다.

현재 클릭 이벤트만 처리하고 있는데, 키보드 접근성을 위해 Enter나 Space 키 처리를 추가하는 것이 좋겠습니다.

다음과 같이 키보드 이벤트 처리를 추가할 수 있습니다:

      <div
        className='flex items-center gap-8 cursor-pointer'
+       role='button'
+       tabIndex={0}
        onClick={() => setIsOpen((prev) => !prev)}
+       onKeyDown={(e) => {
+         if (e.key === 'Enter' || e.key === ' ') {
+           e.preventDefault();
+           setIsOpen((prev) => !prev);
+         }
+       }}
      >
🤖 Prompt for AI Agents
In src/components/ProfileDropdown.tsx around lines 35 to 37, the onClick handler
toggles the dropdown but lacks keyboard accessibility. Add an onKeyDown event
handler that listens for Enter and Space key presses to toggle the dropdown
state, ensuring keyboard users can open and close the menu. Also, add
tabIndex={0} to make the div focusable by keyboard navigation.

>
{profileImageUrl ? (
<Image
src={profileImageUrl}
alt='프로필 이미지'
width={32}
height={32}
className='rounded-full border border-gray-300'
/>
) : (
<div className='size-32 rounded-full border border-gray-300 overflow-hidden'>
<ProfileDefaultIcon size={32} />
</div>
)}
<span>{nickname || '사용자'}</span>
</div>

{/* 드롭다운 메뉴 */}
{isOpen && (
<div
className='absolute top-50 right-0 z-50 mt-12 w-140 rounded-md border border-gray-200 bg-white shadow-md'
onClick={(e) => e.stopPropagation()}
>
<Link
href='/mypage'
className='block w-full px-16 py-12 text-left hover:bg-gray-50'
>
마이페이지
</Link>
<button
onClick={onLogout}
className='w-full px-16 py-12 text-left hover:bg-gray-50'
>
로그아웃
</button>
</div>
)}
</div>
);
}
5 changes: 5 additions & 0 deletions src/types/profileDropdownTypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export interface ProfileDropdownProps {
nickname: string;
profileImageUrl: string | null;
onLogout: () => void;
}