diff --git a/frontend/src/pages/mobile/public/ScheduleDetail.jsx b/frontend/src/pages/mobile/public/ScheduleDetail.jsx index 1978462..49616fe 100644 --- a/frontend/src/pages/mobile/public/ScheduleDetail.jsx +++ b/frontend/src/pages/mobile/public/ScheduleDetail.jsx @@ -90,6 +90,33 @@ const formatTime = (timeStr) => { return timeStr.slice(0, 5); }; +// X URL에서 username 추출 +const extractXUsername = (url) => { + if (!url) return null; + const match = url.match(/(?:twitter\.com|x\.com)\/([^/]+)/); + return match ? match[1] : null; +}; + +// X용 날짜/시간 포맷팅 (오후 2:30 · 2026년 1월 15일) +const formatXDateTime = (dateStr, timeStr) => { + if (!dateStr) return ''; + const date = new Date(dateStr); + const year = date.getFullYear(); + const month = date.getMonth() + 1; + const day = date.getDate(); + + let result = `${year}년 ${month}월 ${day}일`; + + if (timeStr) { + const [hours, minutes] = timeStr.split(':').map(Number); + const period = hours < 12 ? '오전' : '오후'; + const hour12 = hours === 0 ? 12 : hours > 12 ? hours - 12 : hours; + result = `${period} ${hour12}:${String(minutes).padStart(2, '0')} · ${result}`; + } + + return result; +}; + // 유튜브 섹션 컴포넌트 function YoutubeSection({ schedule }) { const videoId = extractYoutubeVideoId(schedule.source_url); @@ -194,6 +221,104 @@ function YoutubeSection({ schedule }) { ); } +// X(트위터) 섹션 컴포넌트 +function XSection({ schedule }) { + const username = extractXUsername(schedule.source_url); + + // 프로필 정보 조회 + const { data: profile } = useQuery({ + queryKey: ['x-profile', username], + queryFn: () => getXProfile(username), + enabled: !!username, + staleTime: 1000 * 60 * 60, // 1시간 + }); + + const displayName = profile?.displayName || schedule.source_name || username || 'Unknown'; + const avatarUrl = profile?.avatarUrl; + + return ( + + {/* 헤더 */} + + + {/* 프로필 이미지 */} + {avatarUrl ? ( + + ) : ( + + + {displayName.charAt(0).toUpperCase()} + + + )} + + + + {displayName} + + + + + + {username && ( + @{username} + )} + + + + + {/* 본문 */} + + + {decodeHtmlEntities(schedule.description || schedule.title)} + + + + {/* 이미지 */} + {schedule.image_url && ( + + + + )} + + {/* 날짜/시간 */} + + + {formatXDateTime(schedule.date, schedule.time)} + + + + {/* X에서 보기 버튼 */} + + + + + + X에서 보기 + + + + ); +} + // 기본 섹션 컴포넌트 (다른 카테고리용 - 임시) function DefaultSection({ schedule }) { return ( @@ -341,6 +466,8 @@ function MobileScheduleDetail() { switch (schedule.category_id) { case CATEGORY_ID.YOUTUBE: return ; + case CATEGORY_ID.X: + return ; default: return ; }
+ {decodeHtmlEntities(schedule.description || schedule.title)} +