'use client'; import React, { useState, useEffect, useCallback, useRef, Suspense } from 'react'; import Image from 'next/image'; import { useSearchParams } from 'next/navigation'; import { swapWorshipVideos } from '@/lib/services'; import { useAuth } from '@/hooks'; import { extractYouTubeId, getYouTubeThumbnailUrl } from '@/lib/utils/youtube'; import { ArrowUp, ArrowDown } from 'lucide-react'; interface VideoItem { id: number; videoUrl: string; category: string; order: number; createdAt: string; } interface Category { id: string; title: string; videos: VideoItem[]; } function WorshipPageContent() { const searchParams = useSearchParams(); const categoryRefs = useRef<{ [key: string]: HTMLDivElement | null }>({}); const playerRef = useRef(null); const [categories, setCategories] = useState([ { id: 'sermon', title: '주일 설교', videos: [] }, { id: 'friday', title: '금요 성령집회', videos: [] }, ]); const [selectedVideo, setSelectedVideo] = useState<{ videoUrl: string; title: string }>({ videoUrl: 'https://www.youtube.com/watch?v=A8xPDnTkNzI', title: '', }); const [isAddModalOpen, setIsAddModalOpen] = useState(false); const [addingCategory, setAddingCategory] = useState(''); const [newVideoUrl, setNewVideoUrl] = useState(''); const [isLoading, setIsLoading] = useState(true); const { user } = useAuth(); useEffect(() => { loadVideos(); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const loadVideos = async () => { try { const response = await fetch('/api/worship'); if (!response.ok) throw new Error('Failed to fetch videos'); const result = await response.json(); const dbVideos: VideoItem[] = result.data || []; // 카테고리별로 그룹화 (order 필드로 내림차순 정렬 - 높은 order가 앞으로) const newCategories: Category[] = [ { id: 'sermon', title: '주일 설교', videos: dbVideos .filter(v => v.category === 'sermon') .sort((a, b) => b.order - a.order) }, { id: 'friday', title: '금요 성령집회', videos: dbVideos .filter(v => v.category === 'friday') .sort((a, b) => b.order - a.order) }, ]; setCategories(newCategories); // URL 쿼리 파라미터에서 category 확인 const categoryParam = searchParams?.get('category'); // category 파라미터가 있으면 해당 카테고리의 첫 번째 비디오 선택 if (categoryParam) { const targetCategory = newCategories.find(cat => cat.id === categoryParam); if (targetCategory && targetCategory.videos.length > 0) { setSelectedVideo({ videoUrl: targetCategory.videos[0].videoUrl, title: targetCategory.title, }); // 카테고리로 스크롤 (약간의 지연을 두어 DOM이 업데이트된 후 스크롤) setTimeout(() => { const targetElement = categoryRefs.current[categoryParam]; if (targetElement) { targetElement.scrollIntoView({ behavior: 'smooth', block: 'center' }); } }, 100); } else { // 해당 카테고리에 비디오가 없으면 첫 번째 비디오 선택 const firstCategoryWithVideo = newCategories.find(cat => cat.videos.length > 0); if (firstCategoryWithVideo && firstCategoryWithVideo.videos[0]) { setSelectedVideo({ videoUrl: firstCategoryWithVideo.videos[0].videoUrl, title: firstCategoryWithVideo.title, }); } } } else { // category 파라미터가 없으면 첫 번째 비디오 선택 const firstCategoryWithVideo = newCategories.find(cat => cat.videos.length > 0); if (firstCategoryWithVideo && firstCategoryWithVideo.videos[0]) { setSelectedVideo({ videoUrl: firstCategoryWithVideo.videos[0].videoUrl, title: firstCategoryWithVideo.title, }); } } setIsLoading(false); } catch (error) { console.error('Error loading videos:', error); setIsLoading(false); } }; const handleDelete = async (video: VideoItem, e: React.MouseEvent) => { e.stopPropagation(); if (!user) { alert('로그인이 필요합니다.'); return; } if (!confirm('정말 삭제하시겠습니까?')) return; try { const response = await fetch(`/api/worship?id=${video.id}`, { method: 'DELETE' }); if (!response.ok) throw new Error('Failed to delete video'); // 로컬 state 업데이트 setCategories(prev => prev.map(category => { if (category.id === video.category) { return { ...category, videos: category.videos.filter(v => v.id !== video.id) }; } return category; })); } catch (error) { console.error('Error deleting video:', error); alert('영상 삭제에 실패했습니다.'); } }; const handleAddVideo = (categoryId: string) => { if (!user) { alert('로그인이 필요합니다.'); return; } setAddingCategory(categoryId); setNewVideoUrl(''); setIsAddModalOpen(true); }; const handleSaveNewVideo = async () => { if (!addingCategory || !newVideoUrl) { alert('YouTube URL을 입력해주세요.'); return; } try { const response = await fetch('/api/worship', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ category: addingCategory, videoUrl: newVideoUrl }) }); if (!response.ok) { const error = await response.json(); throw new Error(error.error || error.message || 'Failed to add video'); } const result = await response.json(); const newVideo = result.data; // 로컬 state 업데이트 - 새 영상이 가장 앞으로 가도록 전체 목록을 다시 정렬 setCategories(prev => prev.map(category => { if (category.id === addingCategory) { const updatedVideos = [...category.videos, newVideo]; // order 기준 내림차순 정렬 (높은 order가 앞으로) return { ...category, videos: updatedVideos.sort((a, b) => b.order - a.order) }; } return category; })); setIsAddModalOpen(false); setAddingCategory(''); setNewVideoUrl(''); } catch (error) { console.error('Error adding video:', error); alert(error instanceof Error ? error.message : '영상 추가에 실패했습니다.'); } }; const moveVideo = async (categoryId: string, videoId: number, direction: 'up' | 'down') => { if (!user) { alert('로그인이 필요합니다.'); return; } const category = categories.find(cat => cat.id === categoryId); if (!category) return; const videoIndex = category.videos.findIndex(v => v.id === videoId); if (videoIndex === -1) return; // 이동할 새 인덱스 계산 const newIndex = direction === 'up' ? videoIndex - 1 : videoIndex + 1; // 범위 체크 if (newIndex < 0 || newIndex >= category.videos.length) return; // 교환할 두 비디오의 ID const video1Id = category.videos[videoIndex].id; const video2Id = category.videos[newIndex].id; // 낙관적 업데이트 (UI 즉시 반영) const newVideos = [...category.videos]; [newVideos[videoIndex], newVideos[newIndex]] = [newVideos[newIndex], newVideos[videoIndex]]; setCategories(prev => prev.map(cat => cat.id === categoryId ? { ...cat, videos: newVideos } : cat )); try { // 서버에 순서 변경 요청 (두 비디오만 교환) const updatedVideos = await swapWorshipVideos(video1Id, video2Id); // 서버 응답으로 상태 업데이트 (order 값이 정확히 반영됨) setCategories(prev => prev.map(cat => cat.id === categoryId ? { ...cat, videos: updatedVideos } : cat )); } catch (error) { console.error('Error swapping videos:', error); // 실패 시 원래 상태로 롤백 setCategories(prev => prev.map(cat => cat.id === categoryId ? { ...cat, videos: category.videos } : cat )); alert('영상 순서 변경에 실패했습니다.'); } }; if (isLoading) { return (
로딩 중...
); } // selectedVideo의 videoUrl에서 embed용 ID 추출 const embedVideoId = extractYouTubeId(selectedVideo.videoUrl); return (
{/* Main YouTube Player */}