Feature: 프로필 페이지 구현#9
Conversation
…se/Comeet_FE into feature/page-profile
- App.tsx에서 온보딩 페이지 관련 코드 제거 및 GlobalLayout로 변경 - profile.ts에 개발자 탭 상수 추가 - DeveloperPage에서 AppTabs 컴포넌트로 탭 기능 구현 - 프로필 및 작성글 컴포넌트 추가
- ProfileSection을 px-4로 감싸 여백 추가 - 텍스트 색상 및 여백 조정으로 가독성 향상
There was a problem hiding this comment.
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
/myand a 'Developer Page' accessible at/developer/:id. - Reusable Profile Section Component: A new
ProfileSection.tsxcomponent has been introduced. This component is designed for reusability, allowing it to display profile information and, whenisEditableis true, provide image upload functionality with a visual preview. - Routing Integration: The application's routing in
src/main.tsxhas 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
-
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. ↩
There was a problem hiding this comment.
Code Review
안녕하세요. 프로필 페이지 기능 추가 PR 잘 보았습니다. 새로운 페이지와 컴포넌트가 추가되었네요. 전반적으로 깔끔하게 구현되었지만, 코드 품질과 유지보수성을 높이기 위해 몇 가지 개선점을 제안합니다.
주요 검토 의견:
- 컴포넌트 재사용성:
ProfileSection에 하드코딩된 데이터를 props로 전달받도록 변경하여 재사용성을 높이는 것을 고려해 보세요. - 타입 안정성: 타입 단언(type assertion)을 사용하는 부분에 타입 가드를 추가하여 런타임 에러 가능성을 줄이는 것이 좋습니다.
- 테스트 코드 부재: 스타일 가이드에 따르면 모든 코드는 자동 테스트가 되어야 합니다1. 새로 추가된
ProfileSection,MyPage,DeveloperPage컴포넌트와 페이지에 대한 렌더링 및 상호작용 테스트를 추가하여 코드의 안정성을 보장하는 것이 중요합니다.
자세한 내용은 각 파일에 남긴 개별 코멘트를 참고해 주세요.
참고한 스타일 가이드
모든 코드는 자동 테스트가 되어야 합니다.
Footnotes
| <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> |
There was a problem hiding this comment.
| reader.onloadend = () => { | ||
| setImagePreview(reader.result as string); | ||
| }; |
There was a problem hiding this comment.
reader.result는 string | ArrayBuffer | null 타입을 가질 수 있으므로, string으로 바로 단언하기보다는 타입을 먼저 확인하는 것이 안전합니다. 이는 "최대한 타입은 좁혀서 사용합니다"라는 프로젝트 스타일 가이드1와도 일치하는 방식입니다.
reader.onloadend = () => {
if (typeof reader.result === "string") {
setImagePreview(reader.result);
}
};Style Guide References
Footnotes
| <h2 className="text-2xl font-bold mt-4 text-white">이름</h2> | ||
| <p className="text-brand-text mt-1">나이 · 포지션</p> |
| }, | ||
| { | ||
| value: "posts", | ||
| label: "작성한 글 ", |
| const handleTabChange = (value: string) => { | ||
| setActiveTab(value as DeveloperTabValue); | ||
| }; |
There was a problem hiding this comment.
AppTabs 컴포넌트에서 string 타입으로 넘어오는 value를 DeveloperTabValue로 바로 단언하고 있습니다. 런타임 에러를 방지하기 위해, 값을 설정하기 전에 유효한 탭 값인지 확인하는 로직을 추가하는 것이 안전합니다. 이는 "최대한 타입은 좁혀서 사용합니다"라는 스타일 가이드 원칙1에도 부합합니다.
const handleTabChange = (value: string) => {
if (DEVELOPER_TABS.some((tab) => tab.value === value)) {
setActiveTab(value as DeveloperTabValue);
}
};Style Guide References
Footnotes
- FileReader의 결과가 문자열인지 확인하여 setImagePreview 호출 시 타입 안전성 강화
- 유효한 탭 값인지 확인하여 setActiveTab 호출 시 타입 안전성 강화
| <input | ||
| type="file" | ||
| ref={fileInputRef} | ||
| onChange={handleFileChange} | ||
| className="hidden" | ||
| accept="image/*" | ||
| /> |
There was a problem hiding this comment.
file input과 같은 컴포넌트는 재사용성이 강합니다.
따라서 따로 컴포넌트 분리를 통해 추상화를 해보시면 좋겠습니다.
There was a problem hiding this comment.
사용자 프로필 이미지 표시 컴포넌트로, my 페이지와 developer 페이지에서 사용합니다.
두 페이지는 각각 본인 프로필 표시, 타인 프로필 표시로 구분하여 구성하였고, 특정 페이지에 종속되지 않고 두 페이지에 걸쳐 사용하는 컴포넌트여서 common으로 구성했습니다.
There was a problem hiding this comment.
common이라는것은 진짜 디자인 컴포넌트 예를 들어 input, button같은거요.
이건 profile이라는 이름으로 하는게 좀더 의미있어 보입니다.
There was a problem hiding this comment.
피드백 감사합니다! 컴포넌트의 역할과 구조에 대해 더 깊이 고민해볼 수 있는 계기가 되었습니다.
shadcn에서 가져오는 컴포넌트들(input, button 등)은 /ui, 그 외 재사용 가능한 컴포넌트들(AppTabs 등)은 /common에 위치시키는 기준으로 ProfileSection도 처음에는 /common에 배치했었습니다. ProfileSection이 AppTabs처럼 범용 컴포넌트라고 판단했기 때문인데, 멘토님 말씀을 듣고 보니 이 컴포넌트는 명확히 프로필 도메인에 종속적인 역할을 하네요. components/profile로 구조를 수정해 반영했습니다!
다만 한 가지 여쭤보고 싶은 점이 있습니다.
현재 특정 페이지에 종속적인 컴포넌트는 pages/(페이지)/_components, 전역적으로 사용하는 도메인 컴포넌트는 components/(도메인) 형태로 관리하고 있습니다. 이렇게 페이지 단위 + 도메인 단위로까지 나누다 보니, 역할은 분명해졌지만 구조가 너무 파편화되는 건 아닐까 하는 고민이 생깁니다.
폴더가 여러 곳에 흩어지면서 오히려 구조의 집중도가 떨어지는 느낌도 있는 것 같아요...
- 현재 구조를 유지
- 아니면
_components로 분리해 놓은 컴포넌트들도 전부src/components/하위로 통합해 관리
어떤 방식이 더 나을지 멘토님의 의견이 궁금합니다!
| 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); | ||
| } | ||
| }; |
There was a problem hiding this comment.
AppTabs 컴포넌트의 onValueChange prop을 (value:string)=>void 형태로 정의해서서, 실제로는 profile|posts처럼 제한된 값만 허용되어야 하는데도, string 전체를 허용하고 있었습니다.
AppTabs가 재사용되는 컴포넌트이고, 사용하는 페이지마다 탭의 타입이 달라, 다양한 문자열 리터럴을 받아야 해서 string으로 뭉뚱그려서 처리했었습니다. 이런 구조여서 사용하는 쪽에서는 .some()으로 런타임 검증을 하고, value as DeveloperTabValue 타입 단언하여 오류를 회피하는 방향을 선택했는데, 임시방편 코드였던 것 같습니다.
AppTabs 컴포넌트를 제네릭으로 개선해서 타입 단언을 사용하지 않는 방향으로 수정해보겠습니다.
- AppTabsProps에 제네릭 타입 추가로 탭 값의 타입 안전성 향상 - DeveloperPage에서 handleTabChange 함수의 매개변수 타입을 DeveloperTabValue로 변경하여 타입 안전성 강화
Monixc
left a comment
There was a problem hiding this comment.
- AppTabsProps에 제네릭 타입 추가
- DeveloperPage에서 handleTabChange 함수의 매개변수 타입을 DeveloperTabValue로 변경
There was a problem hiding this comment.
사용자 프로필 이미지 표시 컴포넌트로, my 페이지와 developer 페이지에서 사용합니다.
두 페이지는 각각 본인 프로필 표시, 타인 프로필 표시로 구분하여 구성하였고, 특정 페이지에 종속되지 않고 두 페이지에 걸쳐 사용하는 컴포넌트여서 common으로 구성했습니다.
| 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); | ||
| } | ||
| }; |
There was a problem hiding this comment.
AppTabs 컴포넌트의 onValueChange prop을 (value:string)=>void 형태로 정의해서서, 실제로는 profile|posts처럼 제한된 값만 허용되어야 하는데도, string 전체를 허용하고 있었습니다.
AppTabs가 재사용되는 컴포넌트이고, 사용하는 페이지마다 탭의 타입이 달라, 다양한 문자열 리터럴을 받아야 해서 string으로 뭉뚱그려서 처리했었습니다. 이런 구조여서 사용하는 쪽에서는 .some()으로 런타임 검증을 하고, value as DeveloperTabValue 타입 단언하여 오류를 회피하는 방향을 선택했는데, 임시방편 코드였던 것 같습니다.
AppTabs 컴포넌트를 제네릭으로 개선해서 타입 단언을 사용하지 않는 방향으로 수정해보겠습니다.
📌 PR 종류 (PR Type)
📝 요약 (Summary)
프로필 페이지(my, developer) 페이지 퍼블리싱 및 라우팅 경로 추가
🔍 상세 내용 (Describe your changes)
ex)
프로필 페이지 퍼블리싱
개발자 프로필 페이지 퍼블리싱
📸 스크린샷 (필수)
✅ 확인 사항 (CheckList)
💬 기타 참고사항 (Optional)