Skip to content
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

마이페이지 구현 #185

Merged
merged 11 commits into from
Aug 3, 2023
76 changes: 76 additions & 0 deletions frontend/src/components/ListFilter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { ListSelectOption } from '@/types/select';
import React from 'react';
import styled, { css, keyframes } from 'styled-components';

interface Props {
options: ListSelectOption[];
selectOption: (value: string | number) => void;
width?: string;
}

const ListFilter = ({ options, selectOption, width }: Props) => {
Copy link
Member

Choose a reason for hiding this comment

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

재활용하기 용이하겠군요 👍

const makeHandleClickOption = (value: string | number) => () => {
if (options.filter((option) => option.value === value).length === 0) return;

selectOption(value);
};

return (
<S.FilterContainer>
<S.FilterList $width={width}>
{options.map((item) => (
<S.FilterItem key={item.value} onClick={makeHandleClickOption(item.value)} isSelected={item.selected}>
Copy link
Member

Choose a reason for hiding this comment

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

map을 사용할 때 item보다는 option으로 넣는게 가독성이 좋을 것 같아요

{item.label}
</S.FilterItem>
))}
</S.FilterList>
</S.FilterContainer>
);
};

export default ListFilter;

const appear = keyframes`
0% {
transform: scaleX(0);
}
100% {
transform: scaleX(1);
}
`;

const underLine = css`
content: '';
margin-top: 5px;
height: 3px;
width: calc(100% + 10px);
background-color: var(--baton-red);
animation: 0.3s ease-in ${appear};
`;

const S = {
FilterContainer: styled.div``,

FilterList: styled.ul<{ $width?: string }>`
display: flex;
justify-content: space-between;

width: ${({ $width }) => $width ?? '920px'};
`,

FilterItem: styled.li<{ isSelected: boolean }>`
display: flex;
flex-direction: column;
align-items: center;

font-size: 26px;
font-weight: 700;
color: ${({ isSelected }) => (isSelected ? 'var(--baton-red)' : 'var(--gray-700)')};

&::after {
${({ isSelected }) => (isSelected ? underLine : null)}
}

cursor: pointer;
`,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import Button from '@/components/common/Button';
import Label from '@/components/common/Label';
import { REVIEW_STATUS_LABEL_TEXT } from '@/constants';
import { ProfileRunnerPost } from '@/types/profile';
import React from 'react';
import styled from 'styled-components';

interface Props extends ProfileRunnerPost {}

const ProfileRunnerPostItem = ({ runnerPostId, title, deadline, reviewStatus, tags }: Props) => {
const handleClickFeedbackButton = () => {
alert('준비중인 기능입니다');
};

return (
<S.RunnerPostItemContainer>
<S.LeftSideContainer>
<S.PostTitle>{title}</S.PostTitle>
<S.DeadLineContainer>
<S.DeadLine>{deadline} 까지</S.DeadLine>
<Label colorTheme={reviewStatus === 'DONE' ? 'GRAY' : reviewStatus === 'IN_PROGRESS' ? 'RED' : 'WHITE'}>
{REVIEW_STATUS_LABEL_TEXT[reviewStatus]}
</Label>
</S.DeadLineContainer>
<S.TagContainer>
{tags.map((tag, index) => (
<span key={index}>#{tag}</span>
))}
</S.TagContainer>
</S.LeftSideContainer>
<S.RightSideContainer>
{reviewStatus === 'DONE' ? (
<Button
colorTheme="WHITE"
fontWeight={700}
width={'180px'}
height={'40px'}
onClick={handleClickFeedbackButton}
>
피드백 작성
</Button>
) : null}
</S.RightSideContainer>
</S.RunnerPostItemContainer>
);
};

export default ProfileRunnerPostItem;

const S = {
RunnerPostItemContainer: styled.li`
display: flex;
justify-content: space-between;

width: 1200px;
height: 206px;
padding: 35px 40px;

border: 0.5px solid var(--gray-500);
border-radius: 12px;
box-shadow: 1px 2px 3px rgba(0, 0, 0, 0.2);

cursor: pointer;
`,

PostTitle: styled.p`
margin-bottom: 15px;

font-size: 28px;
font-weight: 700;
`,

DeadLineContainer: styled.div`
display: flex;
align-items: baseline;
gap: 10px;
`,

DeadLine: styled.p`
margin-bottom: 60px;

color: var(--gray-600);
`,

TagContainer: styled.div`
& span {
margin-right: 10px;

font-size: 14px;
color: var(--gray-600);
}
`,

LeftSideContainer: styled.div``,

RightSideContainer: styled.div`
display: flex;
flex-direction: column;
justify-content: end;
`,
};
31 changes: 31 additions & 0 deletions frontend/src/mocks/data/runnerProfile.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"profile": {
"name": "도리토스",
"imageUrl": "profile.jpg",
"githubUrl": "github.com/shb03323",
"introduction": "안녕하세요 디투입니다."
},
"runnerPosts": [
{
"runnerPostId": 1,
"title": "제목",
"deadline": "마감기한",
"tags": ["java", "JAVA"],
"reviewStatus": "DONE"
},
{
"runnerPostId": 2,
"title": "제목2",
"deadline": "마감기한2",
"tags": ["java", "자바"],
"reviewStatus": "NOT_STARTED"
},
{
"runnerPostId": 3,
"title": "제목3",
"deadline": "마감기한3",
"tags": ["java", "자바"],
"reviewStatus": "NOT_STARTED"
}
]
}
5 changes: 5 additions & 0 deletions frontend/src/mocks/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { rest } from 'msw';
import runnerPostList from './data/runnerPostList.json';
import runnerPostDetails from './data/runnerPostDetails.json';
import supporterCardList from './data/supporterCardList.json';
import runnerProfile from './data/runnerProfile.json';

export const handlers = [
rest.post('*/posts/runner/test', async (req, res, ctx) => {
Expand Down Expand Up @@ -42,4 +43,8 @@ export const handlers = [

return res(ctx.delay(300), ctx.status(201), ctx.set('Content-Type', 'application/json'));
}),

rest.get('*/profile/runner', async (req, res, ctx) => {
return res(ctx.status(200), ctx.set('Content-Type', 'application/json'), ctx.json(runnerProfile));
}),
];
Loading