-
Notifications
You must be signed in to change notification settings - Fork 5
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
Changes from 16 commits
46a9bff
fe0a5b3
6a22b22
79dc6a5
10c0e89
508b4f1
f2c7574
6b01277
d131f7a
f493287
c8419eb
e3e8122
66a31ed
d5d7ed3
3a39981
b7dc853
ef28bb3
ac743f9
8346039
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
frontend/**/*.json linguist-generated |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Large diffs are not rendered by default.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
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; | ||
|
@@ -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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 예외 페이지 처리도 해주면 좋을 것 같네요! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 나중에 로그인/로그아웃 했을 때만 갈 수 있는 페이지도 처리합시당 |
||
</Routes> | ||
</main> | ||
<Footer /> | ||
</div> | ||
</BrowserRouter> | ||
); | ||
}; | ||
|
||
|
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}`); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. url 오류!!! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 수정 했슴다! |
||
return response.data; | ||
}; | ||
|
||
export default getStudyDetail; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import { StudyReview } from '@custom-types/index'; | ||
|
||
import axiosInstance from './axiosInstance'; | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. size를 옵셔널로 해서 구현하는 게 더 낫지 않을까요?
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 좋네요 ! |
||
return response.data; | ||
}; | ||
|
||
export default getStudyReviews; |
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; |
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: '프로필 이미지', | ||
}; |
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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
|
@@ -16,6 +42,8 @@ export const ImageContainer = styled.div` | |
opacity: 0.9; | ||
} | ||
`} | ||
|
||
${dynamicImageContainer} | ||
`; | ||
|
||
export const Image = styled.img` | ||
|
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'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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> | ||
); | ||
}; | ||
|
||
|
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'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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: '스터디방 가입하기', | ||
}; |
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; | ||
`} | ||
`; |
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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fluid가 뭔가용?? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 양옆으로 늘어난다는 의미입니다! |
||
onClick: () => void; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이건 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 }) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 버튼 클릭이벤트인데 noop을 안 주는 게 더 오류 방지에 좋을 것 같습니다! 혹시 클릭이벤트가 발생하면 안되는 버튼이 있나요? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; |
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>, | ||
], | ||
}; |
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'] } }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ㅇ여기에는 왜 main-page는 ㅇ없나요?!
그리고 pages가 있는데 그 내부 폴더들도 따로 만든 이유가 무엇인가요?