Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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: 2 additions & 2 deletions src/entities/user/hooks/useSignUpForm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ interface UseSignUpFormReturn {
handleSubmit: () => void;
}

export function useSignUpForm(): UseSignUpFormReturn {
export function useSignUpForm(defaultName = ""): UseSignUpFormReturn {
const { signUp } = useSignUp();

const [name, setName] = useState("");
const [name, setName] = useState(defaultName);
const [userRole, setUserRole] = useState("");
const [experience, setExperience] = useState("");
const [introduceMyself, setIntroduceMyself] = useState("");
Expand Down
9 changes: 6 additions & 3 deletions src/entities/user/ui/UserInfoForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ import { type JSX } from "react";
import { useSignUpForm } from "@entities/user/hooks/useSignUpForm";
import SubmitButton from "@entities/user/ui/SubmitButton";

import { useAuthStore } from "@shared/stores/authStore";

const UserInfoForm = (): JSX.Element => {
const { user } = useAuthStore();
const defaultName = user?.displayName || "";
const {
name,
userRole,
Expand All @@ -23,21 +27,20 @@ const UserInfoForm = (): JSX.Element => {
errors,
handleChange,
handleSubmit,
} = useSignUpForm();
} = useSignUpForm(defaultName);

return (
<FormContainer>
<Title variant="h5">회원 정보 입력</Title>
{/* 이름 입력 */}
<FormControl error={errors.name} variant="outlined" fullWidth>
<StyledTextField
label="🙋 이름 *"
label="🙋 닉네임 *"
variant="outlined"
value={name}
onChange={(e) => handleChange("name")(e.target.value)}
error={errors.name}
onFocus={() => errors.name && handleChange("name")(name)}
placeholder="이름"
InputLabelProps={{ shrink: true }}
/>
{errors.name && <ErrorText>이름을 입력해주세요.</ErrorText>}
Expand Down
158 changes: 158 additions & 0 deletions src/entities/user/ui/user-profile/UserProfileCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import SettingsIcon from "@mui/icons-material/Settings";
import {
Box,
Avatar,
Typography,
Card,
CardContent,
IconButton,
Divider,
Badge,
} from "@mui/material";
import { styled as muiStyled } from "@mui/material/styles";
import type { JSX } from "react";

// 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) => (
<Badge
key={tabInfo.label}
badgeContent={
idx === 0
? likeProjects?.length || 0
: appliedProjects?.length || 0
}
color={tab === idx ? "primary" : "secondary"}
anchorOrigin={{ vertical: "top", horizontal: "right" }}
overlap="rectangular"
sx={{
"& .MuiBadge-badge": {
fontSize: "1.1rem",
fontWeight: 700,
minWidth: 24,
height: 24,
},
}}
>
<ProfileTabChip
label={tabInfo.label}
active={tab === idx}
clickable
onClick={() => setTab(idx)}
/>
</Badge>
))}
</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),
}));
49 changes: 49 additions & 0 deletions src/entities/user/ui/user-profile/UserProfileHeader.tsx
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 src/entities/user/ui/user-profile/UserProfileProjectList.tsx
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[];
}

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>
);
};

export default UserProfileProjectList;
Loading