-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
* feat: 가수 상세 페이지 구현 * refactor: 핸들러 로직 수정 * design: 반응형 디자인 추가 * chore: type 파일 suffix 추가 * refactor: 가수 디테일과 검색결과 type 분리 * design: 미디어 쿼리 호버 적용 * chore: type 파일 이동 * refactor: SingersSong type 추가 * refactor: 컴포넌트 분리 * test: 스토리 추가 * design: 분리된 컴포넌트 색상 추가 * refactor: Spacing 컴포넌트 반응형 디자인 props 추가 * feat: 노래 클릭 시 듣기페이지 이동 기능 구현 * refactor: 컴포넌트 분리 리스트 컴포넌트 분리 * refactor: 하위 컴포넌트가 title을 가지도록 변경 * feat: 검색 결과 페이지 구현 * chore: 노래 검색결과 픽스쳐 데이터 수정 * feat: 배너 클릭 시 가수페이지 이동 기능 구현 * chore: 사용하지 않는 import 삭제 및 픽스쳐 데이터 삭제 * feat: 배너 호버 시 아이콘 디테일 추가 * design: 아이콘 사이즈 축소 * chore: 사용하지 않는 훅 삭제 * feat: 메인 케러셀 최근 추가된 노래 리스트로 대체 * feat: 메인 케러셀 최근 추가된 노래 리스트로 대체 * feat: 최근 들은 노래 핸들러 및 fixture 구현 * design: 글로벌 스타일 수정 스토리북에서 li요소 단일 랜더 시 ::marker 표시 이슈로 li list styled none 추가 * test: singer관련 스토리 추가 * design: title color 추가 * test: 스토리 title prefix 추가로 디렉터리 구분 * design: 기본 배경색 지정 * fix: lint 에러 해결 * refactor: spacing 컴포넌트 변수명 변경으로 가독성 개선 * refactor: null 명시 * chore: singer 리모트 함수 파일 위치 변경 * refactor: api 명세 변경으로 의미 개선 검색 api 엔드포인트 변경 singer -> search / 검색 의미 강조하고자 함 name -> keyword / 범용적인 검색어의 의미를 주고자 함 search -> type / 검색 타입의 의미를 주고자함 * refactor: api 명세 변경 msw 반영 및 singer 핸들러 분리 * refactor: 함수 불리언 화 방식 변경 * design: 페이지 별 title을 다르게 하여 디자인 구분 되로록 개선 * refactor: params가 변경되면 데이터를 refetch하도록 변경
- Loading branch information
1 parent
27455eb
commit bab208b
Showing
38 changed files
with
673 additions
and
133 deletions.
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.