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

[FE] issue53: 디테일 페이지 구현 #88

Merged
merged 19 commits into from
Jul 19, 2022
Merged
Show file tree
Hide file tree
Changes from 16 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
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
frontend/**/*.json linguist-generated
3 changes: 3 additions & 0 deletions frontend/.prettierrc.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions frontend/.storybook/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@ module.exports = {
'@types': resolve(__dirname, '../src/types'),
'@pages': resolve(__dirname, '../src/pages'),
'@assets': resolve(__dirname, '../src/assets'),
'@utils': resolve(__dirname, '../src/utils'),
'@constants': resolve(__dirname, '../src/constants.ts'),
'@api': resolve(__dirname, '../src/api'),
'@context': resolve(__dirname, '../src/context'),
'@detail-page': resolve(__dirname, '../src/pages/detail-page'),
Copy link
Collaborator

Choose a reason for hiding this comment

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

ㅇ여기에는 왜 main-page는 ㅇ없나요?!

그리고 pages가 있는데 그 내부 폴더들도 따로 만든 이유가 무엇인가요?

'@layout': resolve(__dirname, '../src/layout'),
};

Expand Down
7,764 changes: 3,670 additions & 4,094 deletions frontend/package-lock.json

Large diffs are not rendered by default.

7 changes: 6 additions & 1 deletion frontend/package.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion frontend/public/mockServiceWorker.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
/* tslint:disable */

/**
* Mock Service Worker (0.43.0).
* Mock Service Worker (0.43.1).
* @see https://github.com/mswjs/msw
* - Please do NOT modify this file.
* - Please do NOT serve this file on production.
Expand Down
13 changes: 10 additions & 3 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import { BrowserRouter, Route, Routes } from 'react-router-dom';

import { css } from '@emotion/react';

import Footer from '@layout/footer/Footer';
import Header from '@layout/header/Header';

import MainPage from '@pages/main-page/MainPage';

import DetailPage from '@detail-page/DetailPage';

const App = () => {
return (
<div>
<BrowserRouter>
<Header
css={css`
position: fixed;
Expand All @@ -23,10 +27,13 @@ const App = () => {
min-height: calc(100vh - 80px);
`}
>
<MainPage />
<Routes>
<Route path="/" element={<MainPage />} />
<Route path="/study/:studyId" element={<DetailPage />} />
Comment on lines +31 to +32
Copy link
Collaborator

Choose a reason for hiding this comment

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

예외 페이지 처리도 해주면 좋을 것 같네요!
path="*" element={<ErrorPage />}

Copy link
Collaborator

Choose a reason for hiding this comment

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

나중에 로그인/로그아웃 했을 때만 갈 수 있는 페이지도 처리합시당

</Routes>
</main>
<Footer />
</div>
</BrowserRouter>
);
};

Expand Down
10 changes: 10 additions & 0 deletions frontend/src/api/getStudyDetail.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { StudyDetail } from '@custom-types/index';

import axiosInstance from './axiosInstance';

const getStudyDetail = async (studyId: string): Promise<{ study: StudyDetail }> => {
const response = await axiosInstance.get<{ study: StudyDetail }>(`/api/study/?study-id=${studyId}`);
Copy link
Collaborator

Choose a reason for hiding this comment

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

url 오류!!!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

수정 했슴다!

return response.data;
};

export default getStudyDetail;
16 changes: 16 additions & 0 deletions frontend/src/api/getStudyReviews.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { StudyReview } from '@custom-types/index';

import axiosInstance from './axiosInstance';

Copy link
Collaborator

Choose a reason for hiding this comment

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

절대 경로!

export type StudyReviewResponse = {
reviews: Array<StudyReview>;
totalResults: number;
};

const getStudyReviews = async (studyId: string, size: number, loadAll: boolean): Promise<StudyReviewResponse> => {
const url = loadAll ? `/api/studies/${studyId}/review` : `/api/studies/${studyId}/review?size=${size}`;
const response = await axiosInstance.get<StudyReviewResponse>(url);
Copy link
Collaborator

Choose a reason for hiding this comment

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

size를 옵셔널로 해서 구현하는 게 더 낫지 않을까요?

  • size가 들어오면 일부, size가 없으면 전체

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

좋네요 !

return response.data;
};

export default getStudyReviews;
22 changes: 22 additions & 0 deletions frontend/src/components/Avatar/Avatar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { MakeOptional } from '@custom-types/index';

import * as S from './Avatar.style';

export type AvatarProps = {
className?: string;
profileImg: string;
profileAlt: string;
size: 'sm' | 'md' | 'lg';
};

type OptionalAvatarProps = MakeOptional<AvatarProps, 'size'>;

const Avatar: React.FC<OptionalAvatarProps> = ({ className, size = 'sm', profileImg, profileAlt }) => {
return (
<S.Avatar className={className} size={size}>
<S.Image src={profileImg} alt={profileAlt} />
</S.Avatar>
);
};

export default Avatar;
22 changes: 22 additions & 0 deletions frontend/src/components/avatar/Avatar.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Story } from '@storybook/react';

import Avatar from '@components/avatar/Avatar';
import type { AvatarProps } from '@components/avatar/Avatar';

export default {
title: 'Components/Avatar',
component: Avatar,
argTypes: {
profileImg: { controls: 'text' },
profileAlt: { controls: 'text' },
},
};

const Template: Story<AvatarProps> = props => <Avatar {...props} />;

export const Default = Template.bind({});
Default.args = {
profileImg:
'https://images.unsplash.com/photo-1438761681033-6461ffad8d80?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1770&q=80',
profileAlt: '프로필 이미지',
};
32 changes: 30 additions & 2 deletions frontend/src/components/avatar/Avatar.style.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,33 @@
import { css } from '@emotion/react';
import { SerializedStyles, css } from '@emotion/react';
import styled from '@emotion/styled';

export const ImageContainer = styled.div`
import type { AvatarProps } from './Avatar';

const dynamicSize = {
sm: css`
width: 36px;
min-width: 36px;
height: 36px;
`,
md: css`
width: 42px;
min-width: 42px;
height: 42px;
`,
lg: css`
width: 65px;
min-width: 65px;
height: 65px;
`,
};

type DynamicImageContainerFn = (props: Pick<AvatarProps, 'size'>) => SerializedStyles;

const dynamicImageContainer: DynamicImageContainerFn = props => css`
Comment on lines +24 to +26
Copy link
Collaborator

Choose a reason for hiding this comment

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

타입 나누니까 훨씬 보기 좋네요!

${dynamicSize[props.size]}
`;

export const Avatar = styled.div`
${({ theme }) => css`
width: 36px;
min-width: 36px;
Expand All @@ -16,6 +42,8 @@ export const ImageContainer = styled.div`
opacity: 0.9;
}
`}

${dynamicImageContainer}
`;

export const Image = styled.img`
Expand Down
18 changes: 12 additions & 6 deletions frontend/src/components/avatar/Avatar.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
import * as S from '@components/avatar/Avatar.style';
import { MakeOptional } from '@custom-types/index';

export interface AvatarProps {
import * as S from './Avatar.style';
Copy link
Collaborator

Choose a reason for hiding this comment

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

절대경로!!


export type AvatarProps = {
className?: string;
profileImg: string;
profileAlt: string;
}
size: 'sm' | 'md' | 'lg';
};

type OptionalAvatarProps = MakeOptional<AvatarProps, 'size'>;

const Avatar: React.FC<AvatarProps> = ({ profileImg, profileAlt }) => {
const Avatar: React.FC<OptionalAvatarProps> = ({ className, size = 'sm', profileImg, profileAlt }) => {
return (
<S.ImageContainer>
<S.Avatar className={className} size={size}>
<S.Image src={profileImg} alt={profileAlt} />
</S.ImageContainer>
</S.Avatar>
);
};

Expand Down
16 changes: 16 additions & 0 deletions frontend/src/components/button/Button.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Story } from '@storybook/react';

import Button from './Button';
import type { ButtonProp } from './Button';
Copy link
Collaborator

Choose a reason for hiding this comment

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

절대경로!!


export default {
title: 'Components/Button',
component: Button,
};

const Template: Story<ButtonProp> = props => <Button {...props} />;

export const Default = Template.bind({});
Default.args = {
children: '스터디방 가입하기',
};
19 changes: 19 additions & 0 deletions frontend/src/components/button/Button.style.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { css } from '@emotion/react';
import styled from '@emotion/styled';

import type { ButtonProp } from '@components/button/Button';

export const Button = styled.button<ButtonProp>`
${({ fluid }) => css`
width: ${fluid ? '100%' : 'auto'};
padding: 20px 10px;
text-align: center;

border: none;
border-radius: 10px;
background: #1a237e;
color: white;

white-space: nowrap;
`}
`;
24 changes: 24 additions & 0 deletions frontend/src/components/button/Button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { noop } from '@utils/index';

import { MakeOptional } from '@custom-types/index';

import * as S from './Button.style';

export type ButtonProp = {
className?: string;
children: string;
fluid: boolean;
Copy link
Collaborator

Choose a reason for hiding this comment

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

fluid가 뭔가용??

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

양옆으로 늘어난다는 의미입니다!

onClick: () => void;
Copy link
Collaborator

Choose a reason for hiding this comment

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

이건 React.MouseEventHandler<HTMLButtonElement>가 더 맞지 않을까요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

맞습니다!

};

type OptionalButtonProp = MakeOptional<ButtonProp, 'fluid' | 'onClick'>;

const Button: React.FC<OptionalButtonProp> = ({ className, children, onClick = noop, fluid = true }) => {
Copy link
Collaborator

Choose a reason for hiding this comment

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

버튼 클릭이벤트인데 noop을 안 주는 게 더 오류 방지에 좋을 것 같습니다! 혹시 클릭이벤트가 발생하면 안되는 버튼이 있나요?

Copy link
Collaborator Author

@airman5573 airman5573 Jul 19, 2022

Choose a reason for hiding this comment

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

form의 submit같은 경우에는 onClick이 없어도 될것 같습니다! 실제로 button의 onClick도 optional입니다 :D

return (
<S.Button className={className} fluid={fluid} onClick={onClick}>
{children}
</S.Button>
);
};

export default Button;
37 changes: 37 additions & 0 deletions frontend/src/components/card/Card.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Story } from '@storybook/react';

import type { CardProps } from '@components/card/Card';
import Card from '@components/card/Card';
import Chip from '@components/chip/Chip';

export default {
title: 'Components/Card',
component: Card,
argTypes: {
thumbnailUrl: { controls: 'text' },
thumbnailAlt: { controls: 'text' },
title: { controls: 'text' },
description: { controls: 'text' },
extraChips: { controls: 'object' },
},
};

const Template: Story<CardProps> = props => (
<div style={{ width: '256px' }}>
<Card {...props} />
</div>
);

export const Default = Template.bind({});
Default.args = {
thumbnailUrl:
'https://images.unsplash.com/photo-1456513080510-7bf3a84b82f8?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1673&q=80',
thumbnailAlt: '이미지 Alt',
title: '타이틀',
description: '세부 설명',
extraChips: [
<Chip disabled={false} key="1">
Chip
</Chip>,
],
};
35 changes: 35 additions & 0 deletions frontend/src/components/chip/Chip.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { Story } from '@storybook/react';

import type { ChipProps } from '@components/chip/Chip';
import Chip from '@components/chip/Chip';

export default {
title: 'Components/Chip',
component: Chip,
argTypes: {
children: { controls: 'text' },
disabled: { controls: 'boolean' },
},
};

const Template: Story<ChipProps> = props => <Chip {...props} />;

export const Default = Template.bind({});
Default.args = {
children: '모집중',
disabled: false,
};

export const ActiveChip = Template.bind({});
ActiveChip.args = {
children: '모집중',
disabled: false,
};
ActiveChip.parameters = { controls: { exclude: ['disabled'] } };

export const DisabledChip = Template.bind({});
DisabledChip.args = {
children: '모집완료',
disabled: true,
};
DisabledChip.parameters = { controls: { exclude: ['disabled'] } };
Loading