Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 17 additions & 11 deletions src/app/routes/routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,26 @@ import Loading from '@shared/ui/Loading'
import PrivateRoute from '@widgets/authGuard/PrivateRoute'

const MyPage = lazy(() => import('@/pages/myPage/ui'))
const HomePage = lazy(() => import('@/pages/homePage'))
const SearchPage = lazy(() => import('@/pages/searchPage'))
const SearchResultPage = lazy(() => import('@/pages/searchPage/SearchResultPage'))

export const AppRoutes = () => {
return (
<Routes>
<Route
path="/mypage/*"
element={
<PrivateRoute>
<Suspense fallback={<Loading isLoading width="100%" height="100%" />}>
<Suspense fallback={<Loading isLoading width="100%" height="100%" />}>
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/search" element={<SearchPage />} />
<Route path="/searchResult" element={<SearchResultPage />} />
<Route
path="/mypage/*"
element={
<PrivateRoute>
<MyPage />
</Suspense>
</PrivateRoute>
}
/>
</Routes>
</PrivateRoute>
}
/>
</Routes>
</Suspense>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,9 @@

import { Logo, Notification, Search } from '@/assets/icons'
import { TITLE_TEXT } from '@/pages/homePage/config/messages'
import LoopCarousel from '@/pages/homePage/ui/LoopCarousel'
import Header from '@/shared/ui/Header'
import ScrollCarousel from '@/shared/ui/ScrollCarousel'
import SvgButton from '@/shared/ui/SvgButton'
import Playlist from '@/widgets/playlist/Playlist'
import PlaylistWithSong from '@/widgets/playlist/PlaylistWithSong'
import { LoopCarousel } from '@/pages/homePage/ui'
import { Header, SvgButton, ScrollCarousel } from '@/shared/ui'
import { Playlist, PlaylistWithSong } from '@/widgets/playlist'

const HomePage = () => {
const navigate = useNavigate()
Expand Down Expand Up @@ -43,11 +40,11 @@
<ScrollCarousel gap={14}>
{secondSectionData.map((item, index) => (
<Playlist
key={index}

Check warning on line 43 in src/pages/homePage/index.tsx

View workflow job for this annotation

GitHub Actions / Build and Lint

Do not use Array index in keys
title={item.title}
username={item.username}
liked={item.liked}
onClick={() => console.log(item.title)}

Check warning on line 47 in src/pages/homePage/index.tsx

View workflow job for this annotation

GitHub Actions / Build and Lint

Unexpected console statement. Only these console methods are allowed: warn, error
/>
))}
</ScrollCarousel>
Expand All @@ -57,7 +54,7 @@
<ScrollCarousel gap={16}>
{thirdSectionData.map((playlist, idx) => (
<PlaylistWithSong
key={idx}

Check warning on line 57 in src/pages/homePage/index.tsx

View workflow job for this annotation

GitHub Actions / Build and Lint

Do not use Array index in keys
title={playlist.title}
username={playlist.username}
songs={playlist.songs}
Expand Down
2 changes: 2 additions & 0 deletions src/pages/homePage/ui/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { DotButton } from './DotButton'
export { default as LoopCarousel } from './LoopCarousel'
112 changes: 112 additions & 0 deletions src/pages/searchPage/SearchResultPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { useEffect, useState } from 'react'
import { useLocation, useNavigate } from 'react-router-dom'

import styled from 'styled-components'

import { LeftArrow, Search } from '@/assets/icons'
import { SearchResultItem } from '@/pages/searchPage/ui'
import { useSingleSelect } from '@/shared/hooks/useSingleSelect'
import { ContentHeader, Header, Input, SvgButton } from '@/shared/ui'
import type { SortType } from '@/shared/ui/ContentHeader'

const SearchResultPage = () => {
const navigate = useNavigate()
const location = useLocation()

const queryParams = new URLSearchParams(location.search)
const keyword = queryParams.get('keyword') ?? ''

const [searchValue, setSearchValue] = useState(keyword)
const { selected, onSelect } = useSingleSelect<SortType>('popular')

useEffect(() => {
setSearchValue(keyword)
}, [keyword])

const handleItemClick = (id: number) => {
navigate(`/discover/${id}`)
}
return (
<>
<Header
left={<SvgButton icon={LeftArrow} onClick={() => navigate(-1)} />}
center={<span>검색</span>}
/>
<Input
type="search"
placeholder="플레이리스트명 또는 닉네임으로 검색"
value={searchValue}
onChange={(e) => setSearchValue(e.target.value)}
icon={Search}
iconPosition="left"
onKeyDown={(e) => {
if (e.key === 'Enter') {
navigate(`/searchResult?keyword=${encodeURIComponent(searchValue)}`)
}
}}
/>
<Result>
<ContentHeader
totalCount={searchResultMockData.length}
currentSort={selected}
onSortChange={onSelect}
/>
<ResultList>
{searchResultMockData.map((item) => (
<SearchResultItem
key={item.id}
type={item.type as 'playlist' | 'user'}
searchResult={item.searchResult}
imageUrl={item.imageUrl}
userName={item.type === 'playlist' ? item.userName : null}
onClick={() => handleItemClick(item.id)}
/>
))}
</ResultList>
</Result>
</>
)
}

export default SearchResultPage

const Result = styled.section`
display: flex;
flex-direction: column;
gap: 16px;
padding-top: 24px;
`

const ResultList = styled.div`
display: flex;
flex-direction: column;
gap: 20px;
`

export const searchResultMockData = [

Check warning on line 86 in src/pages/searchPage/SearchResultPage.tsx

View workflow job for this annotation

GitHub Actions / Build and Lint

Fast refresh only works when a file only exports components. Use a new file to share constants or functions between components
{
id: 1,
type: 'playlist',
searchResult: '도파민이 필요할 땐 이 노동요를 들어주세요 😎',
userName: 'deulak',
},
{
id: 2,
type: 'playlist',
searchResult: '카페 재즈 모음',
userName: 'jazzlover',
},
{
id: 3,
type: 'user',
imageUrl: 'image/url/expample.png',
searchResult: '김들락',
},

{
id: 5,
type: 'playlist',
searchResult: '새벽감성 인디 플레이리스트',
userName: 'deulak',
},
]
113 changes: 113 additions & 0 deletions src/pages/searchPage/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { useState } from 'react'
import { useNavigate } from 'react-router-dom'

import styled, { css } from 'styled-components'

import { LeftArrow, Search } from '@/assets/icons'
import { TrendKeyword } from '@/pages/searchPage/ui'
import { CategoryButton, Header, Input, SvgButton } from '@/shared/ui'

const SearchPage = () => {
const [searchValue, setSearchValue] = useState('')
const navigate = useNavigate()

return (
<>
<Header
left={<SvgButton icon={LeftArrow} onClick={() => navigate(-1)} />}
center={<span>검색</span>}
/>
<Input
type="search"
placeholder="플레이리스트명 또는 닉네임으로 검색"
value={searchValue}
onChange={(e) => setSearchValue(e.target.value)}
icon={Search}
iconPosition="left"
onKeyDown={(e) => {
if (e.key === 'Enter') {
navigate(`/searchResult?keyword=${encodeURIComponent(searchValue)}`)
}
}}
/>
<TrendKeywordsSection>
<h1>인기 검색어</h1>
<Keywords>
{trendData.map((item) => (
<TrendKeyword key={item.id} text={item.keyword} />
))}
</Keywords>
</TrendKeywordsSection>
<MoodSection>
<h1>장르와 테마</h1>
<Category>
{cateData.map((item) => (
<CategoryButton key={item.id} text={item.text} size="small" />
))}
</Category>
</MoodSection>
</>
)
}

export default SearchPage

const sectionCommonLayout = css`
display: flex;
flex-direction: column;
gap: 16px;

& h1 {
${({ theme }) => theme.FONT.headline1};
font-weight: 600;
}
`

const TrendKeywordsSection = styled.section`
${sectionCommonLayout}
padding: 40px 0 32px 0;
`
const Keywords = styled.div`
display: flex;
flex-wrap: wrap;
gap: 8px;
`

const MoodSection = styled.section`
${sectionCommonLayout}
padding: 24px 0 48px 0;
`

const Category = styled.div`
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 12px;
`

// TODO: api 연동 후 실 데이터로 수정
const trendData = [
{ id: 1, keyword: '여름' },
{ id: 2, keyword: '바캉스 플리' },
{ id: 3, keyword: '카페 재즈 플레이리스트' },
{ id: 4, keyword: '청량' },
{ id: 5, keyword: '감성 힙합' },
{ id: 6, keyword: '쇠맛 여자아이돌 모음' },
{ id: 7, keyword: '드라이브' },
{ id: 8, keyword: '인디밴드음악' },
{ id: 9, keyword: 'K-POP' },
]

const cateData = [
{ id: 1, text: '발라드' },
{ id: 2, text: '힙합' },
{ id: 3, text: 'R&B' },
{ id: 4, text: '인디' },
{ id: 5, text: '락' },
{ id: 6, text: '댄스' },
{ id: 7, text: 'K-POP' },
{ id: 8, text: 'POP' },
{ id: 9, text: '재즈' },
{ id: 10, text: '클래식' },
{ id: 11, text: 'ASMR' },
{ id: 12, text: '기타' },
]
20 changes: 15 additions & 5 deletions src/pages/searchPage/ui/SearchResultItem.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,31 @@
import styled from 'styled-components'

import { flexRowCenter } from '@/shared/styles/mixins'
import { Cd, Profile } from '@/shared/ui'

interface SearchResultItemProps {
image: React.ReactNode
imageUrl?: string
type: 'playlist' | 'user'
searchResult: string
userName?: string
userName?: string | null
onClick: () => void
}

const SearchResultItem = ({ image, searchResult, userName, onClick }: SearchResultItemProps) => {
const SearchResultItem = ({
imageUrl,
type,
searchResult,
userName,
onClick,
}: SearchResultItemProps) => {
return (
<ItemContainer onClick={onClick}>
<Left>{image}</Left>
<Left>
{type === 'playlist' ? <Cd variant="xs" /> : <Profile size="M" profileUrl={imageUrl} />}
</Left>
<Right>
<SearchResult>{searchResult}</SearchResult>
<SmallText>{userName}</SmallText>
{userName && <SmallText>{userName}</SmallText>}
</Right>
</ItemContainer>
)
Expand Down
25 changes: 25 additions & 0 deletions src/pages/searchPage/ui/TrendKeyword.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import styled from 'styled-components'

interface TrendKeywordProps {
text: string
}

const TrendKeyword = ({ text }: TrendKeywordProps) => {
return <StyledButton>{text}</StyledButton>
}

export default TrendKeyword

const StyledButton = styled.button`
width: fit-content;
background-color: ${({ theme }) => theme.COLOR['gray-700']};
color: ${({ theme }) => theme.COLOR['gray-50']};
padding: 6px 12px;
border-radius: 99px;
${({ theme }) => theme.FONT['body2-normal']};

&:active {
background-color: ${({ theme }) => theme.COLOR['primary-normal']};
color: ${({ theme }) => theme.COLOR['gray-900']};
}
`
2 changes: 2 additions & 0 deletions src/pages/searchPage/ui/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { default as SearchResultItem } from './SearchResultItem'
export { default as TrendKeyword } from './TrendKeyword'
10 changes: 5 additions & 5 deletions src/shared/config/navItems.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ import { Home, Look, Music, Person } from '@/assets/icons'
interface NavItem {
icon: React.ComponentType<React.SVGProps<SVGSVGElement>>
title: string
path: string
paths: string[] // 여러 경로 허용
}

export const NAV_ITEMS: NavItem[] = [
{ icon: Home, title: '홈', path: '/' },
{ icon: Look, title: '둘러보기', path: '/discover' },
{ icon: Music, title: '나의 CD', path: '/mycd' },
{ icon: Person, title: '마이페이지', path: '/mypage' },
{ icon: Home, title: '홈', paths: ['/', '/search', '/searchResult'] },
{ icon: Look, title: '둘러보기', paths: ['/discover'] },
{ icon: Music, title: '나의 CD', paths: ['/mycd'] },
{ icon: Person, title: '마이페이지', paths: ['/mypage'] },
]
27 changes: 27 additions & 0 deletions src/shared/ui/CategoryButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import styled, { css } from 'styled-components'

interface CategoryButtonProps {
text: string
size: 'small' | 'large'
}

const CategoryButton = ({ text, size }: CategoryButtonProps) => {
return <StyledButton size={size}>{text}</StyledButton>
}

export default CategoryButton

const sizes = {
small: { width: '100%', height: '80px', borderRadius: '8px', padding: '10px' },
large: { width: '160px', height: '200px', borderRadius: '10px', padding: '12px' },
}

const StyledButton = styled.button<{ size: 'small' | 'large' }>`
display: flex;
align-items: flex-start;
justify-content: flex-start;
background-color: ${({ theme }) => theme.COLOR['gray-700']};
color: ${({ theme }) => theme.COLOR['gray-10']};

${({ size }) => css(sizes[size])}
`
Loading
Loading