import { useParams, Link } from 'react-router-dom'; import { useQuery, keepPreviousData } from '@tanstack/react-query'; import { useEffect, useState, useRef } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; import { Calendar, Clock, ChevronLeft, Check, Link2, MapPin, Navigation, ExternalLink } from 'lucide-react'; import { getSchedule, getXProfile } from '../../../api/public/schedules'; import '../../../mobile.css'; // 카카오맵 SDK 키 const KAKAO_MAP_KEY = import.meta.env.VITE_KAKAO_JS_KEY; // 카카오맵 컴포넌트 function KakaoMap({ lat, lng, name }) { const mapRef = useRef(null); const [mapLoaded, setMapLoaded] = useState(false); const [mapError, setMapError] = useState(false); useEffect(() => { if (!KAKAO_MAP_KEY) { setMapError(true); return; } if (!window.kakao?.maps) { const script = document.createElement('script'); script.src = `//dapi.kakao.com/v2/maps/sdk.js?appkey=${KAKAO_MAP_KEY}&autoload=false`; script.onload = () => { window.kakao.maps.load(() => setMapLoaded(true)); }; script.onerror = () => setMapError(true); document.head.appendChild(script); } else { setMapLoaded(true); } }, []); useEffect(() => { if (!mapLoaded || !mapRef.current || mapError) return; try { const position = new window.kakao.maps.LatLng(lat, lng); const map = new window.kakao.maps.Map(mapRef.current, { center: position, level: 3, }); const marker = new window.kakao.maps.Marker({ position, map, }); if (name) { const infowindow = new window.kakao.maps.InfoWindow({ content: `
${name}
`, }); infowindow.open(map, marker); } } catch (e) { setMapError(true); } }, [mapLoaded, lat, lng, name, mapError]); if (mapError) { return (

지도에서 보기

); } return (
); } // 전체화면 시 자동 가로 회전 훅 (숏츠가 아닐 때만) function useFullscreenOrientation(isShorts) { useEffect(() => { // 숏츠는 세로 유지 if (isShorts) return; const handleFullscreenChange = async () => { const isFullscreen = !!document.fullscreenElement; if (isFullscreen) { // 전체화면 진입 시 가로 모드로 전환 시도 try { if (screen.orientation && screen.orientation.lock) { await screen.orientation.lock('landscape'); } } catch (e) { // 지원하지 않는 브라우저이거나 권한이 없는 경우 무시 } } else { // 전체화면 종료 시 세로 모드로 복귀 try { if (screen.orientation && screen.orientation.unlock) { screen.orientation.unlock(); } } catch (e) { // 무시 } } }; document.addEventListener('fullscreenchange', handleFullscreenChange); document.addEventListener('webkitfullscreenchange', handleFullscreenChange); return () => { document.removeEventListener('fullscreenchange', handleFullscreenChange); document.removeEventListener('webkitfullscreenchange', handleFullscreenChange); }; }, [isShorts]); } // 카테고리 ID 상수 const CATEGORY_ID = { YOUTUBE: 2, X: 3, ALBUM: 4, FANSIGN: 5, CONCERT: 6, TICKET: 7, }; // HTML 엔티티 디코딩 함수 const decodeHtmlEntities = (text) => { if (!text) return ''; const textarea = document.createElement('textarea'); textarea.innerHTML = text; return textarea.value; }; // 유튜브 비디오 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; }; // 날짜 포맷팅 const formatFullDate = (dateStr) => { if (!dateStr) return ''; const date = new Date(dateStr); const dayNames = ['일', '월', '화', '수', '목', '금', '토']; return `${date.getFullYear()}. ${date.getMonth() + 1}. ${date.getDate()}. (${dayNames[date.getDay()]})`; }; // 시간 포맷팅 const formatTime = (timeStr) => { if (!timeStr) return null; 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); const isShorts = schedule.source?.url?.includes('/shorts/'); // 전체화면 시 가로 회전 (숏츠 제외) useFullscreenOrientation(isShorts); const members = schedule.members || []; const isFullGroup = members.length === 5; if (!videoId) return null; return (
{/* 영상 임베드 */}