Skip to content

Commit

Permalink
Refactor/#433 서비스 메인페이지에 카테고리 별 목록을 추가하여 디자인을 개선한다. (#440)
Browse files Browse the repository at this point in the history
* design: 노래 리스트 Flex direction 수정

* refactor: 컴포넌트 재사용을 위한 파일명, 컴포넌트 명 변경

* refactor: MainPage의 좋아요순 노래 목록 SongItemList 컴포넌트로 분리

* refactor: 정책 변경으로 type 명 변경

각 카테고리의 모든 노래가 좋아요 순으로 정렬되므로 변경하였음.
범용적으로 사용할 수 있도록 popular prefix 제거

* refactor: 컴포넌트에서 remote 함수 분리

* feat: 장르 type 필드 추가

* refactor: 변경된 api 명세 반영

장르 쿼리파람 추가

* chore: 파일명, 상수명 단수 복수 오류 수정

* refactor: 썸네일 컴포넌트 사이즈 추가 및 radius 수정

* feat: 전체장르 추가 및 remote 함수 수정

* feat: 장르별 top10 fetch 기능 구현

* refactor: Songitem의 순위, 좋아요 수 삭제

정책 변경으로 인한 디자인 수정

* refactor: msw fixture 데이터 수정

앨범 커버 resize 옵션 수정

* style: 스타일린트 적용

* design: 케러셀 border radius 통일

* feat: useValidParams 커스텀 훅 추가

params의 undefined 검증 + 에러를 해결해주고 원하는 type이 추론되도록 하는 훅

* refactor: 변경된 api 정책 반영

api url 경로 수정 및 쿼리파람 추가

* fix: useExtraFetch type 에러로 제네릭 수정

2개이상의 인자를 허용하도록 수정

* refactor: 스와이프 페이지 route 경로에 genre path parameter 추가

* fix: 라우트 경로 오류 수정

* refactor: 메인 페이지 아이템 첫 로드시에도 랜더되지 않도록 분기 추가
  • Loading branch information
Creative-Lee authored Sep 21, 2023
1 parent d78c279 commit dbd4c8b
Show file tree
Hide file tree
Showing 17 changed files with 289 additions and 204 deletions.
15 changes: 9 additions & 6 deletions frontend/src/features/songs/components/CarouselItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,41 +41,44 @@ const Wrapper = styled.li`
const CollectingLink = styled(Link)`
display: flex;
justify-content: center;
padding: 10px;
`;

const Album = styled.img`
max-width: 120px;
background-color: white;
border-radius: 4px;
`;

const Contents = styled.div`
display: flex;
flex-direction: column;
align-items: start;
justify-content: space-evenly;
width: 150px;
white-space: nowrap;
justify-content: space-evenly;
`;

const Title = styled.p`
overflow: hidden;
max-width: 150px;
margin-left: 0;
font-size: 18px;
font-weight: 700;
max-width: 150px;
text-overflow: ellipsis;
`;

const Singer = styled.p`
overflow: hidden;
margin-left: 0;
font-size: 14px;
max-width: 150px;
margin-left: 0;
font-size: 14px;
text-overflow: ellipsis;
`;

Expand Down
80 changes: 0 additions & 80 deletions frontend/src/features/songs/components/PopularSongItem.tsx

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import popularSongs from '@/mocks/fixtures/popularSongs.json';
import PopularSongItem from './PopularSongItem';
import SongItem from './SongItem';
import type { Meta, StoryObj } from '@storybook/react';

const meta: Meta<typeof PopularSongItem> = {
component: PopularSongItem,
const meta: Meta<typeof SongItem> = {
component: SongItem,
};

export default meta;

type Story = StoryObj<typeof PopularSongItem>;
type Story = StoryObj<typeof SongItem>;

const { title, singer, albumCoverUrl, totalLikeCount } = popularSongs[0];

Expand Down
49 changes: 49 additions & 0 deletions frontend/src/features/songs/components/SongItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { styled } from 'styled-components';
import Spacing from '@/shared/components/Spacing';
import Thumbnail from './Thumbnail';

interface SongItemProps {
rank: number;
title: string;
singer: string;
albumCoverUrl: string;
totalLikeCount: number;
}

const SongItem = ({ albumCoverUrl, title, singer }: SongItemProps) => {
return (
<Flex>
<Thumbnail size="xl" src={albumCoverUrl} alt={`${title}-${singer}`} />
<Spacing direction="vertical" size={4} />
<SongTitle>{title}</SongTitle>
<Singer>{singer}</Singer>
</Flex>
);
};

export default SongItem;

const Flex = styled.div`
display: flex;
flex-direction: column;
color: ${({ theme: { color } }) => color.white};
`;

const SongTitle = styled.div`
overflow: hidden;
grid-area: title;
font-size: 14px;
font-weight: 700;
text-overflow: ellipsis;
white-space: nowrap;
`;

const Singer = styled.div`
overflow: hidden;
grid-area: singer;
font-size: 12px;
text-overflow: ellipsis;
white-space: nowrap;
`;
81 changes: 81 additions & 0 deletions frontend/src/features/songs/components/SongItemList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { Link } from 'react-router-dom';
import styled from 'styled-components';
import SongItem from '@/features/songs/components/SongItem';
import Spacing from '@/shared/components/Spacing';
import ROUTE_PATH from '@/shared/constants/path';
import useFetch from '@/shared/hooks/useFetch';
import GENRES from '../constants/genres';
import { getHighLikedSongs } from '../remotes/song';
import type { Genre, Song } from '../types/Song.type';

interface SongItemListProps {
genre: Genre;
}

const SongItemList = ({ genre }: SongItemListProps) => {
const { data: songs } = useFetch<Song[]>(() => getHighLikedSongs(genre));

if (songs === null || songs?.length === 0) return;

return (
<>
<Title>{`${GENRES[genre]} Top 10`}</Title>
<Spacing direction="vertical" size={16} />
<SongList>
{songs?.map(({ id, albumCoverUrl, title, singer, totalLikeCount }, i) => (
<Li key={id}>
<StyledLink
to={`${ROUTE_PATH.SONG_DETAILS}/${id}/${genre}`}
aria-label={`${GENRES[genre]} 장르 ${i + 1}${singer} ${title}`}
>
<SongItem
rank={i + 1}
albumCoverUrl={albumCoverUrl}
title={title}
singer={singer}
totalLikeCount={totalLikeCount}
/>
</StyledLink>
</Li>
))}
</SongList>
<Spacing direction="vertical" size={30} />
</>
);
};

export default SongItemList;

const SongList = styled.ol`
scroll-snap-type: x mandatory;
overflow-x: scroll;
display: flex;
flex-direction: row;
gap: 12px;
align-items: flex-start;
width: 100%;
`;

const Li = styled.li`
scroll-snap-align: center;
scroll-snap-stop: normal;
max-width: 130px;
`;

const StyledLink = styled(Link)`
width: 100%;
&:hover,
&:focus {
background-color: ${({ theme }) => theme.color.secondary};
}
`;

const Title = styled.h2`
align-self: flex-start;
font-size: 18px;
font-weight: 700;
color: white;
`;
12 changes: 8 additions & 4 deletions frontend/src/features/songs/components/Thumbnail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,18 @@ const Thumbnail = ({ size = 'lg', ...props }: ThumbnailProps) => {
};

return (
<Wrapper size={size}>
<Wrapper $size={size}>
<img {...props} alt="노래 앨범" aria-hidden loading="lazy" onError={insertDefaultJacket} />
</Wrapper>
);
};

export default Thumbnail;

const Wrapper = styled.div<{ size: Size }>`
const Wrapper = styled.div<{ $size: Size }>`
overflow: hidden;
${({ size }) => SIZE_VARIANTS[size]};
border-radius: 8px;
${({ $size }) => SIZE_VARIANTS[$size]};
border-radius: 4px;
`;

const SIZE_VARIANTS = {
Expand All @@ -35,6 +35,10 @@ const SIZE_VARIANTS = {
width: 70px;
height: 70px;
`,
xl: css`
width: 130px;
height: 130px;
`,
} as const;

type Size = keyof typeof SIZE_VARIANTS;
20 changes: 20 additions & 0 deletions frontend/src/features/songs/constants/genres.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
const GENRES = {
ALL: '전체',
DANCE: '댄스',
HIPHOP: '힙합',
BALLAD: '발라드',
POP: '팝',
RHYTHM_AND_BLUES: 'R&B/Soul',
INDIE: '인디',
ROCK_METAL: '락/메탈',
TROT: '트로트',
FOLK_BLUES: '포크/블루스',
JAZZ: '재즈',
CLASSIC: '클래식',
J_POP: 'J-POP',
EDM: 'EDM',
ETC: '기타',
} as const;
` `;

export default GENRES;
7 changes: 7 additions & 0 deletions frontend/src/features/songs/remotes/song.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import fetcher from '@/shared/remotes';
import type { Genre, Song } from '../types/Song.type';
import type { SongDetail } from '@/shared/types/song';

export const getSongDetail = async (songId: number): Promise<SongDetail> => {
return await fetcher(`/songs/${songId}`, 'GET');
};

export const getHighLikedSongs = async (genre: Genre): Promise<Song[]> => {
const query = genre === 'ALL' ? '' : `?genre=${genre}`;

return await fetcher(`/songs/high-liked${query}`, 'GET');
};
25 changes: 19 additions & 6 deletions frontend/src/features/songs/remotes/songs.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,27 @@
import fetcher from '@/shared/remotes';
import type { Genre } from '../types/Song.type';
import type { SongDetail, SongDetailEntries } from '@/shared/types/song';

export const getSongDetailEntries = async (songId: number): Promise<SongDetailEntries> => {
return await fetcher(`/songs/${songId}`, 'GET');
export const getSongDetailEntries = async (
songId: number,
genre: Genre
): Promise<SongDetailEntries> => {
const query = genre === 'ALL' ? '' : `?genre=${genre}`;
return await fetcher(`/songs/high-liked/${songId}${query}`, 'GET');
};

export const getExtraPrevSongDetails = async (songId: number): Promise<SongDetail[]> => {
return await fetcher(`/songs/${songId}/prev`, 'GET');
export const getExtraPrevSongDetails = async (
songId: number,
genre: Genre
): Promise<SongDetail[]> => {
const query = genre === 'ALL' ? '' : `?genre=${genre}`;
return await fetcher(`/songs/high-liked/${songId}/prev${query}`, 'GET');
};

export const getExtraNextSongDetails = async (songId: number): Promise<SongDetail[]> => {
return await fetcher(`/songs/${songId}/next`, 'GET');
export const getExtraNextSongDetails = async (
songId: number,
genre: Genre
): Promise<SongDetail[]> => {
const query = genre === 'ALL' ? '' : `?genre=${genre}`;
return await fetcher(`/songs/high-liked/${songId}/next${query}`, 'GET');
};
Loading

0 comments on commit dbd4c8b

Please sign in to comment.