diff --git a/src/frontend/src/components/MyRoomCard/index.css.ts b/src/frontend/src/components/MyRoomCard/index.css.ts index 4cfa3cf3..2e0862bd 100644 --- a/src/frontend/src/components/MyRoomCard/index.css.ts +++ b/src/frontend/src/components/MyRoomCard/index.css.ts @@ -10,7 +10,7 @@ export const Card = styled.div` `; export const Thumbnail = styled.div` - width: 124px; + width: 120px; flex-shrink: 0; aspect-ratio: 16 / 9; border-radius: 0.625rem; diff --git a/src/frontend/src/components/MyRoomCard/index.tsx b/src/frontend/src/components/MyRoomCard/index.tsx index b64fe349..6ba3293c 100644 --- a/src/frontend/src/components/MyRoomCard/index.tsx +++ b/src/frontend/src/components/MyRoomCard/index.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import UserIcon from '@/assets/img/UsersLine.svg'; import TrashcanIcon from '@/assets/img/Trashcan.svg'; import ShareIcon from '@/assets/img/ShareLink.svg'; @@ -17,12 +17,25 @@ import { Title, UserCount, } from './index.css'; +import { getYoutubeThumbnail } from '@/utils/youtubeUtils'; export const MyRoomCard = ({ room }: { room: MyRoomDto }) => { const [isHovered, setIsHovered] = useState(false); const [isClickDelete, setIsClickDelete] = useState(false); + const [thumbnail, setThumbnail] = useState(DefaultThumbnail); const navigate = useNavigate(); + useEffect(() => { + if (!room.playlistUrl) return; + + const fetchThumbnail = async () => { + const url = await getYoutubeThumbnail(room.playlistUrl ?? '', 'default'); + setThumbnail(url ?? DefaultThumbnail); + }; + + fetchThumbnail(); + }, [room.playlistUrl]); + const handleClick = (e: React.MouseEvent) => { e.stopPropagation(); navigate(`/room?code=${room.code}`); @@ -61,7 +74,7 @@ export const MyRoomCard = ({ room }: { room: MyRoomDto }) => { > { e.currentTarget.src = DefaultThumbnail; }} diff --git a/src/frontend/src/components/Sidebar/Playlist/PlaylistItem/index.css.ts b/src/frontend/src/components/Sidebar/Playlist/PlaylistItem/index.css.ts index 36ddecf0..c98764af 100644 --- a/src/frontend/src/components/Sidebar/Playlist/PlaylistItem/index.css.ts +++ b/src/frontend/src/components/Sidebar/Playlist/PlaylistItem/index.css.ts @@ -14,7 +14,7 @@ export const Container = styled.div` border-radius: 5px; background-color: ${({ $isPreview }) => $isPreview ? 'var(--palette-line-normal-normal)' : 'var(--palette-static-white)'}; - border: 1px solid #ddd + border: 1px solid #ddd; cursor: pointer; position: relative; transition: all 0.2s ease; @@ -38,7 +38,7 @@ export const Container = styled.div` transition: transform 0.2s ease; } - &:hover .button-container { + &:hover .button-container { opacity: ${({ $isDragging }) => ($isDragging ? 0 : 1)}; visibility: ${({ $isDragging }) => ($isDragging ? 'hidden' : 'visible')}; } diff --git a/src/frontend/src/components/VideoCard/index.css.ts b/src/frontend/src/components/VideoCard/index.css.ts index d65b0c9f..9df9edb5 100644 --- a/src/frontend/src/components/VideoCard/index.css.ts +++ b/src/frontend/src/components/VideoCard/index.css.ts @@ -1,3 +1,4 @@ +import { getYoutubeThumbnail } from '@/utils/youtubeUtils'; import { styled } from 'styled-components'; export const VideoCardContainer = styled.div` @@ -14,7 +15,7 @@ export const VideoCardContainer = styled.div` } `; -export const Thumbnail = styled.div` +export const Thumbnail = styled.div<{ $playlistUrl: string | undefined }>` width: 100%; aspect-ratio: 16 / 9; border-radius: 0.625rem; @@ -25,6 +26,25 @@ export const Thumbnail = styled.div` width: 100%; height: 100%; object-fit: cover; + content: url(${props => getYoutubeThumbnail(props.$playlistUrl, 'maxres')}); + } + + @media (max-width: 639px) { + & > img { + content: url(${props => getYoutubeThumbnail(props.$playlistUrl, 'sd')}); + } + } + + @media (max-width: 479px) { + & > img { + content: url(${props => getYoutubeThumbnail(props.$playlistUrl, 'hq')}); + } + } + + @media (max-width: 319px) { + & > img { + content: url(${props => getYoutubeThumbnail(props.$playlistUrl, 'mq')}); + } } `; diff --git a/src/frontend/src/components/VideoCard/index.tsx b/src/frontend/src/components/VideoCard/index.tsx index 37ae6a62..8df0711f 100644 --- a/src/frontend/src/components/VideoCard/index.tsx +++ b/src/frontend/src/components/VideoCard/index.tsx @@ -10,18 +10,33 @@ import { import { RoomDto } from '@/api/endpoints/room/room.interface'; import DefaultThumbnail from '@/assets/img/DefaultThumbnail.svg'; import DefaultProfile from '@/assets/img/DefaultProfile.svg'; +import { getYoutubeThumbnail } from '@/utils/youtubeUtils'; +import { useEffect, useState } from 'react'; interface IVideoCard { video: RoomDto; onClick?: () => void; } export const VideoCard = ({ video, onClick }: IVideoCard) => { + const [thumbnail, setThumbnail] = useState(DefaultThumbnail); + + useEffect(() => { + if (!video.playlistUrl) return; + + const fetchThumbnail = async () => { + const url = await getYoutubeThumbnail(video.playlistUrl ?? '', 'hq'); + setThumbnail(url ?? DefaultThumbnail); + }; + + fetchThumbnail(); + }, [video.playlistUrl]); + return ( - + {video.userCount}명 { e.currentTarget.src = DefaultThumbnail; }} diff --git a/src/frontend/src/utils/youtubeUtils.ts b/src/frontend/src/utils/youtubeUtils.ts new file mode 100644 index 00000000..d79566ec --- /dev/null +++ b/src/frontend/src/utils/youtubeUtils.ts @@ -0,0 +1,29 @@ +export const getYoutubeVideoInfo = async (videoId: string) => { + const response = await fetch( + `https://www.googleapis.com/youtube/v3/videos?part=snippet&id=${videoId}&key=${import.meta.env.VITE_YOUTUBE_API_KEY}`, + ); + const data = await response.json(); + return data; +}; + +// 유튜브 썸네일 이미지 가져오기 +// maxres: 1280p, sd: 640p, hq: 480p, mq: 320p, default: 120p +export const getYoutubeThumbnail = ( + url: string | undefined, + quality: 'default' | 'mq' | 'hq' | 'sd' | 'maxres', +): string | undefined => { + if (!url) return undefined; + + const videoId = url.split('v=')[1]; + if (!videoId) return url; + + const qualityMap = { + maxres: 'maxresdefault', + sd: 'sddefault', + hq: 'hqdefault', + mq: 'mqdefault', + default: 'default', + }; + + return `https://img.youtube.com/vi/${videoId}/${qualityMap[quality]}.jpg`; +};