-
Notifications
You must be signed in to change notification settings - Fork 2
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
Feat/#513, #523 검색완료, 가수 상세 페이지 구현 및 메인페이지 케러셀 정책 변경 반영 #525
Merged
Merged
Changes from all commits
Commits
Show all changes
41 commits
Select commit
Hold shift + click to select a range
9c0abec
feat: 가수 상세 페이지 구현
Creative-Lee f7ebde1
refactor: 핸들러 로직 수정
Creative-Lee f8eb5c5
design: 반응형 디자인 추가
Creative-Lee 4ed3b2b
chore: type 파일 suffix 추가
Creative-Lee 9e40f33
refactor: 가수 디테일과 검색결과 type 분리
Creative-Lee 111c2e5
design: 미디어 쿼리 호버 적용
Creative-Lee b44dbab
chore: type 파일 이동
Creative-Lee 3873123
refactor: SingersSong type 추가
Creative-Lee 954d871
refactor: 컴포넌트 분리
Creative-Lee 65d0f1a
test: 스토리 추가
Creative-Lee 91a580a
design: 분리된 컴포넌트 색상 추가
Creative-Lee 749c9db
refactor: Spacing 컴포넌트 반응형 디자인 props 추가
Creative-Lee 217b9f4
feat: 노래 클릭 시 듣기페이지 이동 기능 구현
Creative-Lee 3e72fab
refactor: 컴포넌트 분리
Creative-Lee edc9df0
refactor: 하위 컴포넌트가 title을 가지도록 변경
Creative-Lee ca30a5f
feat: 검색 결과 페이지 구현
Creative-Lee c200e1f
chore: 노래 검색결과 픽스쳐 데이터 수정
Creative-Lee 41d756c
feat: 배너 클릭 시 가수페이지 이동 기능 구현
Creative-Lee 9db9be2
chore: 사용하지 않는 import 삭제 및 픽스쳐 데이터 삭제
Creative-Lee 2433293
feat: 배너 호버 시 아이콘 디테일 추가
Creative-Lee 56e7ab5
design: 아이콘 사이즈 축소
Creative-Lee 558733f
chore: 사용하지 않는 훅 삭제
Creative-Lee 5306d46
feat: 메인 케러셀 최근 추가된 노래 리스트로 대체
Creative-Lee d170a1a
feat: 메인 케러셀 최근 추가된 노래 리스트로 대체
Creative-Lee dd78f3c
feat: 최근 들은 노래 핸들러 및 fixture 구현
Creative-Lee 5afc6d2
design: 글로벌 스타일 수정
Creative-Lee 6417256
test: singer관련 스토리 추가
Creative-Lee 19518c8
design: title color 추가
Creative-Lee 16fd725
test: 스토리 title prefix 추가로 디렉터리 구분
Creative-Lee 0c108bd
design: 기본 배경색 지정
Creative-Lee 7da9bb3
Merge branch 'main' into feat/#513
Creative-Lee 1597a23
fix: lint 에러 해결
Creative-Lee 537b832
refactor: spacing 컴포넌트 변수명 변경으로 가독성 개선
Creative-Lee 5527cf6
refactor: null 명시
Creative-Lee fae6725
chore: singer 리모트 함수 파일 위치 변경
Creative-Lee 6afd8b0
refactor: api 명세 변경으로 의미 개선
Creative-Lee db32c8a
refactor: api 명세 변경 msw 반영 및 singer 핸들러 분리
Creative-Lee 29eae71
refactor: 함수 불리언 화 방식 변경
Creative-Lee 5dbd153
design: 페이지 별 title을 다르게 하여 디자인 구분 되로록 개선
Creative-Lee 0b989a1
refactor: params가 변경되면 데이터를 refetch하도록 변경
Creative-Lee cd49437
Merge branch 'main' into feat/#513
Creative-Lee File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
15 changes: 15 additions & 0 deletions
15
frontend/src/features/search/components/SearchPreviewSheet.stories.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import searchedSingerPreview from '@/mocks/fixtures/searchedSingerPreview.json'; | ||
import SearchPreviewSheet from './SearchPreviewSheet'; | ||
import type { Meta, StoryObj } from '@storybook/react'; | ||
|
||
const meta = { | ||
component: SearchPreviewSheet, | ||
title: 'search/SearchPreviewSheet', | ||
} satisfies Meta<typeof SearchPreviewSheet>; | ||
|
||
export default meta; | ||
type Story = StoryObj<typeof SearchPreviewSheet>; | ||
|
||
export const Default: Story = { | ||
render: () => <SearchPreviewSheet result={searchedSingerPreview} endSearch={() => {}} />, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,16 +1,13 @@ | ||
import fetcher from '@/shared/remotes'; | ||
import type { SingerSearchPreview, SingerSearchResult } from '../types/search'; | ||
import type { SingerDetail } from '../../singer/types/singer.type'; | ||
import type { SingerSearchPreview } from '../types/search.type'; | ||
|
||
export const getSingerSearchPreview = async (query: string): Promise<SingerSearchPreview[]> => { | ||
const encodedQuery = encodeURIComponent(query); | ||
return await fetcher(`/singers?name=${encodedQuery}&search=singer`, 'GET'); | ||
return await fetcher(`/search?keyword=${encodedQuery}&type=singer`, 'GET'); | ||
}; | ||
|
||
export const getSingerSearch = async (query: string): Promise<SingerSearchResult[]> => { | ||
export const getSingerSearch = async (query: string): Promise<SingerDetail[]> => { | ||
const encodedQuery = encodeURIComponent(query); | ||
return await fetcher(`/singers?name=${encodedQuery}&search=singer&search=song`, 'GET'); | ||
}; | ||
|
||
export const getSingerDetail = async (singerId: number): Promise<SingerSearchResult> => { | ||
return await fetcher(`/singers/${singerId}`, 'GET'); | ||
return await fetcher(`/search?keyword=${encodedQuery}&type=singer&type=song`, 'GET'); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
export interface SingerSearchPreview { | ||
id: number; | ||
singer: string; | ||
profileImageUrl: string; | ||
} |
15 changes: 15 additions & 0 deletions
15
frontend/src/features/singer/components/SingerBanner.stories.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import searchedSingers from '@/mocks/fixtures/searchedSingers.json'; | ||
import SingerBanner from './SingerBanner'; | ||
import type { Meta, StoryObj } from '@storybook/react'; | ||
|
||
const meta = { | ||
component: SingerBanner, | ||
title: 'singer/SingerBanner', | ||
} satisfies Meta<typeof SingerBanner>; | ||
|
||
export default meta; | ||
type Story = StoryObj<typeof SingerBanner>; | ||
|
||
export const Default: Story = { | ||
render: () => <SingerBanner {...searchedSingers[0]} />, | ||
}; |
107 changes: 107 additions & 0 deletions
107
frontend/src/features/singer/components/SingerBanner.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
import styled from 'styled-components'; | ||
import rightArrow from '@/assets/icon/right-long-arrow.svg'; | ||
import Flex from '@/shared/components/Flex/Flex'; | ||
import type { SingerDetail } from '@/features/singer/types/singer.type'; | ||
|
||
interface SingerBannerProps | ||
extends Pick<SingerDetail, 'profileImageUrl' | 'singer' | 'totalSongCount'> { | ||
onClick?: () => void; | ||
} | ||
|
||
const SingerBanner = ({ profileImageUrl, singer, totalSongCount, onClick }: SingerBannerProps) => { | ||
const clickable = typeof onClick === 'function'; | ||
|
||
return ( | ||
<Container> | ||
<Title>아티스트</Title> | ||
<SingerInfoContainer $align="center" $gap={24} onClick={onClick} $clickable={clickable}> | ||
<ProfileImage src={profileImageUrl} alt="" /> | ||
<Flex $direction="column" $gap={16}> | ||
<Name>{singer}</Name> | ||
<SongCount>{`등록된 노래 ${totalSongCount}개`}</SongCount> | ||
</Flex> | ||
</SingerInfoContainer> | ||
</Container> | ||
); | ||
}; | ||
|
||
export default SingerBanner; | ||
|
||
const Container = styled.div``; | ||
|
||
const SingerInfoContainer = styled(Flex)<{ $clickable: boolean }>` | ||
cursor: ${({ $clickable }) => ($clickable ? 'pointer' : 'default')}; | ||
|
||
position: relative; | ||
|
||
width: 370px; | ||
height: 240px; | ||
padding: 20px; | ||
|
||
color: ${({ theme: { color } }) => color.white}; | ||
|
||
background-color: ${({ theme: { color } }) => color.black500}; | ||
border-radius: 8px; | ||
|
||
transition: background-color 0.3s ease; | ||
|
||
@media (hover: hover) { | ||
&:hover { | ||
background-color: ${({ $clickable, theme: { color } }) => | ||
$clickable ? color.secondary : ''}; | ||
|
||
&::after { | ||
content: ''; | ||
|
||
position: absolute; | ||
right: 10px; | ||
bottom: 4px; | ||
|
||
width: 30px; | ||
height: 30px; | ||
|
||
background: ${({ $clickable }) => | ||
$clickable ? `no-repeat center url(${rightArrow})` : ''}; | ||
} | ||
} | ||
} | ||
|
||
@media (max-width: ${({ theme }) => theme.breakPoints.md}) { | ||
width: 100%; | ||
height: 180px; | ||
} | ||
`; | ||
|
||
const ProfileImage = styled.img` | ||
width: 100px; | ||
height: 100px; | ||
border-radius: 50%; | ||
`; | ||
|
||
const Name = styled.div` | ||
font-size: 36px; | ||
font-weight: 700; | ||
|
||
@media (max-width: ${({ theme }) => theme.breakPoints.md}) { | ||
font-size: 20px; | ||
} | ||
`; | ||
|
||
const SongCount = styled.p` | ||
font-size: 18px; | ||
|
||
@media (max-width: ${({ theme }) => theme.breakPoints.md}) { | ||
font-size: 14px; | ||
} | ||
`; | ||
|
||
const Title = styled.h1` | ||
margin-bottom: 18px; | ||
font-size: 28px; | ||
font-weight: 700; | ||
color: ${({ theme: { color } }) => color.white}; | ||
|
||
@media (max-width: ${({ theme }) => theme.breakPoints.md}) { | ||
font-size: 24px; | ||
} | ||
`; |
17 changes: 17 additions & 0 deletions
17
frontend/src/features/singer/components/SingerSongItem.stories.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import searchedSingers from '@/mocks/fixtures/searchedSingers.json'; | ||
import SingerSongItem from './SingerSongItem'; | ||
import type { Meta, StoryObj } from '@storybook/react'; | ||
|
||
const song = searchedSingers[0].songs[0]; | ||
|
||
const meta = { | ||
component: SingerSongItem, | ||
title: 'singer/SingerSongItem', | ||
} satisfies Meta<typeof SingerSongItem>; | ||
|
||
export default meta; | ||
type Story = StoryObj<typeof SingerSongItem>; | ||
|
||
export const Default: Story = { | ||
render: () => <SingerSongItem {...song} />, | ||
}; |
116 changes: 116 additions & 0 deletions
116
frontend/src/features/singer/components/SingerSongItem.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
import { Link } from 'react-router-dom'; | ||
import styled from 'styled-components'; | ||
import Flex from '@/shared/components/Flex/Flex'; | ||
import ROUTE_PATH from '@/shared/constants/path'; | ||
import { toMinSecText } from '@/shared/utils/convertTime'; | ||
import type { SingersSong } from '@/features/singer/types/singer.type'; | ||
|
||
interface SingerSongItemProps extends SingersSong {} | ||
|
||
const SingerSongItem = ({ id, singer, albumCoverUrl, title, videoLength }: SingerSongItemProps) => { | ||
return ( | ||
<SongListItem as="li"> | ||
<FlexLink to={`/${ROUTE_PATH.SONG_DETAILS}/${id}/ALL`}> | ||
<AlbumCoverWrapper> | ||
<AlbumCover src={albumCoverUrl} /> | ||
</AlbumCoverWrapper> | ||
<FlexInfo $direction="column" $gap={8} $xs={{ $gap: 4 }}> | ||
<SongTitle>{title}</SongTitle> | ||
<Singer>{singer}</Singer> | ||
</FlexInfo> | ||
<VideoLength as="span" $align="center" $justify="flex-end"> | ||
{toMinSecText(videoLength)} | ||
</VideoLength> | ||
</FlexLink> | ||
</SongListItem> | ||
); | ||
}; | ||
|
||
export default SingerSongItem; | ||
|
||
const FlexLink = styled(Link)` | ||
display: flex; | ||
gap: 16px; | ||
justify-content: center; | ||
width: 100%; | ||
|
||
@media (max-width: ${({ theme }) => theme.breakPoints.xs}) { | ||
align-items: center; | ||
} | ||
`; | ||
|
||
const SongListItem = styled.li` | ||
width: 100%; | ||
padding: 8px; | ||
|
||
color: ${({ theme: { color } }) => color.white}; | ||
|
||
background-color: ${({ theme: { color } }) => color.black}; | ||
border-radius: 4px; | ||
|
||
transition: background-color 0.3s ease; | ||
|
||
@media (hover: hover) { | ||
&:hover { | ||
background-color: ${({ theme: { color } }) => color.secondary}; | ||
} | ||
} | ||
|
||
@media (max-width: ${({ theme }) => theme.breakPoints.xs}) { | ||
border-radius: 0; | ||
} | ||
`; | ||
|
||
const AlbumCoverWrapper = styled.div` | ||
aspect-ratio: 1 / 1; | ||
width: 100px; | ||
|
||
@media (max-width: ${({ theme }) => theme.breakPoints.md}) { | ||
width: 70px; | ||
} | ||
`; | ||
|
||
const AlbumCover = styled.img` | ||
width: 100%; | ||
`; | ||
|
||
const FlexInfo = styled(Flex)` | ||
overflow: hidden; | ||
flex: 3 1 0; | ||
|
||
padding: 8px 0; | ||
|
||
text-overflow: ellipsis; | ||
white-space: nowrap; | ||
|
||
@media (max-width: ${({ theme }) => theme.breakPoints.md}) { | ||
padding: 6px 0; | ||
} | ||
`; | ||
|
||
const SongTitle = styled.div` | ||
overflow: hidden; | ||
|
||
font-size: 16px; | ||
font-weight: 700; | ||
text-overflow: ellipsis; | ||
white-space: nowrap; | ||
`; | ||
|
||
const Singer = styled.div` | ||
overflow: hidden; | ||
|
||
font-size: 14px; | ||
color: ${({ theme: { color } }) => color.subText}; | ||
text-overflow: ellipsis; | ||
white-space: nowrap; | ||
`; | ||
|
||
const VideoLength = styled(Flex)` | ||
flex: 1 0 0; | ||
font-size: 16px; | ||
|
||
@media (max-width: ${({ theme }) => theme.breakPoints.md}) { | ||
font-size: 14px; | ||
} | ||
`; |
17 changes: 17 additions & 0 deletions
17
frontend/src/features/singer/components/SingerSongList.stories.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import singerSongs from '@/mocks/fixtures/searchedSingers.json'; | ||
import SingerSongList from './SingerSongList'; | ||
import type { Meta, StoryObj } from '@storybook/react'; | ||
|
||
const singerSong = singerSongs[0]; | ||
|
||
const meta = { | ||
component: SingerSongList, | ||
title: 'singer/SingerSongList', | ||
} satisfies Meta<typeof SingerSongList>; | ||
|
||
export default meta; | ||
type Story = StoryObj<typeof SingerSongList>; | ||
|
||
export const Default: Story = { | ||
render: () => <SingerSongList songs={singerSong.songs} title="곡" />, | ||
}; |
44 changes: 44 additions & 0 deletions
44
frontend/src/features/singer/components/SingerSongList.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import styled from 'styled-components'; | ||
import Flex from '@/shared/components/Flex/Flex'; | ||
import SingerSongItem from './SingerSongItem'; | ||
import type { SingersSong } from '../types/singer.type'; | ||
|
||
interface SingerSongListProps { | ||
songs: SingersSong[]; | ||
title: string; | ||
} | ||
|
||
const SingerSongList = ({ songs, title }: SingerSongListProps) => { | ||
return ( | ||
<Container> | ||
<Title>{title}</Title> | ||
<SongsItemList as="ol" $direction="column" $gap={12} $align="center"> | ||
{songs.map((song) => ( | ||
<SingerSongItem key={song.id} {...song} /> | ||
))} | ||
</SongsItemList> | ||
</Container> | ||
); | ||
}; | ||
|
||
export default SingerSongList; | ||
|
||
const Container = styled.div` | ||
flex: 1; | ||
`; | ||
|
||
const SongsItemList = styled(Flex)` | ||
overflow-y: scroll; | ||
width: 100%; | ||
`; | ||
|
||
const Title = styled.h2` | ||
margin-bottom: 18px; | ||
font-size: 28px; | ||
font-weight: 700; | ||
color: ${({ theme: { color } }) => color.white}; | ||
|
||
@media (max-width: ${({ theme }) => theme.breakPoints.xs}) { | ||
font-size: 24px; | ||
} | ||
`; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import fetcher from '@/shared/remotes'; | ||
import type { SingerDetail } from '../types/singer.type'; | ||
|
||
export const getSingerDetail = async (singerId: number): Promise<SingerDetail> => { | ||
return await fetcher(`/singers/${singerId}`, 'GET'); | ||
}; |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
💬 type alias 과 비교해 어떤 것이 이해하기 쉬운가용?
저는 여러 타입 연산을 사용하거나 유틸리티 타입을 이용하는 경우 type alias가 해석하기 쉽긴 합니다.
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.
저도 동감합니다~ 자동 개행되는 부분이 별로 안이쁘긴 하더라고요
개인적으로는 이제 type을 더 선호하게 된것 ? 같기도 합니다 ㅋㅋ
다만, 저희 컨벤션대로 interface를 사용할 수 밖에 없었습니다 ㅎ...