YouTube 상세 페이지 API 응답 구조 변경 대응

- schedule.source.url → schedule.videoUrl
- schedule.source.name → schedule.channelName
- schedule.date/time → schedule.datetime
- videoId를 URL에서 추출 대신 API에서 직접 사용
- formatXDateTime 유틸리티 사용

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
caadiq 2026-01-21 12:24:14 +09:00
parent beabcc094f
commit bdd5b90870
2 changed files with 15 additions and 56 deletions

View file

@ -144,19 +144,6 @@ const decodeHtmlEntities = (text) => {
return textarea.value; return textarea.value;
}; };
// ID (YoutubeSection)
const extractYoutubeVideoId = (url) => {
if (!url) return null;
const shortMatch = url.match(/youtu\.be\/([a-zA-Z0-9_-]{11})/);
if (shortMatch) return shortMatch[1];
const watchMatch = url.match(/youtube\.com\/watch\?v=([a-zA-Z0-9_-]{11})/);
if (watchMatch) return watchMatch[1];
const shortsMatch = url.match(/youtube\.com\/shorts\/([a-zA-Z0-9_-]{11})/);
if (shortsMatch) return shortsMatch[1];
return null;
};
// //
const formatFullDate = (dateStr) => { const formatFullDate = (dateStr) => {
if (!dateStr) return ''; if (!dateStr) return '';
@ -180,8 +167,8 @@ const extractXUsername = (url) => {
// //
function YoutubeSection({ schedule }) { function YoutubeSection({ schedule }) {
const videoId = extractYoutubeVideoId(schedule.source?.url); const videoId = schedule.videoId;
const isShorts = schedule.source?.url?.includes('/shorts/'); const isShorts = schedule.videoType === 'shorts';
// ( ) // ( )
useFullscreenOrientation(isShorts); useFullscreenOrientation(isShorts);
@ -229,18 +216,12 @@ function YoutubeSection({ schedule }) {
<div className="flex flex-wrap items-center gap-3 text-xs text-gray-500 mb-3"> <div className="flex flex-wrap items-center gap-3 text-xs text-gray-500 mb-3">
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
<Calendar size={12} /> <Calendar size={12} />
<span>{formatFullDate(schedule.date)}</span> <span>{formatXDateTime(schedule.datetime)}</span>
</div> </div>
{schedule.time && ( {schedule.channelName && (
<div className="flex items-center gap-1">
<Clock size={12} />
<span>{formatTime(schedule.time)}</span>
</div>
)}
{schedule.source?.name && (
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
<Link2 size={12} /> <Link2 size={12} />
<span>{schedule.source?.name}</span> <span>{schedule.channelName}</span>
</div> </div>
)} )}
</div> </div>
@ -267,7 +248,7 @@ function YoutubeSection({ schedule }) {
{/* 유튜브에서 보기 버튼 */} {/* 유튜브에서 보기 버튼 */}
<a <a
href={schedule.source?.url} href={schedule.videoUrl}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="flex items-center justify-center gap-2 w-full py-3 bg-red-500 active:bg-red-600 text-white rounded-xl font-medium transition-colors" className="flex items-center justify-center gap-2 w-full py-3 bg-red-500 active:bg-red-600 text-white rounded-xl font-medium transition-colors"

View file

@ -1,6 +1,7 @@
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import { Calendar, Clock, Link2 } from 'lucide-react'; import { Calendar, Clock, Link2 } from 'lucide-react';
import { decodeHtmlEntities, formatFullDate, formatTime } from './utils'; import { decodeHtmlEntities } from './utils';
import { formatXDateTime } from '../../../../utils/date';
// () // ()
function VideoInfo({ schedule, isShorts }) { function VideoInfo({ schedule, isShorts }) {
@ -16,30 +17,19 @@ function VideoInfo({ schedule, isShorts }) {
{/* 메타 정보 */} {/* 메타 정보 */}
<div className={`flex flex-wrap items-center gap-4 text-sm ${isShorts ? 'gap-3' : ''}`}> <div className={`flex flex-wrap items-center gap-4 text-sm ${isShorts ? 'gap-3' : ''}`}>
{/* 날짜 */} {/* 날짜/시간 */}
<div className="flex items-center gap-1.5 text-gray-500"> <div className="flex items-center gap-1.5 text-gray-500">
<Calendar size={14} /> <Calendar size={14} />
<span>{formatFullDate(schedule.date)}</span> <span>{formatXDateTime(schedule.datetime)}</span>
</div> </div>
{/* 시간 */}
{schedule.time && (
<>
<div className="w-px h-4 bg-gray-300" />
<div className="flex items-center gap-1.5 text-gray-500">
<Clock size={14} />
<span>{formatTime(schedule.time)}</span>
</div>
</>
)}
{/* 채널명 */} {/* 채널명 */}
{schedule.source?.name && ( {schedule.channelName && (
<> <>
<div className="w-px h-4 bg-gray-300" /> <div className="w-px h-4 bg-gray-300" />
<div className="flex items-center gap-2 text-gray-500"> <div className="flex items-center gap-2 text-gray-500">
<Link2 size={14} className="opacity-60" /> <Link2 size={14} className="opacity-60" />
<span className="font-medium">{schedule.source.name}</span> <span className="font-medium">{schedule.channelName}</span>
</div> </div>
</> </>
)} )}
@ -68,7 +58,7 @@ function VideoInfo({ schedule, isShorts }) {
{/* 유튜브에서 보기 버튼 */} {/* 유튜브에서 보기 버튼 */}
<div className="mt-6 pt-5 border-t border-gray-200"> <div className="mt-6 pt-5 border-t border-gray-200">
<a <a
href={schedule.source?.url} href={schedule.videoUrl}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="inline-flex items-center gap-2 px-5 py-2.5 bg-red-500 hover:bg-red-600 text-white rounded-xl font-medium transition-colors shadow-lg shadow-red-500/20" className="inline-flex items-center gap-2 px-5 py-2.5 bg-red-500 hover:bg-red-600 text-white rounded-xl font-medium transition-colors shadow-lg shadow-red-500/20"
@ -83,22 +73,10 @@ function VideoInfo({ schedule, isShorts }) {
); );
} }
// ID
const extractYoutubeVideoId = (url) => {
if (!url) return null;
const shortMatch = url.match(/youtu\.be\/([a-zA-Z0-9_-]{11})/);
if (shortMatch) return shortMatch[1];
const watchMatch = url.match(/youtube\.com\/watch\?v=([a-zA-Z0-9_-]{11})/);
if (watchMatch) return watchMatch[1];
const shortsMatch = url.match(/youtube\.com\/shorts\/([a-zA-Z0-9_-]{11})/);
if (shortsMatch) return shortsMatch[1];
return null;
};
// //
function YoutubeSection({ schedule }) { function YoutubeSection({ schedule }) {
const videoId = extractYoutubeVideoId(schedule.source?.url); const videoId = schedule.videoId;
const isShorts = schedule.source?.url?.includes('/shorts/'); const isShorts = schedule.videoType === 'shorts';
if (!videoId) return null; if (!videoId) return null;