-
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
[Feature] - 마이 페이지 구현 #236
Changes from all commits
907e35a
e1a7409
b916835
b2ef02d
d9e14a8
6b780c4
6c6695c
fcd53a8
3352e04
536ee5d
3b997d3
81877fe
1838659
3c981b5
ea86c3a
46ef7eb
b96c57b
267c5f8
794de85
e6d8c7d
0f321c5
117fe4b
f872f57
760705a
1ecb243
b74a499
c1fac1d
2649e6f
937d37c
14f500c
a0d5d6c
3d4a5b8
31614a6
2b16133
e9f82ce
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 |
---|---|---|
|
@@ -6,15 +6,16 @@ import type { AvatarCircleSize } from "./AvatarCircle.type"; | |
interface AvatarCircleProps { | ||
$size?: AvatarCircleSize; | ||
profileImageUrl?: string; | ||
imageAlt?: string; | ||
} | ||
|
||
const AvatarCircle = ({ $size = "small", profileImageUrl }: AvatarCircleProps) => { | ||
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. $ prefix는 @emotion/styled를 사용하는 영역에서 필요한 것이라고 이해했는데 |
||
const AvatarCircle = ({ $size = "small", profileImageUrl, imageAlt }: AvatarCircleProps) => { | ||
const { imageError, handleImageError } = useImageError({ imageUrl: profileImageUrl }); | ||
|
||
return ( | ||
<S.AvatarCircleContainer $size={$size}> | ||
{!imageError ? ( | ||
<img src={profileImageUrl} alt="사용자 프로필 이미지" onError={handleImageError} /> | ||
<img src={profileImageUrl} alt={imageAlt} onError={handleImageError} /> | ||
) : ( | ||
<S.FallbackIcon $size={$size}> | ||
<svg | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
export type AvatarCircleSize = "small" | "large"; | ||
export type AvatarCircleSize = "small" | "medium" | "large"; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export const FIRST_TAB_INDEX = 0; | ||
export const INITIAL_START_X = 0; | ||
export const INITIAL_SCROLL_LEFT = 0; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
import { css } from "@emotion/react"; | ||
import styled from "@emotion/styled"; | ||
|
||
import theme from "@styles/theme"; | ||
import { PRIMITIVE_COLORS } from "@styles/tokens"; | ||
|
||
export const Layout = styled.div` | ||
display: flex; | ||
flex-direction: column; | ||
align-items: center; | ||
width: 100%; | ||
gap: ${(props) => props.theme.spacing.xl}; | ||
padding: ${(props) => props.theme.spacing.l}; | ||
`; | ||
|
||
export const BoxButton = styled.button` | ||
display: flex; | ||
gap: ${(props) => props.theme.spacing.m}; | ||
justify-content: flex-start; | ||
align-items: center; | ||
width: 100%; | ||
padding: ${(props) => props.theme.spacing.m}; | ||
border: 1px solid ${(props) => props.theme.colors.border}; | ||
border-radius: 10px; | ||
`; | ||
|
||
export const ColorButtonStyle = css` | ||
display: flex; | ||
justify-content: flex-start; | ||
align-items: center; | ||
width: 100%; | ||
height: 6.8rem; | ||
padding: ${theme.spacing.m}; | ||
border-radius: 10px; | ||
|
||
background-color: ${PRIMITIVE_COLORS.blue[50]}; | ||
gap: ${theme.spacing.m}; | ||
`; | ||
|
||
export const ListStyle = css` | ||
li { | ||
${theme.typography.mobile.body}; | ||
font-weight: 700; | ||
} | ||
`; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import { css } from "@emotion/react"; | ||
|
||
import { useUserProfile } from "@queries/useUserProfile"; | ||
|
||
import { AvatarCircle, Tab, Text } from "@components/common"; | ||
|
||
import * as S from "./MyPage.styled"; | ||
import MyTravelPlans from "./MyTravelPlans/MyTravelPlans"; | ||
import MyTravelogues from "./MyTravelogues/MyTravelogues"; | ||
|
||
const MyPage = () => { | ||
const { data } = useUserProfile(); | ||
|
||
return ( | ||
<S.Layout> | ||
<AvatarCircle $size="large" profileImageUrl={data?.profileImageUrl} /> | ||
<Text | ||
textType="body" | ||
css={css` | ||
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. 700으로 통일해주시면 좋을거 같슴다 |
||
font-weight: 700; | ||
`} | ||
> | ||
{data?.nickname} | ||
</Text> | ||
|
||
<Tab | ||
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. 제가 render props로 바꿔놨는데도 잘 활용해주셨네요 :) |
||
labels={["내 여행 계획", "내 여행기"]} | ||
tabContent={(selectedIndex) => ( | ||
<>{selectedIndex === 0 ? <MyTravelPlans /> : <MyTravelogues />}</> | ||
)} | ||
Comment on lines
+28
to
+30
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. 엄청 깔끔하네요! |
||
css={S.ListStyle} | ||
/> | ||
</S.Layout> | ||
); | ||
}; | ||
|
||
export default MyPage; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import { css } from "@emotion/react"; | ||
import styled from "@emotion/styled"; | ||
|
||
import theme from "@styles/theme"; | ||
import { PRIMITIVE_COLORS } from "@styles/tokens"; | ||
|
||
export const Container = styled.div` | ||
display: flex; | ||
gap: ${(props) => props.theme.spacing.s}; | ||
flex-direction: column; | ||
justify-content: space-between; | ||
align-items: flex-start; | ||
`; | ||
|
||
export const List = styled.ul` | ||
display: flex; | ||
flex-direction: column; | ||
width: 100%; | ||
gap: ${(props) => props.theme.spacing.m}; | ||
`; | ||
|
||
export const ColorButtonStyle = css` | ||
display: flex; | ||
justify-content: flex-start; | ||
align-items: center; | ||
width: 100%; | ||
height: 6.8rem; | ||
padding: ${theme.spacing.m}; | ||
border-radius: 10px; | ||
|
||
background-color: ${PRIMITIVE_COLORS.blue[50]}; | ||
gap: ${theme.spacing.m}; | ||
`; | ||
|
||
export const BoxButton = styled.button` | ||
display: flex; | ||
gap: ${(props) => props.theme.spacing.m}; | ||
justify-content: flex-start; | ||
align-items: center; | ||
width: 100%; | ||
padding: ${(props) => props.theme.spacing.m}; | ||
border: 1px solid ${(props) => props.theme.colors.border}; | ||
border-radius: 10px; | ||
`; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import { css } from "@emotion/react"; | ||
|
||
import { IconButton } from "@components/common"; | ||
|
||
import { SEMANTIC_COLORS } from "@styles/tokens"; | ||
|
||
import * as S from "./MyPageTabContent.styled"; | ||
|
||
const ICON_BUTTON_TEXT = { | ||
ADD_TRAVEL_PLAN: "새 여행 계획 추가하기", | ||
ADD_TRAVELOGUE: "새 여행기 추가하기", | ||
} as const; | ||
|
||
interface MyPageTabContentProps<T extends { id: string }> { | ||
iconButtonLabel: (typeof ICON_BUTTON_TEXT)[keyof typeof ICON_BUTTON_TEXT]; | ||
onClickIconButton: () => void; | ||
contentDetail: T[]; | ||
renderItem: (item: T) => React.ReactNode; | ||
} | ||
|
||
const MyPageTabContent = <T extends { id: string }>({ | ||
contentDetail, | ||
iconButtonLabel, | ||
onClickIconButton, | ||
renderItem, | ||
}: React.PropsWithChildren<MyPageTabContentProps<T>>) => { | ||
return ( | ||
<S.List> | ||
<IconButton | ||
size="16" | ||
position="left" | ||
iconType="plus" | ||
color={SEMANTIC_COLORS.primary} | ||
css={[ | ||
S.ColorButtonStyle, | ||
css` | ||
font-weight: 600; | ||
`, | ||
]} | ||
onClick={onClickIconButton} | ||
> | ||
{iconButtonLabel} | ||
</IconButton> | ||
|
||
{contentDetail.map((item) => ( | ||
<S.BoxButton key={item.id}>{renderItem(item)}</S.BoxButton> | ||
))} | ||
</S.List> | ||
); | ||
}; | ||
|
||
export default MyPageTabContent; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import { css } from "@emotion/react"; | ||
import styled from "@emotion/styled"; | ||
|
||
import theme from "@styles/theme"; | ||
|
||
export const Layout = styled.div` | ||
display: flex; | ||
justify-content: space-between; | ||
align-items: center; | ||
gap: ${(props) => props.theme.spacing.s}; | ||
width: 100%; | ||
`; | ||
|
||
export const Container = styled.div` | ||
display: flex; | ||
flex-direction: column; | ||
align-items: flex-start; | ||
gap: ${(props) => props.theme.spacing.s}; | ||
`; | ||
|
||
export const iconButtonStyle = css` | ||
display: flex; | ||
gap: ${theme.spacing.s}; | ||
padding: ${theme.spacing.m}; | ||
border: 1px solid ${theme.colors.border}; | ||
border-radius: 10px; | ||
${theme.typography.mobile.detail}; | ||
`; |
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.
이 부분 switch 문을 사용해도 좋을 거 같아요 아니면
이런식으로 하는 것도 좋아보이네요! 또 css로 해주는게 좀 더 의미 전달이 명확하다는 피드백을 받았는데 참고해보고 판단하면 될 것 같습니다!