-
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.
* refactor: 댓글 응답 값에 날짜 추가 * feat: 앨범 상세 페이지 구현 * build: fast average color 라이브러리 추가 * feat: 앨범 상세 페이지 배경 설정 * feat: 노래 제목 옆 숫자 출력 및 코멘트 익명 출력 * refactor: 프록시 설정 제거 * refactor: 배경 그라데이션 위치 수정 및 부모 영역 패딩 추가 * feat: 댓글 작성 기능 구현 * style: 콘텐츠 영역이 화면 밖으로 나가지 않도록 스타일 개선 * refactor: comment 데이터 서버 응답 시 최신순으로 정렬하여 반환 * refactor: comment 데이터 서버 응답 시 최신 10개만 반환 하도록 수정 * refactor: 댓글 저장 시 제한된 글자 수 이내로 작성했는지 검증 * feat: 앨범명 및 부가 정보 추가 * fix: 재생 시간 계산 문제 해결 * refactor: 아티스트 정보 출력 길이 수정 및 컴포넌트 분리 * style: 댓글 텍스트 수정 * style: 제목 및 아티스트명 출력 영역 확장 * style: 플레이리스트 출력 영역 확장 * refactor: 불필요한 then 메소드 사용 제거 * refactor: useEffect에서 albumId가 수정될 때 useEffect가 호출되도록 수정 * refactor: 이미지 alt 추가 * refactor: 정적 px 값 tailwind 방식으로 수정 * refactor: 스크롤 바 색 수정 * refactor: 불필요한 export 제거
- Loading branch information
Showing
17 changed files
with
375 additions
and
68 deletions.
There are no files selected for viewing
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
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,3 +1,105 @@ | ||
import { AlbumArtist, CommentList, Playlist } from '@/widgets/albums'; | ||
import { publicAPI } from '@/shared/api/publicAPI.ts'; | ||
import { useParams } from 'react-router-dom'; | ||
import { useEffect, useState } from 'react'; | ||
import { FastAverageColor } from 'fast-average-color'; | ||
import { darken } from 'polished'; | ||
|
||
interface AlbumDetails { | ||
albumId: string; | ||
albumName: string; | ||
artist: string; | ||
jacketUrl: string; | ||
} | ||
|
||
export function AlbumPage() { | ||
return <div>AlbumPage</div>; | ||
const { albumId } = useParams<{ albumId: string }>(); | ||
if (!albumId) return; | ||
|
||
const [songDetails, setSongDetails] = useState< | ||
{ name: string; duration: string }[] | ||
>([]); | ||
const [albumJacketUrl, setAlbumJacketUrl] = useState<string>('LogoAlbum'); | ||
const [albumDetails, setAlbumDetails] = useState<AlbumDetails>({}); | ||
|
||
useEffect(() => { | ||
(async () => { | ||
const albumResponse = await publicAPI | ||
.getAlbumInfo(albumId) | ||
.catch((err) => console.log(err)); | ||
|
||
setAlbumDetails(albumResponse.result.albumDetails); | ||
setSongDetails(albumResponse.result.songDetails); | ||
setAlbumJacketUrl(albumResponse.result.albumDetails.jacketUrl); | ||
})(); | ||
}, [albumJacketUrl, albumId]); | ||
|
||
const [backgroundColor, setBackgroundColor] = useState<string>('#222'); | ||
|
||
useEffect(() => { | ||
const fac = new FastAverageColor(); | ||
const img = new Image(); | ||
img.crossOrigin = 'anonymous'; | ||
img.src = albumJacketUrl; | ||
|
||
img.onload = () => { | ||
try { | ||
if (img.width === 0 || img.height === 0) { | ||
console.error('Image has no dimensions'); | ||
return; | ||
} | ||
|
||
const color = fac.getColor(img, { | ||
algorithm: 'dominant', // 주요 색상 추출 | ||
mode: 'precision', // 더 정확한 색상 계산 | ||
}); | ||
|
||
setBackgroundColor(darken(0.4, color.hex)); | ||
} catch (e) { | ||
console.error('Color extraction failed:', e); | ||
} | ||
}; | ||
|
||
return () => { | ||
img.onload = null; // 클린업 | ||
}; | ||
}, [albumJacketUrl]); // albumJacketUrl이 변경될 때마다 실행 | ||
|
||
const totalDuration = songDetails.reduce( | ||
(total, acc) => total + Number(acc.songDuration), | ||
0, | ||
); | ||
|
||
return ( | ||
<div | ||
className={'px-80 pt-16 flex flex-col w-full'} | ||
style={{ | ||
background: `linear-gradient(180deg, ${backgroundColor} 0%, rgba(0, 0, 0, 0) 20%)`, | ||
}} | ||
> | ||
<div className={'flex h-680 gap-20 mb-24 relative z-10'}> | ||
<article className={'w-[21.25rem] h-85 flex-shrink-0'}> | ||
<img | ||
id={'album-jacket'} | ||
src={albumJacketUrl} | ||
className={'w-[21.25rem] h-[21.25rem] select-none'} | ||
alt={`${albumDetails.albumName} 앨범 커버`} | ||
></img> | ||
<p | ||
className={`${albumDetails.albumName?.length >= 12 ? 'text-2xl' : albumDetails.albumName?.length >= 10 ? 'text-3xl' : 'text-4xl'} text-grayscale-50 mt-8 truncate`} | ||
style={{ fontWeight: 900 }} | ||
> | ||
{albumDetails.albumName} | ||
</p> | ||
<AlbumArtist | ||
artist={albumDetails.artist} | ||
songLength={songDetails.length} | ||
totalDuration={totalDuration} | ||
/> | ||
</article> | ||
<Playlist playlist={songDetails} /> | ||
</div> | ||
<CommentList albumId={albumId} /> | ||
</div> | ||
); | ||
} |
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
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 +1,4 @@ | ||
export { AlbumList } from './ui/AlbumList'; | ||
export { CommentList } from './ui/CommentList.tsx'; | ||
export { Playlist } from './ui/Playlist'; | ||
export { AlbumArtist } from './ui/AlbumArtist'; |
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,39 @@ | ||
interface AlbumArtistProps { | ||
artist: string; | ||
songLength: number; | ||
totalDuration: number; | ||
} | ||
|
||
export function AlbumArtist({ | ||
artist, | ||
songLength, | ||
totalDuration, | ||
}: AlbumArtistProps) { | ||
const hour = Math.floor(Number(totalDuration) / 3600); | ||
const minute = Math.floor((Number(totalDuration) % 3600) / 60); | ||
const second = Math.floor(Number(totalDuration) % 60); | ||
|
||
return ( | ||
<section | ||
className={ | ||
'text-lg text-grayscale-400 mt-4 flex justify-start overflow-visible whitespace-nowrap absolute max-w-[calc(100vw-340px)]' | ||
} | ||
> | ||
<span className={'truncate'}>{artist}</span> | ||
<p className={'flex-shrink-0 flex-grow-0 whitespace-nowrap'}> | ||
<span className={'mx-2'}>•</span> | ||
<span>{songLength}곡</span> | ||
</p> | ||
<p className={'flex-shrink-0 flex-grow-0 whitespace-nowrap'}> | ||
<span className={'mx-2'}>•</span> | ||
<span> | ||
{(hour > 0 ? String(hour).padStart(2, '0') + '시간 ' : '') + | ||
String(minute).padStart(2, '0') + | ||
'분 ' + | ||
String(second).padStart(2, '0') + | ||
'초'} | ||
</span> | ||
</p> | ||
</section> | ||
); | ||
} |
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 |
---|---|---|
@@ -0,0 +1,16 @@ | ||
interface CommentProps { | ||
comment: { content: string; createdAt: Date }; | ||
index: number; | ||
} | ||
|
||
export function Comment({ comment, index }: CommentProps) { | ||
const date: Date = new Date(comment.createdAt); | ||
const dateFormat = `${date.getFullYear()}. ${date.getMonth() + 1}. ${date.getDate()}.`; | ||
return ( | ||
<article className={'w-full flex justify-between mb-4 overflow-hidden'}> | ||
<p className={'w-[80px] mr-[24px] flex-shrink-0'}>댓글 #{index + 1}</p> | ||
<p className={'word-break break-all flex-grow'}>{comment.content}</p> | ||
<p className={'w-[90px] ml-[24px] flex-shrink-0'}>{dateFormat}</p> | ||
</article> | ||
); | ||
} |
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,61 @@ | ||
import { Comment } from './Comment.tsx'; | ||
import { publicAPI } from '@/shared/api/publicAPI.ts'; | ||
import { Button } from '@/shared/ui'; | ||
import { useEffect, useState } from 'react'; | ||
|
||
interface CommentListProps { | ||
albumId: string; | ||
} | ||
|
||
export function CommentList({ albumId }: CommentListProps) { | ||
const [commentList, setCommentList] = useState<{ albumName: string }[]>([]); | ||
|
||
useEffect(() => { | ||
(async () => { | ||
const commentResponse = await publicAPI | ||
.getComment(albumId) | ||
.catch((err) => console.log(err)); | ||
|
||
setCommentList(commentResponse.result.albumComments); | ||
})(); | ||
}, [commentList]); | ||
|
||
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => { | ||
e.preventDefault(); | ||
|
||
const dom = e.currentTarget.previousElementSibling; | ||
const response = publicAPI.createComment(albumId, dom.value); | ||
|
||
response.then((res) => { | ||
alert('댓글이 등록되었습니다.'); | ||
setCommentList([dom.value, ...commentList]); | ||
dom.parentElement.reset(); | ||
}); | ||
}; | ||
|
||
return ( | ||
<div className="w-full text-grayscale-50 border-t border-grayscale-700 border-solid"> | ||
<p className={'text-4xl font-bold mb-8 mt-14'}> | ||
코멘트 | ||
<span className={'ml-6 text-sm font-normal text-grayscale-400'}> | ||
최신 10개의 댓글만 조회합니다. | ||
</span> | ||
</p> | ||
<form className={'flex justify-between items-baseline mb-12'}> | ||
<p className={'w-20 mr-6'}>댓글 작성</p> | ||
<input | ||
name={'content'} | ||
className={ | ||
'bg-transparent border-b border-solid h-10 py-2 focus:outline-none flex-grow mr-16' | ||
} | ||
placeholder={'여기에 댓글을 입력해주세요.'} | ||
maxLength={200} | ||
/> | ||
<Button type={'submit'} message={'등록'} onClick={handleSubmit} /> | ||
</form> | ||
{commentList.map((comment, index) => ( | ||
<Comment key={index} comment={comment} index={index} /> | ||
))} | ||
</div> | ||
); | ||
} |
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,18 @@ | ||
import { TrackItem } from './TrackItem'; | ||
import './Scrollbar.css'; | ||
|
||
export interface PlaylistComponentProps { | ||
playlist: { songName: string; songDuration: string }[]; | ||
} | ||
|
||
export function Playlist({ playlist }: PlaylistComponentProps) { | ||
return ( | ||
<article className={'w-full overflow-y-scroll h-96 pr-4'}> | ||
{playlist.map( | ||
(item: { songName: string; songDuration: string }, index) => ( | ||
<TrackItem trackData={item} index={index} key={index} /> | ||
), | ||
)} | ||
</article> | ||
); | ||
} |
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,3 @@ | ||
::-webkit-scrollbar-thumb { | ||
background: #fafafa !important; | ||
} |
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,30 @@ | ||
interface TrackItemProps { | ||
trackData: { songName: string; songDuration: string }; | ||
index: number; | ||
} | ||
|
||
export function TrackItem({ trackData, index }: TrackItemProps) { | ||
const hour = Math.floor(Number(trackData.songDuration) / 3600); | ||
const minute = Math.floor((Number(trackData.songDuration) % 3600) / 60); | ||
const second = Math.floor(Number(trackData.songDuration) % 60); | ||
|
||
return ( | ||
<section | ||
className={ | ||
'flex w-full h-[30px] text-grayscale-50 justify-between mb-[24px]' | ||
} | ||
> | ||
<section className={'flex'}> | ||
<section className={'mr-8'}>{index + 1}.</section> | ||
<section>{trackData.songName}</section> | ||
</section> | ||
|
||
<section className={'text-grayscale-200 text-sm'}> | ||
{(hour > 0 ? String(hour).padStart(2, '0') + ':' : '') + | ||
String(minute).padStart(2, '0') + | ||
':' + | ||
String(second).padStart(2, '0')} | ||
</section> | ||
</section> | ||
); | ||
} |
Oops, something went wrong.