Skip to content

Feature: 프로필 페이지 구현#9

Merged
Monixc merged 10 commits into
developfrom
feature/page-profile
Aug 3, 2025
Merged

Feature: 프로필 페이지 구현#9
Monixc merged 10 commits into
developfrom
feature/page-profile

Conversation

@Monixc

@Monixc Monixc commented Jul 28, 2025

Copy link
Copy Markdown
Contributor

📌 PR 종류 (PR Type)

  • 🐛 버그 수정 (Bugfix)
  • ✨ 기능 추가 (Feature)
  • 🎨 UI 스타일 변경 (Style) - CSS, 클래스명, 레이아웃 등 UI 변경
  • ♻️ 코드 리팩토링 (Refactor) - 기능 변화 없이 코드 구조 개선
  • 📦 패키지/빌드 관련 변경 (Build) - 설정 파일 수정, 의존성 추가 등
  • 🧱 테스트 코드 추가/수정 (Test)
  • 📝 문서 수정 (Docs)
  • 🚧 WIP (작업 중 PR)
  • 기타 (Other):

📝 요약 (Summary)

프로필 페이지(my, developer) 페이지 퍼블리싱 및 라우팅 경로 추가


🔍 상세 내용 (Describe your changes)

ex)
프로필 페이지 퍼블리싱

  • 이미지 영역 클릭 시 파일 업로드 화면

개발자 프로필 페이지 퍼블리싱

  • 프로필, 작성한 글 탭 선택에 따라 해당하는 컴포넌트 렌더링 도도록 구성

📸 스크린샷 (필수)

image image

✅ 확인 사항 (CheckList)

  • 브라우저에서 정상 동작 확인
  • 기존 기능에 영향 없음
  • 반응형 UI 대응 확인
  • 코드 컨벤션에 맞게 작성됨 (eslint, prettier 등)
  • 테스트 코드 추가/수정 또는 테스트 통과 확인

💬 기타 참고사항 (Optional)

Monixc added 5 commits July 28, 2025 10:02
- App.tsx에서 온보딩 페이지 관련 코드 제거 및 GlobalLayout로 변경
- profile.ts에 개발자 탭 상수 추가
- DeveloperPage에서 AppTabs 컴포넌트로 탭 기능 구현
- 프로필 및 작성글 컴포넌트 추가
- ProfileSection을 px-4로 감싸 여백 추가
- 텍스트 색상 및 여백 조정으로 가독성 향상
@github-actions github-actions Bot added ✨feature 기능 개발 🐞bugfix 버그 수정 labels Jul 28, 2025

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Summary of Changes

Hello @Monixc, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request focuses on implementing and publishing two new profile-related pages: a personal 'My Page' and a 'Developer Page'. It establishes the necessary routing for these pages and provides foundational UI components, including a reusable profile section with image upload capabilities and tabbed navigation for the developer profile.

Highlights

  • New Profile Pages: I've implemented two new core profile-related pages: a personal 'My Page' accessible at /my and a 'Developer Page' accessible at /developer/:id.
  • Reusable Profile Section Component: A new ProfileSection.tsx component has been introduced. This component is designed for reusability, allowing it to display profile information and, when isEditable is true, provide image upload functionality with a visual preview.
  • Routing Integration: The application's routing in src/main.tsx has been updated to include the new paths for the 'My Page' and 'Developer Page', ensuring seamless navigation.
  • Tabbed Navigation for Developer Profile: The 'Developer Page' (src/pages/developer/index.tsx) now features tabbed navigation, allowing users to switch between a 'Profile' view and a 'Posts' view, with the active content rendered dynamically.
  • My Page Navigation Menu: The 'My Page' (src/pages/my/index.tsx) includes a structured list of navigation items such as 'Written Posts', 'Activity History', 'Liked Posts', and 'Bookmarks', enhancing user experience for personal profile management.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in issue comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments or fill out our survey to provide feedback.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

안녕하세요. 프로필 페이지 기능 추가 PR 잘 보았습니다. 새로운 페이지와 컴포넌트가 추가되었네요. 전반적으로 깔끔하게 구현되었지만, 코드 품질과 유지보수성을 높이기 위해 몇 가지 개선점을 제안합니다.

주요 검토 의견:

  • 컴포넌트 재사용성: ProfileSection에 하드코딩된 데이터를 props로 전달받도록 변경하여 재사용성을 높이는 것을 고려해 보세요.
  • 타입 안정성: 타입 단언(type assertion)을 사용하는 부분에 타입 가드를 추가하여 런타임 에러 가능성을 줄이는 것이 좋습니다.
  • 테스트 코드 부재: 스타일 가이드에 따르면 모든 코드는 자동 테스트가 되어야 합니다1. 새로 추가된 ProfileSection, MyPage, DeveloperPage 컴포넌트와 페이지에 대한 렌더링 및 상호작용 테스트를 추가하여 코드의 안정성을 보장하는 것이 중요합니다.

자세한 내용은 각 파일에 남긴 개별 코멘트를 참고해 주세요.


참고한 스타일 가이드

모든 코드는 자동 테스트가 되어야 합니다.

Footnotes

  1. style_guide:12-12

Comment thread src/pages/my/index.tsx
Comment on lines +18 to +23
<li
key={item.id}
className="flex justify-between items-center py-4 border-b border-brand-surface cursor-pointer">
<span>{item.label}</span>
<ChevronRight className="size-5 text-brand-text" />
</li>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

<li> 요소에 cursor-pointer 스타일이 적용되어 있어 사용자가 클릭 가능하다고 인지할 수 있지만, 실제로는 아무런 동작도 하지 않습니다. 이는 사용자 경험에 혼란을 줄 수 있습니다.

각 메뉴 항목 클릭 시 해당 페이지로 이동하도록 react-router-dom<Link> 컴포넌트를 사용하거나, onClick 핸들러를 구현하는 것을 권장합니다. 만약 아직 기능 구현 전이라면, 사용자에게 혼동을 주지 않도록 cursor-pointer 클래스를 제거하는 것이 좋습니다.

Comment on lines +22 to +24
reader.onloadend = () => {
setImagePreview(reader.result as string);
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

reader.resultstring | ArrayBuffer | null 타입을 가질 수 있으므로, string으로 바로 단언하기보다는 타입을 먼저 확인하는 것이 안전합니다. 이는 "최대한 타입은 좁혀서 사용합니다"라는 프로젝트 스타일 가이드1와도 일치하는 방식입니다.

      reader.onloadend = () => {
        if (typeof reader.result === "string") {
          setImagePreview(reader.result);
        }
      };

Style Guide References

Footnotes

  1. 타입을 가능한 한 좁게 사용하여 코드의 안정성을 높여야 합니다. (link)

Comment on lines +57 to +58
<h2 className="text-2xl font-bold mt-4 text-white">이름</h2>
<p className="text-brand-text mt-1">나이 · 포지션</p>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

현재 프로필 정보가 하드코딩되어 있어 컴포넌트의 재사용성이 떨어집니다. name, description (예: '나이 · 포지션'), imageUrl 등을 props로 받아 동적으로 표시하도록 수정하는 것을 권장합니다. 이렇게 하면 MyPageDeveloperPage에서 각각 다른 사용자 데이터를 유연하게 표시할 수 있습니다.

Comment thread src/constants/profile.ts
},
{
value: "posts",
label: "작성한 글 ",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

label 값 끝에 불필요한 공백이 포함되어 있습니다. UI에 의도치 않은 영향을 줄 수 있으므로 제거하는 것이 좋습니다.

Suggested change
label: "작성한 글 ",
label: "작성한 글",

Comment thread src/pages/developer/index.tsx Outdated
Comment on lines +12 to +14
const handleTabChange = (value: string) => {
setActiveTab(value as DeveloperTabValue);
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

AppTabs 컴포넌트에서 string 타입으로 넘어오는 valueDeveloperTabValue로 바로 단언하고 있습니다. 런타임 에러를 방지하기 위해, 값을 설정하기 전에 유효한 탭 값인지 확인하는 로직을 추가하는 것이 안전합니다. 이는 "최대한 타입은 좁혀서 사용합니다"라는 스타일 가이드 원칙1에도 부합합니다.

  const handleTabChange = (value: string) => {
    if (DEVELOPER_TABS.some((tab) => tab.value === value)) {
      setActiveTab(value as DeveloperTabValue);
    }
  };

Style Guide References

Footnotes

  1. 타입을 가능한 한 좁게 사용하여 코드의 안정성을 높여야 합니다. (link)

@Monixc Monixc requested review from ksy90101 and woo-dev-log July 28, 2025 03:20
Monixc added 2 commits July 28, 2025 12:22
- FileReader의 결과가 문자열인지 확인하여 setImagePreview 호출 시 타입 안전성 강화
- 유효한 탭 값인지 확인하여 setActiveTab 호출 시 타입 안전성 강화
Comment on lines +51 to +57
<input
type="file"
ref={fileInputRef}
onChange={handleFileChange}
className="hidden"
accept="image/*"
/>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

file input과 같은 컴포넌트는 재사용성이 강합니다.
따라서 따로 컴포넌트 분리를 통해 추상화를 해보시면 좋겠습니다.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

이 컴포넌트가 common인 근거가 무엇인가요?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

사용자 프로필 이미지 표시 컴포넌트로, my 페이지와 developer 페이지에서 사용합니다.
두 페이지는 각각 본인 프로필 표시, 타인 프로필 표시로 구분하여 구성하였고, 특정 페이지에 종속되지 않고 두 페이지에 걸쳐 사용하는 컴포넌트여서 common으로 구성했습니다.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

common이라는것은 진짜 디자인 컴포넌트 예를 들어 input, button같은거요.
이건 profile이라는 이름으로 하는게 좀더 의미있어 보입니다.

@Monixc Monixc Jul 31, 2025

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

피드백 감사합니다! 컴포넌트의 역할과 구조에 대해 더 깊이 고민해볼 수 있는 계기가 되었습니다.

shadcn에서 가져오는 컴포넌트들(input, button 등)은 /ui, 그 외 재사용 가능한 컴포넌트들(AppTabs 등)은 /common에 위치시키는 기준으로 ProfileSection도 처음에는 /common에 배치했었습니다. ProfileSectionAppTabs처럼 범용 컴포넌트라고 판단했기 때문인데, 멘토님 말씀을 듣고 보니 이 컴포넌트는 명확히 프로필 도메인에 종속적인 역할을 하네요. components/profile로 구조를 수정해 반영했습니다!

다만 한 가지 여쭤보고 싶은 점이 있습니다.
현재 특정 페이지에 종속적인 컴포넌트는 pages/(페이지)/_components, 전역적으로 사용하는 도메인 컴포넌트는 components/(도메인) 형태로 관리하고 있습니다. 이렇게 페이지 단위 + 도메인 단위로까지 나누다 보니, 역할은 분명해졌지만 구조가 너무 파편화되는 건 아닐까 하는 고민이 생깁니다.
폴더가 여러 곳에 흩어지면서 오히려 구조의 집중도가 떨어지는 느낌도 있는 것 같아요...

  • 현재 구조를 유지
  • 아니면 _components로 분리해 놓은 컴포넌트들도 전부 src/components/ 하위로 통합해 관리

어떤 방식이 더 나을지 멘토님의 의견이 궁금합니다!

Comment on lines +9 to +16
export const DeveloperPage = () => {
const [activeTab, setActiveTab] = useState<DeveloperTabValue>("profile");

const handleTabChange = (value: string) => {
if (DEVELOPER_TABS.some((tab) => tab.value === value)) {
setActiveTab(value as DeveloperTabValue);
}
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

as를 쓰지 않을 방법을 고민해보시면 좋겠습니다.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

AppTabs 컴포넌트의 onValueChange prop을 (value:string)=>void 형태로 정의해서서, 실제로는 profile|posts처럼 제한된 값만 허용되어야 하는데도, string 전체를 허용하고 있었습니다.

AppTabs가 재사용되는 컴포넌트이고, 사용하는 페이지마다 탭의 타입이 달라, 다양한 문자열 리터럴을 받아야 해서 string으로 뭉뚱그려서 처리했었습니다. 이런 구조여서 사용하는 쪽에서는 .some()으로 런타임 검증을 하고, value as DeveloperTabValue 타입 단언하여 오류를 회피하는 방향을 선택했는데, 임시방편 코드였던 것 같습니다.

AppTabs 컴포넌트를 제네릭으로 개선해서 타입 단언을 사용하지 않는 방향으로 수정해보겠습니다.

- AppTabsProps에 제네릭 타입 추가로 탭 값의 타입 안전성 향상
- DeveloperPage에서 handleTabChange 함수의 매개변수 타입을 DeveloperTabValue로 변경하여 타입 안전성 강화

@Monixc Monixc left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

  • AppTabsProps에 제네릭 타입 추가
  • DeveloperPage에서 handleTabChange 함수의 매개변수 타입을 DeveloperTabValue로 변경

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

사용자 프로필 이미지 표시 컴포넌트로, my 페이지와 developer 페이지에서 사용합니다.
두 페이지는 각각 본인 프로필 표시, 타인 프로필 표시로 구분하여 구성하였고, 특정 페이지에 종속되지 않고 두 페이지에 걸쳐 사용하는 컴포넌트여서 common으로 구성했습니다.

Comment on lines +9 to +16
export const DeveloperPage = () => {
const [activeTab, setActiveTab] = useState<DeveloperTabValue>("profile");

const handleTabChange = (value: string) => {
if (DEVELOPER_TABS.some((tab) => tab.value === value)) {
setActiveTab(value as DeveloperTabValue);
}
};

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

AppTabs 컴포넌트의 onValueChange prop을 (value:string)=>void 형태로 정의해서서, 실제로는 profile|posts처럼 제한된 값만 허용되어야 하는데도, string 전체를 허용하고 있었습니다.

AppTabs가 재사용되는 컴포넌트이고, 사용하는 페이지마다 탭의 타입이 달라, 다양한 문자열 리터럴을 받아야 해서 string으로 뭉뚱그려서 처리했었습니다. 이런 구조여서 사용하는 쪽에서는 .some()으로 런타임 검증을 하고, value as DeveloperTabValue 타입 단언하여 오류를 회피하는 방향을 선택했는데, 임시방편 코드였던 것 같습니다.

AppTabs 컴포넌트를 제네릭으로 개선해서 타입 단언을 사용하지 않는 방향으로 수정해보겠습니다.

@woo-dev-log woo-dev-log left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

👍

@Monixc Monixc merged commit 5c9dac7 into develop Aug 3, 2025
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🐞bugfix 버그 수정 ✨feature 기능 개발

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants