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;
};
// 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) => {
if (!dateStr) return '';
@ -180,8 +167,8 @@ const extractXUsername = (url) => {
//
function YoutubeSection({ schedule }) {
const videoId = extractYoutubeVideoId(schedule.source?.url);
const isShorts = schedule.source?.url?.includes('/shorts/');
const videoId = schedule.videoId;
const isShorts = schedule.videoType === 'shorts';
// ( )
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 items-center gap-1">
<Calendar size={12} />
<span>{formatFullDate(schedule.date)}</span>
<span>{formatXDateTime(schedule.datetime)}</span>
</div>
{schedule.time && (
<div className="flex items-center gap-1">
<Clock size={12} />
<span>{formatTime(schedule.time)}</span>
</div>
)}
{schedule.source?.name && (
{schedule.channelName && (
<div className="flex items-center gap-1">
<Link2 size={12} />
<span>{schedule.source?.name}</span>
<span>{schedule.channelName}</span>
</div>
)}
</div>
@ -267,7 +248,7 @@ function YoutubeSection({ schedule }) {
{/* 유튜브에서 보기 버튼 */}
<a
href={schedule.source?.url}
href={schedule.videoUrl}
target="_blank"
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"

View file

@ -1,6 +1,7 @@
import { motion } from 'framer-motion';
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 }) {
@ -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 items-center gap-1.5 text-gray-500">
<Calendar size={14} />
<span>{formatFullDate(schedule.date)}</span>
<span>{formatXDateTime(schedule.datetime)}</span>
</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="flex items-center gap-2 text-gray-500">
<Link2 size={14} className="opacity-60" />
<span className="font-medium">{schedule.source.name}</span>
<span className="font-medium">{schedule.channelName}</span>
</div>
</>
)}
@ -68,7 +58,7 @@ function VideoInfo({ schedule, isShorts }) {
{/* 유튜브에서 보기 버튼 */}
<div className="mt-6 pt-5 border-t border-gray-200">
<a
href={schedule.source?.url}
href={schedule.videoUrl}
target="_blank"
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"
@ -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 }) {
const videoId = extractYoutubeVideoId(schedule.source?.url);
const isShorts = schedule.source?.url?.includes('/shorts/');
const videoId = schedule.videoId;
const isShorts = schedule.videoType === 'shorts';
if (!videoId) return null;