-
Notifications
You must be signed in to change notification settings - Fork 3
feat/profile: 유저 프로필 페이지 및 프로젝트 리스트 UI 구현 #46
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
tkyoun0421
merged 10 commits into
amicable-development-center:develop
from
namee-h:feat/profile
Jun 26, 2025
Merged
Changes from 5 commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
5e78813
feat: sign up 유저 네임 디폴트값 추가
namee-h 4304e01
feat: 헤더 네브바 추가
namee-h 1fb3c2b
refactor: 유저 프로파일페이지 컴포넌트 분리
namee-h c97fc32
refactor: useSignUpForm에서 useAuthStore 직접 사용하도록 리팩토링
namee-h 06fe1c3
refactor: userProfileCard 탭+뱃지 UI TabWithBadge 컴포넌트로 분리
namee-h 1d826f0
feat: 유저프로필 비어있는 프로젝트 카드 추가 및 공용 네비게이트버튼 추가
namee-h c66a44f
resolve: lint error
namee-h 7636740
refactor: 스타일컴포넌트 코드 위치 변경
namee-h 85afb22
refactor: 프로젝트 리스트 타입 any[] → ProjectListRes[]로 변경
namee-h 7cfe257
refactor: projectTabPanel 별도 파일로 분리
namee-h File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| import { Badge } from "@mui/material"; | ||
| import type { JSX } from "react"; | ||
|
|
||
| interface TabWithBadgeProps { | ||
| label: string; | ||
| count: number; | ||
| active: boolean; | ||
| onClick: () => void; | ||
| ProfileTabChip: any; | ||
| } | ||
|
|
||
| const TabWithBadge = ({ | ||
| label, | ||
| count, | ||
| active, | ||
| onClick, | ||
| ProfileTabChip, | ||
| }: TabWithBadgeProps): JSX.Element => ( | ||
| <Badge | ||
| badgeContent={count} | ||
| color={active ? "primary" : "secondary"} | ||
| anchorOrigin={{ vertical: "top", horizontal: "right" }} | ||
| overlap="rectangular" | ||
| sx={{ | ||
| "& .MuiBadge-badge": { | ||
| fontSize: "1.1rem", | ||
| fontWeight: 700, | ||
| minWidth: 24, | ||
| height: 24, | ||
| }, | ||
| }} | ||
| > | ||
| <ProfileTabChip label={label} active={active} clickable onClick={onClick} /> | ||
| </Badge> | ||
| ); | ||
|
|
||
| export default TabWithBadge; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,145 @@ | ||
| import SettingsIcon from "@mui/icons-material/Settings"; | ||
| import { | ||
| Box, | ||
| Avatar, | ||
| Typography, | ||
| Card, | ||
| CardContent, | ||
| IconButton, | ||
| Divider, | ||
| } from "@mui/material"; | ||
| import { styled as muiStyled } from "@mui/material/styles"; | ||
| import type { JSX } from "react"; | ||
|
|
||
| import TabWithBadge from "./TapWithBadge"; | ||
|
|
||
| // Chip 컴포넌트는 상위에서 import해서 prop으로 넘겨야 함 | ||
|
|
||
| interface UserProfileCardProps { | ||
| userProfile: any; | ||
| PROFILE_TABS: { label: string; color: string }[]; | ||
| likeProjects: any[]; | ||
| appliedProjects: any[]; | ||
| tab: number; | ||
| setTab: (idx: number) => void; | ||
| ProfileTabChip: any; | ||
| } | ||
|
|
||
| const userRoleMap: Record<string, string> = { | ||
| frontend: "프론트엔드", | ||
| backend: "백엔드", | ||
| fullstack: "풀스택", | ||
| designer: "디자이너", | ||
| pm: "PM", | ||
| }; | ||
| const experienceMap: Record<string, string> = { | ||
| junior: "주니어 (3년 이하)", | ||
| mid: "미들 (3년 이상 10년 이하)", | ||
| senior: "시니어 (10년 이상)", | ||
| }; | ||
|
|
||
| const UserProfileCard = ({ | ||
| userProfile, | ||
| PROFILE_TABS, | ||
| likeProjects, | ||
| appliedProjects, | ||
| tab, | ||
| setTab, | ||
| ProfileTabChip, | ||
| }: UserProfileCardProps): JSX.Element => { | ||
| return ( | ||
| <ProfileCard> | ||
| <ProfileCardContent> | ||
| <ProfileCardHeader> | ||
| <IconButton size="small" aria-label="프로필 수정"> | ||
| <SettingsIcon /> | ||
| </IconButton> | ||
| </ProfileCardHeader> | ||
| <ProfileMainRow> | ||
| <ProfileAvatar src={userProfile.avatar} /> | ||
| <ProfileInfoCol> | ||
| <Typography variant="h5" fontWeight={700}> | ||
| {userProfile.name} | ||
| </Typography> | ||
| <Typography> | ||
| {userRoleMap[userProfile.userRole] || userProfile.userRole} | ||
| </Typography> | ||
| <Typography> | ||
| {experienceMap[userProfile.experience] || userProfile.experience} | ||
| </Typography> | ||
| </ProfileInfoCol> | ||
| </ProfileMainRow> | ||
| <Box mt={2} width="100%"> | ||
| <Typography>{userProfile.introduceMyself}</Typography> | ||
| </Box> | ||
| <Divider sx={{ my: 2 }} /> | ||
| <ProfileEmail>{userProfile.email}</ProfileEmail> | ||
| <Box mt={2} display="flex" gap={1} justifyContent="center"> | ||
| {PROFILE_TABS.map((tabInfo, idx) => ( | ||
| <TabWithBadge | ||
| key={tabInfo.label} | ||
| label={tabInfo.label} | ||
| count={ | ||
| idx === 0 | ||
| ? likeProjects?.length || 0 | ||
| : appliedProjects?.length || 0 | ||
| } | ||
| active={tab === idx} | ||
| onClick={() => setTab(idx)} | ||
| ProfileTabChip={ProfileTabChip} | ||
| /> | ||
| ))} | ||
| </Box> | ||
| </ProfileCardContent> | ||
| </ProfileCard> | ||
| ); | ||
| }; | ||
|
|
||
| export default UserProfileCard; | ||
|
|
||
| // 스타일 컴포넌트 재사용 | ||
| const ProfileCard = muiStyled(Card)(({ theme }) => ({ | ||
| minWidth: 280, | ||
| maxWidth: 320, | ||
| borderRadius: 12, | ||
| boxShadow: theme.shadows[2], | ||
| position: "relative", | ||
| padding: 0, | ||
| })); | ||
| const ProfileCardContent = muiStyled(CardContent)(({ theme }) => ({ | ||
| padding: theme.spacing(3), | ||
| paddingBottom: "16px", | ||
| "&:last-child": { | ||
| paddingBottom: "16px", | ||
| }, | ||
| })); | ||
| const ProfileCardHeader = muiStyled(Box)({ | ||
| display: "flex", | ||
| alignItems: "center", | ||
| justifyContent: "flex-end", | ||
| marginBottom: 8, | ||
| minHeight: 32, | ||
| }); | ||
| const ProfileMainRow = muiStyled(Box)({ | ||
| display: "flex", | ||
| flexDirection: "row", | ||
| alignItems: "center", | ||
| gap: 16, | ||
| }); | ||
| const ProfileAvatar = muiStyled(Avatar)({ | ||
| width: 100, | ||
| height: 100, | ||
| marginBottom: 0, | ||
| }); | ||
| const ProfileInfoCol = muiStyled(Box)({ | ||
| display: "flex", | ||
| flexDirection: "column", | ||
| gap: 4, | ||
| }); | ||
| const ProfileEmail = muiStyled(Typography)(({ theme }) => ({ | ||
| color: theme.palette.text.disabled, | ||
| fontSize: "0.95rem", | ||
| fontStyle: "italic", | ||
| fontWeight: 400, | ||
| marginTop: theme.spacing(1), | ||
| })); | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| import ArrowBackIcon from "@mui/icons-material/ArrowBack"; | ||
| import { Box, styled } from "@mui/material"; | ||
| import type { JSX } from "react"; | ||
| import { useNavigate } from "react-router-dom"; | ||
|
|
||
| interface UserProfileHeaderProps { | ||
| title?: string; | ||
| backText?: string; | ||
| backTo?: string; | ||
| } | ||
|
|
||
| const UserProfileHeader = ({ | ||
| title = "마이페이지", | ||
| backText = "홈으로 돌아가기", | ||
| backTo = "/", | ||
| }: UserProfileHeaderProps): JSX.Element => { | ||
| const navigate = useNavigate(); | ||
| return ( | ||
| <HeadBox> | ||
| <ClickSpan onClick={() => navigate(backTo)}> | ||
| <ArrowBackIcon fontSize="small" /> | ||
| {backText} | ||
| </ClickSpan> | ||
| <span>/</span> | ||
| <TitleSpan>{title}</TitleSpan> | ||
| </HeadBox> | ||
| ); | ||
| }; | ||
|
|
||
| export default UserProfileHeader; | ||
|
|
||
| const HeadBox = styled(Box)` | ||
| display: flex; | ||
| align-items: center; | ||
| padding: 2rem 0; | ||
| font-size: 14px; | ||
| gap: 8px; | ||
| `; | ||
| const ClickSpan = styled("span")` | ||
| display: flex; | ||
| align-items: center; | ||
| gap: 8px; | ||
| cursor: pointer; | ||
| font-size: 14px; | ||
| `; | ||
| const TitleSpan = styled("span")` | ||
| font-size: 14px; | ||
| /* 필요시 font-weight, color 등 추가 */ | ||
| `; |
62 changes: 62 additions & 0 deletions
62
src/entities/user/ui/user-profile/UserProfileProjectList.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| import { Box, Tabs, Tab } from "@mui/material"; | ||
| import type { JSX } from "react"; | ||
|
|
||
| import ProjectCard from "@shared/ui/ProjectCard"; | ||
|
|
||
| interface UserProfileProjectListProps { | ||
| PROFILE_TABS: { label: string; color: string }[]; | ||
| tab: number; | ||
| setTab: (idx: number) => void; | ||
| likeProjects: any[]; | ||
| appliedProjects: any[]; | ||
namee-h marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| const UserProfileProjectList = ({ | ||
| PROFILE_TABS, | ||
| tab, | ||
| setTab, | ||
| likeProjects, | ||
| appliedProjects, | ||
| }: UserProfileProjectListProps): JSX.Element => { | ||
| return ( | ||
| <Box flex={1}> | ||
| <Tabs value={tab} onChange={(_, v) => setTab(v)} sx={{ mb: 3 }}> | ||
| {PROFILE_TABS.map((tabInfo, _idx) => ( | ||
| <Tab key={tabInfo.label} label={tabInfo.label} /> | ||
| ))} | ||
| </Tabs> | ||
| {tab === 0 && ( | ||
| <Box | ||
| display="grid" | ||
| gridTemplateColumns={{ | ||
| xs: "1fr", | ||
| sm: "repeat(2, 1fr)", | ||
| md: "repeat(3, 1fr)", | ||
| }} | ||
| gap={2} | ||
| > | ||
| {likeProjects?.slice(0, 3).map((project) => ( | ||
| <ProjectCard key={project.id} project={project} simple /> | ||
| ))} | ||
| </Box> | ||
| )} | ||
| {tab === 1 && ( | ||
| <Box | ||
| display="grid" | ||
| gridTemplateColumns={{ | ||
| xs: "1fr", | ||
| sm: "repeat(2, 1fr)", | ||
| md: "repeat(3, 1fr)", | ||
| }} | ||
| gap={2} | ||
| > | ||
| {appliedProjects?.slice(0, 3).map((project) => ( | ||
| <ProjectCard key={project.id} project={project} simple /> | ||
| ))} | ||
| </Box> | ||
| )} | ||
| </Box> | ||
namee-h marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| ); | ||
| }; | ||
|
|
||
| export default UserProfileProjectList; | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.