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

미션 현황 페이지 구현 (issue#49) #67

Merged
merged 9 commits into from
Jul 19, 2024
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import styled from 'styled-components';

export const MyMissionCompletedContainer = styled.div`
display: flex;
flex-direction: column;
gap: 3.5rem;
`;

export const MissionCardListWrapper = styled.div`
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 2.7rem;
`;

export const MissionCardWrapper = styled.div`
min-height: 32.6rem;
border-radius: 0.8rem;

display: flex;
flex-direction: column;
background-color: var(--grey-50);

position: relative;
box-sizing: border-box;
`;

export const MissionCardHeaderWrapper = styled.div`
display: flex;
position: relative;
height: 50%;
`;

export const MissionCardContentWrapper = styled.div`
width: 100%;
height: 50%;

display: flex;
flex-direction: column;
justify-content: space-between;

box-sizing: border-box;
padding: 0.8rem 1.7rem;
`;

export const Title = styled.h2`
font-size: 3.2rem;
font-weight: bold;
`;

export const MissionLanguageBox = styled.div`
background-color: var(--primary-300);
position: absolute;
top: 0.8rem;
right: 0.8rem;

padding: 0.3rem 1.2rem;
box-sizing: border-box;
border-radius: 0.4rem;

font-size: 1.4rem;
font-weight: 500;
`;

export const ThumbnailImg = styled.img`
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;

object-fit: cover;
border-radius: 0.8rem 0.8rem 0 0;
`;

export const MissionTitle = styled.h3`
font-size: 2.2rem;
font-weight: bold;
`;

export const MissionDate = styled.p`
font-size: 1.4rem;
font-weight: 500;
color: var(--grey-400);
`;

export const PrButtonWrapper = styled.div`
display: flex;
gap: 0.7rem;
justify-content: start;
`;

export const PrButton = styled.button`
background-color: var(--grey-200);
width: fit-content;
padding: 0.6rem 1.9rem;
border-radius: 0.4rem;

display: flex;
gap: 0.4rem;
justify-content: center;
align-items: center;

white-space: nowrap;
font-size: 1.2rem;
font-weight: bold;
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { Link, useNavigate } from 'react-router-dom';
import * as S from './MyMissionCompleted.styled';

const mocks = [
{
id: 1,
mission: {
id: 1,
title: '루터회관 흡연 단속',
language: 'JAVA',
description: '루터회관 흡연 벌칙 프로그램을 구현한다.',
thumbnail:
'https://dszw1qtcnsa5e.cloudfront.net/community/20231115/ccefd368-2687-4d26-954d-712315f38fea/2232611625864AF59FF189AB61A75869.jpeg',
url: 'https://github.com/develup-mission/java-smoking',
},
myPrLink: 'https://github.com/develup-mission/java-smoking/pull/1',
pairPrLink: 'https://github.com/develup-mission/java-smoking/pull/2',
status: '리뷰 완료',
},
{
id: 2,
mission: {
id: 2,
title: '루터회관 흡연 단속 2',
language: 'JAVA',
description: '루터회관 흡연 벌칙 프로그램을 구현한다.',
thumbnail:
'https://dszw1qtcnsa5e.cloudfront.net/community/20231115/ccefd368-2687-4d26-954d-712315f38fea/2232611625864AF59FF189AB61A75869.jpeg',
url: 'https://github.com/develup-mission/java-smoking',
},
myPrLink: 'https://github.com/develup-mission/java-smoking/pull/1',
pairPrLink: 'https://github.com/develup-mission/java-smoking/pull/2',
status: '리뷰 완료',
},
{
id: 3,
mission: {
id: 2,
title: '루터회관 흡연 단속 3',
language: 'JAVA',
description: '루터회관 흡연 벌칙 프로그램을 구현한다.',
thumbnail:
'https://dszw1qtcnsa5e.cloudfront.net/community/20231115/ccefd368-2687-4d26-954d-712315f38fea/2232611625864AF59FF189AB61A75869.jpeg',
url: 'https://github.com/develup-mission/java-smoking',
},
myPrLink: 'https://github.com/develup-mission/java-smoking/pull/1',
pairPrLink: 'https://github.com/develup-mission/java-smoking/pull/2',
status: '리뷰 완료',
},
{
id: 4,
mission: {
id: 2,
title: '루터회관 흡연 단속 4',
language: 'JAVA',
description: '루터회관 흡연 벌칙 프로그램을 구현한다.',
thumbnail:
'https://dszw1qtcnsa5e.cloudfront.net/community/20231115/ccefd368-2687-4d26-954d-712315f38fea/2232611625864AF59FF189AB61A75869.jpeg',
url: 'https://github.com/develup-mission/java-smoking',
},
myPrLink: 'https://github.com/develup-mission/java-smoking/pull/1',
pairPrLink: 'https://github.com/develup-mission/java-smoking/pull/2',
status: '리뷰 완료',
},
];

export default function MyMissionCompleted() {
const navigate = useNavigate();

const handleMissionClick = (id: number) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

mission click은 Link 이용하지 않고 navigate로 라우팅을 처리하신 이유가 있으신지 궁금해요~!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

MyMissionCompleted 컴포넌트에 라우팅이 총 두 개 들어가게 되는데요! (바깥 카드, 내부 버튼) Link가 중첩되면 warning: validatedomnesting(...): <a> cannot appear as a descendant of <a> 메세지가 뜨더라구요😭 Link는 a 태그 역할을 하는데, Link가 두 번 들어가면 a 태그 내부에 a가 중첩되기 때문이었어요. 그래서 바깥 카드 부분은 navigate를 사용했습니다~!

navigate(`/missions/${id}`);
};

return (
<S.MyMissionCompletedContainer>
<S.Title>완료한 미션</S.Title>
<S.MissionCardListWrapper>
{mocks.map(({ id, mission, pairPrLink, myPrLink }) => (
<S.MissionCardWrapper key={id} onClick={() => handleMissionClick(id)}>
<S.MissionCardHeaderWrapper>
<S.ThumbnailImg src={mission.thumbnail} />
<S.MissionLanguageBox>{mission.language}</S.MissionLanguageBox>
</S.MissionCardHeaderWrapper>

<S.MissionCardContentWrapper>
<div>
<S.MissionTitle>{mission.title}</S.MissionTitle>
<S.MissionDate>2024.07.17 ~ 2024.07.24</S.MissionDate>
</div>

<S.PrButtonWrapper>
<Link to={pairPrLink} target="_blank" onClick={(e) => e.stopPropagation()}>
Copy link
Contributor

Choose a reason for hiding this comment

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

여기서 e.stopPropagation()은 이벤트 버블링 때문일까요?!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

네 맞아요! 바깥 부분의 카드에 링크 기능을 넣었더니 카드 안쪽에 있는 버튼의 라우팅이 동작하지 않더라구요..! 그래서 stopPropagation 을 넣었습니다 ☺️

Copy link
Contributor

Choose a reason for hiding this comment

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

아니면 안에 버튼 없이

<Link to={pairPrLink} target="_blank">
페어 PR 이동
 </Link>

이런식으로는 힘들까요..!? 🥹

Copy link
Contributor Author

Choose a reason for hiding this comment

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

missionDetailButtonGroup 컴포넌트의 버튼에서도 비슷한 코드가 있었는데요! Link를 스타일 태그에 넣는 것보다는 Button 바깥을 Link로 감싸서 스타일과 컴포넌트를 명확하게 분리하는 것이 좋을 것 같다는 이야기를 했던 것 같아요! 다른 의견 있으시면 언제든지 말해주세용🫡

// missionDetailButtonGroup
<Link to={`/submit/${id}`}>
  <S.Button>미션 제출하기</S.Button>
</Link>

<S.PrButton>페어 PR 이동</S.PrButton>
</Link>
<Link to={myPrLink} target="_blank" onClick={(e) => e.stopPropagation()}>
<S.PrButton>내 PR 이동</S.PrButton>
</Link>
</S.PrButtonWrapper>
</S.MissionCardContentWrapper>
</S.MissionCardWrapper>
))}
</S.MissionCardListWrapper>
</S.MyMissionCompletedContainer>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import GithubLogo from '@/assets/images/githubLogo.svg';
import styled from 'styled-components';

export const MyMissionInProgressContainer = styled.div`
display: flex;
flex-direction: column;
gap: 3.5rem;
`;

export const MyMissionInProgressWrapper = styled.div`
background-color: var(--grey-50);
width: 100%;
height: 24rem;

display: grid;
grid-template-columns: 1fr 1.5fr 1fr;
gap: 2.7rem;
padding: 3.2rem 4.1rem;
box-sizing: border-box;
border-radius: 0.8rem;
`;

export const MissionContentWrapper = styled.div`
display: flex;
flex-direction: column;
justify-content: space-between;
`;

export const MissionButtonWrapper = styled.div`
display: flex;
flex-direction: column;
justify-content: space-between;
`;

export const PrButtonWrapper = styled.div`
display: flex;
gap: 0.7rem;
justify-content: flex-end;
`;

export const MissionCompleteWrapper = styled.div`
display: flex;
justify-content: flex-end;
`;

export const ThumbnailImg = styled.img`
width: 100%;
height: -webkit-fill-available; // Chrome, Safari
height: -moz-available; // Firefox
Copy link
Contributor

Choose a reason for hiding this comment

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

오호 브라우저마다 height를 다르게 주어야하는군요 ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

그러게요..! 저도 이번에 처음 알게 된 방법이에요😀😀 브라우저 엔진이 서로 달라서 다양한 브라우저를 지원하려면 여러가지 변수를 고려해야 할 것 같아요..!!

overflow: hidden;
object-fit: cover;
border-radius: 0.8rem;
`;

export const Title = styled.h2`
font-size: 3.2rem;
font-weight: bold;
`;

export const MissionTitle = styled.h3`
font-size: 2.4rem;
font-weight: bold;
`;

export const MissionDate = styled.p`
font-size: 1.8rem;
font-weight: 500;
color: var(--grey-400);
`;

export const GithubIcons = styled(GithubLogo)`
height: 100%;
`;

export const PrButton = styled.button`
background-color: var(--grey-200);
width: fit-content;
padding: 0.6rem 1.9rem;
border-radius: 0.4rem;

display: flex;
gap: 0.4rem;
justify-content: center;
align-items: center;

white-space: nowrap;
font-size: 1.4rem;
font-weight: bold;
`;

export const MissionCompleteButton = styled.button`
background-color: var(--primary-200);
width: fit-content;
padding: 0.6rem 1.9rem;
border-radius: 0.4rem;

display: flex;
gap: 0.4rem;
justify-content: center;
align-items: center;

white-space: nowrap;
font-size: 1.4rem;
font-weight: bold;
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import * as S from './MyMissionInProgress.styled';

export default function MyMissionInProgress() {
Copy link
Contributor

Choose a reason for hiding this comment

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

MyMissionXXX 형태의 통일성 있는 이름 넘 좋네요 👍🏻👍🏻

return (
<S.MyMissionInProgressContainer>
<S.Title>진행 중인 미션</S.Title>

<S.MyMissionInProgressWrapper>
<S.ThumbnailImg src="https://dszw1qtcnsa5e.cloudfront.net/community/20231115/ccefd368-2687-4d26-954d-712315f38fea/2232611625864AF59FF189AB61A75869.jpeg" />

<S.MissionContentWrapper>
<S.MissionTitle>숫자 야구 게임 미션</S.MissionTitle>
<S.MissionDate>2024.07.17 ~ 2024.07.24 23:59</S.MissionDate>
</S.MissionContentWrapper>

<S.MissionButtonWrapper>
<S.PrButtonWrapper>
<S.PrButton>
<S.GithubIcons />
페어 PR 이동
</S.PrButton>
<S.PrButton>
<S.GithubIcons />내 PR 이동
</S.PrButton>
</S.PrButtonWrapper>
<S.MissionCompleteWrapper>
<S.MissionCompleteButton>리뷰 완료</S.MissionCompleteButton>
</S.MissionCompleteWrapper>
</S.MissionButtonWrapper>
</S.MyMissionInProgressWrapper>
</S.MyMissionInProgressContainer>
);
}
1 change: 1 addition & 0 deletions frontend/src/constants/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ export const ROUTES = {
submit: '/submit/:id',
missionDetail: '/missions/:id',
profile: '/profile',
myMission: '/my-mission',
} as const;
9 changes: 9 additions & 0 deletions frontend/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { createBrowserRouter, RouterProvider } from 'react-router-dom';
import MissionDetailPage from './pages/MissionDetailPage';
import MissionListPage from './pages/MissionListPage';
import MissionSubmitPage from './pages/MissionSubmitPage';
import MyMissionPage from './pages/MyMissionPage';
import UserProfilePage from './pages/UserProfilePage';

const queryClient = new QueryClient();
Expand Down Expand Up @@ -48,6 +49,14 @@ const routes = [
</App>
),
},
{
path: ROUTES.myMission,
element: (
<App>
<MyMissionPage />
</App>
),
},
];

const router = createBrowserRouter(routes, {
Expand Down
10 changes: 10 additions & 0 deletions frontend/src/pages/MyMissionPage.styled.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import styled from 'styled-components';

export const MyMissionPageContainer = styled.div`
width: 100rem;
margin: 4rem auto;

display: flex;
flex-direction: column;
gap: 7rem;
`;
Loading