Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
a61f31a
fix: 검색 시 입력 텍스트 잘려보이는 문제 해결 (#129)
maylh Oct 27, 2025
da07045
fix: ios svg 깨져보이는 이슈 해결 (#129)
maylh Oct 28, 2025
54bdffe
fix: 검색창 placeholder 수정 (#129)
maylh Oct 28, 2025
2bdefd8
fix: 프로 그레스 바 컨트롤러 사이즈 수정 및 드래그 추가 (#129)
maylh Oct 28, 2025
0bacf06
fix: 캐러셀 슬라이드 전환 시 오버레이가 정상적으로 동작하지 않는 문제 수정 (#129)
maylh Oct 28, 2025
a53475a
fix: 채팅 삭제 시 채팅 메시지 중복 표시 문제 수정 (#129)
maylh Oct 28, 2025
cd852d4
fix: Loading 컴포넌트 적용 (#129)
maylh Oct 28, 2025
fb34f48
feat: 좋아요 탭일 때 크리에이터 이름 표시 (#129)
maylh Oct 28, 2025
ad74e37
fix: 나의 CD 모바일 레이아웃 수정사항 반영 (#129)
maylh Oct 29, 2025
5f31959
fix: 둘러보기 모바일 레이아웃 수정 (#129)
maylh Oct 29, 2025
2cb1aef
fix: 둘러보기 CD 타입 수정 (#129)
maylh Oct 29, 2025
976fb63
fix: 모바일 볼륨 버튼 렌더링 조건 수정 (#129)
maylh Oct 29, 2025
d61a0b9
feat: 플레이 버튼 fade animation 추가 (#129)
maylh Oct 30, 2025
104203d
fix: 프로그레스 바 핸들 portal로 변경 (#129)
maylh Oct 30, 2025
03d622c
fix: 버튼 텍스트 삭제 (#129)
maylh Oct 30, 2025
8dd3391
fix: isEmpty 탭 분기처리 (#129)
maylh Oct 30, 2025
e8d5aaf
fix: 탭별로 상세정보 훅 호출 분리 (#129)
maylh Oct 30, 2025
d277911
fix: 모든 cd 썸네일 보이도록 수정 (#129)
maylh Oct 30, 2025
4e13e85
refactor: 상태 변수와 setter 이름 불일치 수정 (#129)
maylh Oct 30, 2025
ede62df
fix: sentAt required 필드로 변경 (#129)
maylh Oct 30, 2025
d0f99b6
fix: 의존성 배열 수정 (#129)
maylh Oct 30, 2025
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
9 changes: 6 additions & 3 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,21 @@
import { GlobalErrorModal } from '@/shared/ui'
import NavBar, { NAV_HEIGHT } from '@/widgets/layout/NavBar'

const LAYOUT_BOTTOM_GAP = 34

const App = () => {
const deviceType = useDevice()
const location = useLocation()
const isMobile = deviceType === 'mobile'

const { isLogin } = useAuthStore()
const { mutate } = useAnonymousLogin()

const [isNavVisible, setIsNavVisible] = useState(true)

const LAYOUT_WIDTH = deviceType === 'mobile' ? 'clamp(320px, 100dvw, 430px)' : '430px'
const LAYOUT_WIDTH = isMobile ? 'clamp(320px, 100dvw, 430px)' : '430px'

const isMobileView =
isMobile && (location.pathname.startsWith('/mycd') || location.pathname.startsWith('/discover'))
const LAYOUT_BOTTOM_GAP = isMobileView ? 16 : 34

// 비회원일 경우 API 호출을 위한 익명 토큰 발급
// TODO: 토큰 만료됐을 경우 응답 체크해서 해당 값일 경우 토큰 재발급
Expand Down Expand Up @@ -91,7 +94,7 @@

// 네비게이션 미노출 여부 확인
setIsNavVisible(!getHideNav(pathname))
}, [location.pathname])

Check warning on line 97 in src/App.tsx

View workflow job for this annotation

GitHub Actions / Build and Lint

React Hook useEffect has missing dependencies: 'checkAnonymousLogin' and 'location'. Either include them or remove the dependency array

return (
<RootWrapper>
Expand Down
3 changes: 1 addition & 2 deletions src/entities/playlist/types/playlist.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,7 @@ export interface PlaylistInfo extends PlaylistDetail {
totalTime?: string
creator: Creator

// TODO : 타입 맞춰달라고 수정 요청 해야 함
onlyCdResponse?: {
cdResponse?: {
cdItems: CdCustomData[]
}
cdItems?: CdCustomData[]
Expand Down
2 changes: 1 addition & 1 deletion src/features/chat/model/sendMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
messageId: string
username: string | null
content: string
sentAt?: string
sentAt: string
profileImage: string | null
systemMessage?: boolean
roomId: string
Expand All @@ -26,7 +26,7 @@

const client = new Client({
webSocketFactory: () => new SockJS('https://api.deulak.com/chat/ws') as unknown as WebSocket,
debug: (str) => console.log('[STOMP]', str),

Check warning on line 29 in src/features/chat/model/sendMessage.ts

View workflow job for this annotation

GitHub Actions / Build and Lint

Unexpected console statement. Only these console methods are allowed: warn, error
})

client.onConnect = () => {
Expand Down
3 changes: 1 addition & 2 deletions src/features/share/ui/ShareButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ const ShareButton = ({ playlistId, stickers, type = 'DISCOVER' }: ShareButtonPro
const shareRefs = useRef<(HTMLDivElement | null)[]>([])

const slides = [
{ id: 'cd', content: <Cd variant="share" bgColor="none" stickers={stickers} /> },
{ id: 'cd', content: <Cd variant="customize" bgColor="none" stickers={stickers} /> },
{
id: 'member',
content: <img src={MemberCharacter} alt="Member Character" width={220} height={220} />,
Expand Down Expand Up @@ -59,7 +59,6 @@ const ShareButton = ({ playlistId, stickers, type = 'DISCOVER' }: ShareButtonPro
<>
<ButtonWrapper $isMy={type === 'MY'} onClick={handleShare}>
<SvgButton icon={Share} width={type === 'MY' ? 16 : 24} height={type === 'MY' ? 16 : 24} />
{type === 'MY' && <p>공유</p>}
</ButtonWrapper>

<BottomSheet
Expand Down
4 changes: 2 additions & 2 deletions src/features/swipe/ui/SwipeCarousel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ const SwipeCarousel = ({
</div>
) : (
<EmblaViewport ref={emblaRef}>
<VerticaContainer>{children}</VerticaContainer>
<VerticalContainer>{children}</VerticalContainer>
</EmblaViewport>
)}
</>
Expand All @@ -80,7 +80,7 @@ const EmblaViewport = styled.div`
overflow: hidden;
`

const VerticaContainer = styled.div`
const VerticalContainer = styled.div`
display: flex;
flex-direction: column;
height: 100%;
Expand Down
11 changes: 7 additions & 4 deletions src/pages/discover/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
} = useShufflePlaylists()

// shuffleData에서 실제 playlist 배열만 추출
const shufflePlaylists = shuffleData?.pages.flatMap((page) => page.content) ?? []

Check warning on line 63 in src/pages/discover/index.tsx

View workflow job for this annotation

GitHub Actions / Build and Lint

The 'shufflePlaylists' logical expression could make the dependencies of useMemo Hook (at line 97) change on every render. Move it inside the useMemo callback. Alternatively, wrap the initialization of 'shufflePlaylists' in its own useMemo() Hook

// PlaylistDetailResponse → PlaylistInfo 변환
const playlistAsInfo = useMemo(() => {
Expand Down Expand Up @@ -164,7 +164,7 @@
currentPlaylist={currentPlaylist}
currentTrackIndex={currentTrackIndex}
currentTime={currentTime}
isPlaying={isPlaying}
isPlaying={isPlaying && !showCoachmark}
onPlayPause={() => (isPlaying ? pause() : play())}
onNext={nextTrack}
onPrev={prevTrack}
Expand All @@ -179,14 +179,16 @@
))}
</SwipeCarousel>

{videoId && (
{!showCoachmark && videoId && (
<YoutubePlayer
videoId={videoId}
onReady={(event) => {
playerRef.current = event.target
playerRef.current?.seekTo(currentTime, true)
if (isPlaying) playerRef.current?.playVideo()
else playerRef.current?.pauseVideo()
if (!isPlaying) {
playerRef.current?.pauseVideo()
}

setIsMuted(playerRef.current?.isMuted() ?? null)
}}
onStateChange={handlePlayerStateChange}
Expand All @@ -200,6 +202,7 @@

const Slide = styled.div`
flex: 0 0 100%;
position: relative;
`
const Page = styled.div`
position: relative;
Expand Down
28 changes: 6 additions & 22 deletions src/pages/home/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
useRecommendedGenres,
} from '@/features/recommend'
import { FirstSection } from '@/pages/home/ui'
import { ScrollCarousel } from '@/shared/ui'
import { CategoryButton, ScrollCarousel } from '@/shared/ui'
import { Playlist, PlaylistWithSong } from '@/widgets/playlist'

const HomePage = () => {
Expand Down Expand Up @@ -67,13 +67,13 @@ const HomePage = () => {
<h1>오늘은 이런 기분</h1>
<ScrollCarousel gap={12}>
{GenreData?.map((item) => (
<Slide
<CategoryButton
key={item.code}
$bgImage={CARD_IMAGES_LARGE[item.code as keyof typeof CARD_IMAGES_LARGE]}
size="large"
text={item.displayName}
bgImage={CARD_IMAGES_LARGE[item.code as keyof typeof CARD_IMAGES_LARGE]}
onClick={() => handleKeywordSearch(item.code)}
>
{item.displayName}
</Slide>
/>
))}
</ScrollCarousel>
</FourthSection>
Expand All @@ -83,22 +83,6 @@ const HomePage = () => {

export default HomePage

const Slide = styled.button<{ $bgImage?: string }>`
border-radius: 10px;
width: 160px;
height: 200px;
color: ${({ theme }) => theme.COLOR['gray-100']};
${({ theme }) => theme.FONT['body1-normal']};
background-color: ${({ theme }) => theme.COLOR['gray-600']};
background-image: url(${({ $bgImage }) => $bgImage});
background-size: cover;
background-position: center;
padding: 12px;
display: flex;
flex-direction: column;
align-items: flex-start;
`

const PageLayout = styled.div`
display: flex;
flex-direction: column;
Expand Down
98 changes: 72 additions & 26 deletions src/pages/mycd/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import { useLocation, useNavigate, useParams } from 'react-router-dom'
import styled from 'styled-components'

import { usePlaylist } from '@/app/providers/PlayerProvider'
import { NoLike } from '@/assets/icons'
import { MemberCharacter } from '@/assets/images'
import { usePlaylistDetail } from '@/entities/playlist'
import { useMyCdActions, useMyCdList, useMyLikedCdList } from '@/entities/playlist/model/useMyCd'
import { useAuthStore } from '@/features/auth/store/authStore'
import { useChatSocket } from '@/features/chat/model/sendMessage'
Expand All @@ -13,7 +15,7 @@ import type { MyCdTab } from '@/pages/mycd/ui/HeaderTab'
import { getVideoId } from '@/shared/lib'
import { useDevice } from '@/shared/lib/useDevice'
import { flexColCenter, flexRowCenter } from '@/shared/styles/mixins'
import { Button, LiveInfo } from '@/shared/ui'
import { Button, LiveInfo, Loading } from '@/shared/ui'
import { ActionBar, ControlBar, ProgressBar, VolumeButton, YoutubePlayer } from '@/widgets/playlist'

const MyCdPage = () => {
Expand Down Expand Up @@ -45,10 +47,11 @@ const MyCdPage = () => {
const likedCdPlaylist = useMyLikedCdList('RECENT')

const playlistQuery = selectedTab === 'MY' ? myCdPlaylist : likedCdPlaylist

const playlistData = useMemo(() => playlistQuery.data ?? [], [playlistQuery.data])

const isLoading = playlistQuery.isLoading
const isError = playlistQuery.isError
const isMyEmpty = !myCdPlaylist.isLoading && (myCdPlaylist.data?.length ?? 0) === 0
const isLikedEmpty = !likedCdPlaylist.isLoading && (likedCdPlaylist.data?.length ?? 0) === 0

const [centerItem, setCenterItem] = useState<{
playlistId: number | null
Expand Down Expand Up @@ -112,9 +115,15 @@ const MyCdPage = () => {
)

/* 플레이리스트 세팅 */
const { tracklist: playlistDetail } = useMyCdActions(Number(centerItem.playlistId), {
enabled: !!centerItem.playlistId,
const myCdDetail = useMyCdActions(Number(centerItem.playlistId), {
enabled: selectedTab === 'MY' && !!centerItem.playlistId,
})

const likedCdDetail = usePlaylistDetail(centerItem.playlistId, {
enabled: selectedTab === 'LIKE' && !!centerItem.playlistId,
})

const playlistDetail = selectedTab === 'MY' ? myCdDetail.tracklist : likedCdDetail.data
useEffect(() => {
if (playlistDetail && userInfo) {
if (currentPlaylist?.playlistId === playlistDetail.playlistId) return
Expand Down Expand Up @@ -162,21 +171,41 @@ const MyCdPage = () => {
? getVideoId(currentPlaylist.songs[currentTrackIndex]?.youtubeUrl)
: null

if (isLoading) {
return <Loading isLoading />
}

if (isError) {
navigate('/error')
return null
}

const isEmpty = playlistData && playlistData.length === 0
if (selectedTab === 'MY' && isMyEmpty) {
return (
<EmptyPage>
<HeaderTab selectedTab={selectedTab} onSelect={handleTabSelect} />
<CenterContent>
<img src={MemberCharacter} alt="Guest Character" width={160} height={160} />
<NavigateBtn onClick={() => navigate('/mypage/customize')}>
새로운 CD에 취향 담기
</NavigateBtn>
</CenterContent>
</EmptyPage>
)
}

if (isEmpty) {
if (selectedTab === 'LIKE' && isLikedEmpty) {
return (
<ViewContainer>
<img src={MemberCharacter} alt="Guest Character" width={160} height={160} />
<NavigateBtn onClick={() => navigate('/mypage/customize')}>
새로운 CD에 취향 담기
</NavigateBtn>
</ViewContainer>
<EmptyPage>
<HeaderTab selectedTab={selectedTab} onSelect={handleTabSelect} />
<CenterContent>
<NoLike width={160} height={160} />
<SubText>
아직 좋아요한 CD가 없어요. <br /> 새로운 음악을 찾아볼까요?
</SubText>
<NavigateBtn onClick={() => navigate('/discover')}>둘러보기로 가기</NavigateBtn>
</CenterContent>
</EmptyPage>
)
}

Expand Down Expand Up @@ -204,18 +233,18 @@ const MyCdPage = () => {
</Button>
)}
</Container>

<PlaylistCarousel data={playlistData ?? []} onCenterChange={handleCenterChange} />

<ActionBar
playlistId={centerItem.playlistId ?? 0}
creatorId={currentPlaylist.creator.creatorId}
stickers={playlistDetail?.cdResponse?.cdItems || []}
type="MY"
selectedTab={selectedTab}
/>

<Title>{centerItem.playlistName}</Title>
<Title $isMobile={isMobile}> {centerItem.playlistName}</Title>
{selectedTab === 'LIKE' && playlistDetail?.creatorNickname && (
<Creator>{playlistDetail.creatorNickname}</Creator>
)}

<BottomWrapper>
<ProgressBar
Expand Down Expand Up @@ -259,24 +288,18 @@ const Container = styled.div`
height: 30px;
`

const Title = styled.p`
const Title = styled.p<{ $isMobile?: boolean }>`
${({ theme }) => theme.FONT.headline1};
padding-top: 40px;
padding-top: ${({ $isMobile }) => ($isMobile ? '24px' : '40px')};
`

const BottomWrapper = styled.div`
padding-top: 24px;
padding-top: 20px;
display: flex;
flex-direction: column;
gap: 20px;
`

const ViewContainer = styled.div`
${flexColCenter}
height: 100%;
gap: 16px;
`

const NavigateBtn = styled.button`
${flexRowCenter}
background-color: ${({ theme }) => theme.COLOR['primary-normal']};
Expand All @@ -285,4 +308,27 @@ const NavigateBtn = styled.button`
padding: 6px 20px;
height: 32px;
${({ theme }) => theme.FONT['body2-normal']};
margin-top: 16px;
`
const Creator = styled.p`
${({ theme }) => theme.FONT['body2-normal']};
color: ${({ theme }) => theme.COLOR['gray-300']};
`

const SubText = styled.p`
${({ theme }) => theme.FONT['body1-normal']}
color: ${({ theme }) => theme.COLOR['gray-50']};
`

const EmptyPage = styled.div`
display: flex;
flex-direction: column;
height: 100%;
background-color: ${({ theme }) => theme.COLOR['gray-900']};
`

const CenterContent = styled.div`
flex: 1;
${flexColCenter};
text-align: center;
`
Loading
Loading