Skip to content
Closed
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
3 changes: 3 additions & 0 deletions src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ body {
.Text-gray {
@apply text-[#787486] dark:text-[#BCBCBC];
}
.Text-white {
@apply text-[#FFFFFF] dark:text-[#333236];
}
.Border-btn {
@apply border border-[#D9D9D9] dark:border-[#747474];
}
Expand Down
80 changes: 80 additions & 0 deletions src/app/shared/components/common/Profile.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
'use client'

import Image from 'next/image'

type ProfileProps = {
nickname: string
imageUrl?: string
size?: number
}

const customColors = [
'#efaa8d',
'#FFC85A',
'#b9ef8d',
'#8eef8d',
'#8defd3',
'#8dcaef',
'#8d9def',
'#a58def',
'#e292e0',
]

// 첫 글자로 사용자 프로필 생성하는 함수
function getInitial(nickname: string): string {
const firstChar = nickname.trim().charAt(0)

if (/[a-zA-Z]/.test(firstChar)) {
return firstChar.toUpperCase() // 영어: 대문자
}

if (/[-]/.test(firstChar)) {
return firstChar // 한글은 그대로 반환
}

return '?' // 기타문자: 물음표
}

// 닉네임으로부터 배경색 생성 함수
function getColor(nickname: string): string {
const hash = nickname
.split('')
.reduce((acc, char) => acc + char.charCodeAt(0), 0)
return customColors[hash % customColors.length]
}

export function Profile({ nickname, imageUrl, size = 36 }: ProfileProps) {
const initial = getInitial(nickname)
const bgColor = getColor(nickname)

return imageUrl ? (
// 프로필 이미지가 있을 때
<div className="flex items-center gap-4">
<div className="relative size-48 overflow-hidden rounded-full">
<Image
src="/images/profile.gif"
fill
alt="프로필 이미지"
className="size-full object-cover"
/>
</div>
<span className="text-sm font-semibold">사용자</span>
</div>
Comment on lines +50 to +62
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

imageUrl 프롭이 실제로 사용되지 않음

imageUrl 을 받아 놓고 하드코딩된 "/images/profile.gif" 를 렌더링하고 있어 기능이 동작하지 않습니다.

-        <Image
-          src="/images/profile.gif"
+        <Image
+          src={imageUrl}

또한 표시되는 이름이 "사용자" 로 고정되어 있어 nickname 과 불일치합니다.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return imageUrl ? (
// 프로필 이미지가 있을 때
<div className="flex items-center gap-4">
<div className="relative size-48 overflow-hidden rounded-full">
<Image
src="/images/profile.gif"
fill
alt="프로필 이미지"
className="size-full object-cover"
/>
</div>
<span className="text-sm font-semibold">사용자</span>
</div>
return imageUrl ? (
// 프로필 이미지가 있을 때
<div className="flex items-center gap-4">
<div className="relative size-48 overflow-hidden rounded-full">
<Image
- src="/images/profile.gif"
+ src={imageUrl}
fill
alt="프로필 이미지"
className="size-full object-cover"
/>
</div>
<span className="text-sm font-semibold">사용자</span>
</div>
🤖 Prompt for AI Agents
In src/app/shared/components/common/Profile.tsx around lines 50 to 62, the
imageUrl prop is not used and the profile image source is hardcoded to
"/images/profile.gif". Replace the hardcoded src with the imageUrl prop to
correctly display the passed profile image. Also, update the displayed name from
the fixed string "사용자" to use the nickname prop so the rendered name matches the
user's nickname.

) : (
// 프로필 이미지가 없을 때
<>
<div
className="ml-8 flex items-center justify-center rounded-full font-semibold text-white"
style={{
width: size,
height: size,
fontSize: size * 0.4, // 글자 크기 조정
backgroundColor: bgColor,
}}
>
{initial}
</div>
<div className="text-base font-medium">{nickname}</div>
</>
)
}
40 changes: 15 additions & 25 deletions src/app/shared/components/common/header/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
'use client'

import { useUserStore } from '@store/useUserStore' // Zustand 예시
import { usePathname, useRouter } from 'next/navigation'
import { cn } from '@lib/cn' // 클래스 이름 병합 유틸리티
import Link from 'next/link'
import Image from 'next/image'
import { Profile } from '@components/common/Profile'

export default function Header() {
const pathname = usePathname()
const router = useRouter()
const goToMypage = () => {
router.push('/mypage')
}
const { user, logout } = useUserStore() // Zustand 상태

return (
<header className="flex items-center justify-between border-b border-gray-200 bg-white px-36 py-16 dark:border-gray-700 dark:bg-black">
<header className="BG-White Border-section Text-black flex items-center justify-between border-b px-36 py-16">
{/* 좌측 대시보드명 */}
<div className="flex items-center gap-8">
<div className="font-bold">대시보드 명</div>
Expand All @@ -24,12 +24,15 @@ export default function Header() {
</div>

{/* 우측 사용자 정보/다크모드 */}
<div className="flex items-center gap-16">
<div className="flex items-center gap-8">
<>
<nav className="hidden gap-8 text-sm text-gray-600 dark:text-gray-300 md:flex">
<Link
href="/dashboard"
className={`flex items-center gap-6 rounded-md border-2 border-solid px-8 py-4 ${pathname === '/dashboard' ? 'font-semibold' : ''}`}
className={cn(
'Border-btn flex items-center gap-6 rounded-md border-solid px-12 py-6',
pathname === '/dashboard' && 'font-semibold',
)}
>
<div className="relative flex size-12">
<Image src="/images/management.png" fill alt="관리 버튼" />
Expand All @@ -38,7 +41,10 @@ export default function Header() {
</Link>
<Link
href="/modal"
className={`flex items-center gap-6 rounded-6 border-2 border-solid px-8 py-4 ${pathname === '/modal' ? 'font-semibold' : ''}`}
className={cn(
'Border-btn mr-16 flex items-center gap-6 rounded-6 border-solid px-12 py-6',
pathname === '/modal' && 'font-semibold',
)}
>
<div className="relative flex size-12">
<Image src="/images/invitation.png" fill alt="초대 버튼" />
Expand All @@ -47,15 +53,7 @@ export default function Header() {
</Link>
</nav>
{/* 공동작업자 프로필 이미지 */}
<div className="relative size-48 overflow-hidden rounded-full">
<Image
src="/images/collaborator.png"
fill
alt="초대된 사용자"
style={{ objectFit: 'cover' }}
/>
</div>
<div className="relative size-48 overflow-hidden rounded-full">
<div className="relative mx-16 size-48 overflow-hidden rounded-full">
<Image
src="/images/collaborator.png"
fill
Expand All @@ -64,20 +62,12 @@ export default function Header() {
/>
</div>
|{/* 내 프로필 이미지 */}
<div className="relative size-48 overflow-hidden rounded-full">
<Image
src="/images/profile.gif"
fill
alt="프로필이미지"
style={{ objectFit: 'cover' }}
/>
</div>
<span className="text-sm">배유철 {user?.name}</span>
<Profile nickname="전유진" />
{/* 드롭다운 메뉴 */}
<button onClick={goToMypage} className="text-xs">
마이페이지
</button>
<button onClick={logout} className="text-xs">
<button onClick={goToMypage} className="text-xs">
로그아웃
</button>
Comment on lines 67 to 72
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

로그아웃 버튼이 실제 로그아웃을 수행하지 않음

onClick={goToMypage} 로 연결되어 있어 ‘마이페이지’ 와 동일한 동작을 합니다. 로그아웃 로직(세션 제거, redirect 등)을 별도 함수로 분리해 연결해야 합니다.

🤖 Prompt for AI Agents
In src/app/shared/components/common/header/Header.tsx around lines 67 to 72, the
logout button incorrectly uses the goToMypage function for its onClick handler,
causing it to perform the same action as the My Page button. Create a separate
logout handler function that performs the logout logic such as clearing the
session and redirecting the user, then assign this new function to the logout
button's onClick event.

</>
Expand Down
6 changes: 6 additions & 0 deletions src/app/shared/lib/cn.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { clsx, type ClassValue } from 'clsx'
import { twMerge } from 'tailwind-merge'

Comment on lines +1 to +3
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

clsx 기본-/명명형 가져오기 오류로 런타임 예외 발생

clsx 는 default export 입니다. 현재 형태로는 clsxundefined 가 되어 twMerge(undefined) 호출 시 크리티컬 런타임 오류가 납니다.

-import { clsx, type ClassValue } from 'clsx'
+import clsx, { type ClassValue } from 'clsx'
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { clsx, type ClassValue } from 'clsx'
import { twMerge } from 'tailwind-merge'
import clsx, { type ClassValue } from 'clsx'
import { twMerge } from 'tailwind-merge'
🤖 Prompt for AI Agents
In src/app/shared/lib/cn.ts at lines 1 to 3, the import of clsx is incorrect
because clsx is a default export, not a named export. This causes clsx to be
undefined and leads to a runtime error when calling twMerge(clsx). Fix this by
importing clsx as the default import without curly braces, ensuring clsx is
properly defined before passing it to twMerge.

export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
3 changes: 2 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
"@/*": ["./src/*"],
"@components/*": ["./src/app/shared/components/*"],
"@store/*": ["./src/app/shared/store/*"],
"@hooks/*": ["./src/app/shared/hooks/*"]
"@hooks/*": ["./src/app/shared/hooks/*"],
"@lib/*": ["./src/app/shared/lib/*"]
}
},
"include": [
Expand Down