diff --git a/frontend/src/pages/Admin.jsx b/frontend/src/pages/Admin.jsx index a07b480..02b0637 100644 --- a/frontend/src/pages/Admin.jsx +++ b/frontend/src/pages/Admin.jsx @@ -126,13 +126,11 @@ export default function Admin({ isMobile = false }) { const [showModpackDialog, setShowModpackDialog] = useState(false); const [modpackDialogMode, setModpackDialogMode] = useState('upload'); // 'upload' | 'edit' const [editingModpack, setEditingModpack] = useState(null); - const [modpackForm, setModpackForm] = useState({ version: '', changelog: '' }); - const [modpacks, setModpacks] = useState([ - { id: 1, version: '1.2.0', name: '테스트 서버 모드팩', date: '2024-12-20', size: '15.0 MB' }, - { id: 2, version: '1.1.0', name: '테스트 서버 모드팩', date: '2024-12-15', size: '12.0 MB' }, - { id: 3, version: '1.0.0', name: '테스트 서버 모드팩', date: '2024-12-01', size: '8.0 MB' }, - ]); + const [modpackForm, setModpackForm] = useState({ changelog: '' }); + const [modpackFile, setModpackFile] = useState(null); // 업로드할 파일 + const [modpacks, setModpacks] = useState([]); const [modpackDeleteTarget, setModpackDeleteTarget] = useState(null); // 삭제 확인 다이얼로그용 + const [modpackLoading, setModpackLoading] = useState(false); // 업로드/삭제 로딩 // 권한 확인 useEffect(() => { @@ -154,6 +152,121 @@ export default function Admin({ isMobile = false }) { } }, [toast]); + // 모드팩 목록 fetch + const fetchModpacks = useCallback(async () => { + try { + const res = await fetch('/api/modpacks'); + const data = await res.json(); + // API 응답을 UI 형식에 맞게 변환 + const formatted = data.map(mp => ({ + id: mp.id, + version: mp.version, + name: mp.name, + date: new Date(mp.created_at).toISOString().split('T')[0], + size: (mp.file_size / (1024 * 1024)).toFixed(1) + ' MB', + })); + setModpacks(formatted); + } catch (error) { + console.error('모드팩 목록 로드 실패:', error); + } + }, []); + + // 모드팩 탭 활성화 시 목록 로드 + useEffect(() => { + if (activeTab === 'modpack') { + fetchModpacks(); + } + }, [activeTab, fetchModpacks]); + + // 모드팩 업로드 + const handleModpackUpload = async () => { + if (!modpackFile) { + setToast('.mrpack 파일을 선택해주세요.'); + return; + } + setModpackLoading(true); + try { + const token = localStorage.getItem('token'); + const formData = new FormData(); + formData.append('file', modpackFile); + formData.append('changelog', modpackForm.changelog); + + const res = await fetch('/api/admin/modpacks', { + method: 'POST', + headers: { 'Authorization': `Bearer ${token}` }, + body: formData, + }); + const result = await res.json(); + if (result.success) { + setToast(`${result.name} v${result.version} 업로드 완료!`); + setShowModpackDialog(false); + setModpackFile(null); + setModpackForm({ changelog: '' }); + fetchModpacks(); + } else { + setToast(result.error || '업로드 실패'); + } + } catch (error) { + setToast('업로드 실패: ' + error.message); + } finally { + setModpackLoading(false); + } + }; + + // 모드팩 수정 (변경 로그) + const handleModpackEdit = async () => { + if (!editingModpack) return; + setModpackLoading(true); + try { + const token = localStorage.getItem('token'); + const res = await fetch(`/api/admin/modpacks/${editingModpack.id}`, { + method: 'PUT', + headers: { + 'Authorization': `Bearer ${token}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ changelog: modpackForm.changelog }), + }); + const result = await res.json(); + if (result.success) { + setToast('변경 로그가 수정되었습니다.'); + setShowModpackDialog(false); + fetchModpacks(); + } else { + setToast(result.error || '수정 실패'); + } + } catch (error) { + setToast('수정 실패: ' + error.message); + } finally { + setModpackLoading(false); + } + }; + + // 모드팩 삭제 + const handleModpackDelete = async () => { + if (!modpackDeleteTarget) return; + setModpackLoading(true); + try { + const token = localStorage.getItem('token'); + const res = await fetch(`/api/admin/modpacks/${modpackDeleteTarget.id}`, { + method: 'DELETE', + headers: { 'Authorization': `Bearer ${token}` }, + }); + const result = await res.json(); + if (result.success) { + setToast('모드팩이 삭제되었습니다.'); + setModpackDeleteTarget(null); + fetchModpacks(); + } else { + setToast(result.error || '삭제 실패'); + } + } catch (error) { + setToast('삭제 실패: ' + error.message); + } finally { + setModpackLoading(false); + } + }; + // 플레이어 목록 fetch (안정적인 참조) const fetchPlayers = useCallback(async () => { try { @@ -1948,11 +2061,23 @@ export default function Admin({ isMobile = false }) { {modpackDialogMode === 'upload' && (
-
+
+ {modpackFile ? ( +

{modpackFile.name}

+ ) : ( + <> +

클릭하여 파일 선택

+

또는 파일을 여기에 드래그

+ + )} +
)} @@ -1984,19 +2109,21 @@ export default function Admin({ isMobile = false }) { {/* 버튼 */}
@@ -2038,14 +2165,11 @@ export default function Admin({ isMobile = false }) { 취소 diff --git a/frontend/src/pages/Modpack.jsx b/frontend/src/pages/Modpack.jsx index ac25267..c66583a 100644 --- a/frontend/src/pages/Modpack.jsx +++ b/frontend/src/pages/Modpack.jsx @@ -5,86 +5,6 @@ import React, { useState } from 'react'; import { Download, Package, ChevronDown, ChevronUp, Calendar, HardDrive, Gamepad2, Box, Image } from 'lucide-react'; import { motion, AnimatePresence } from 'framer-motion'; -// 더미 데이터 -const DUMMY_MODPACKS = [ - { - id: 1, - version: '1.2.0', - name: '테스트 서버 모드팩', - minecraftVersion: '1.21.1', - modLoader: 'NeoForge', - modLoaderVersion: '21.1.213', - changelog: `### 새로운 기능 -- Create 모드 추가 -- JEI (Just Enough Items) 추가 -- Jade 모드 추가 - -### 버그 수정 -- 일부 텍스처 누락 문제 해결`, - fileSize: 15728640, // 15MB - downloadCount: 42, - createdAt: '2024-12-20T10:30:00Z', - contents: { - mods: [ - { name: 'Create', version: '0.5.1' }, - { name: 'Just Enough Items', version: '15.2.0' }, - { name: 'Jade', version: '11.7.1' }, - { name: 'JourneyMap', version: '5.9.18' }, - { name: 'Sodium', version: '0.5.8' }, - ], - resourcepacks: [ - { name: 'Faithful 32x', version: '1.21' }, - ], - shaderpacks: [ - { name: 'Complementary Shaders', version: '5.2.1' }, - { name: 'BSL Shaders', version: '8.2.09' }, - ], - }, - }, - { - id: 2, - version: '1.1.0', - name: '테스트 서버 모드팩', - minecraftVersion: '1.21.1', - modLoader: 'NeoForge', - modLoaderVersion: '21.1.200', - changelog: `### 변경사항 -- JourneyMap 추가 -- Sodium 성능 모드 추가`, - fileSize: 12582912, // 12MB - downloadCount: 128, - createdAt: '2024-12-15T14:00:00Z', - contents: { - mods: [ - { name: 'Just Enough Items', version: '15.1.0' }, - { name: 'JourneyMap', version: '5.9.17' }, - { name: 'Sodium', version: '0.5.7' }, - ], - resourcepacks: [], - shaderpacks: [], - }, - }, - { - id: 3, - version: '1.0.0', - name: '테스트 서버 모드팩', - minecraftVersion: '1.21.1', - modLoader: 'NeoForge', - modLoaderVersion: '21.1.180', - changelog: `### 최초 릴리즈 -- 기본 모드 구성`, - fileSize: 8388608, // 8MB - downloadCount: 256, - createdAt: '2024-12-01T09:00:00Z', - contents: { - mods: [ - { name: 'Just Enough Items', version: '15.0.0' }, - ], - resourcepacks: [], - shaderpacks: [], - }, - }, -]; // 파일 크기 포맷 const formatFileSize = (bytes) => { @@ -136,10 +56,13 @@ const ModpackCard = ({ modpack, isLatest }) => { {/* 다운로드 버튼 */} - + {/* 메타 정보 */} @@ -300,7 +223,44 @@ const ModpackCard = ({ modpack, isLatest }) => { }; export default function Modpack() { - const modpacks = DUMMY_MODPACKS; + const [modpacks, setModpacks] = useState([]); + const [loading, setLoading] = useState(true); + + // API에서 모드팩 목록 가져오기 + React.useEffect(() => { + const fetchModpacks = async () => { + try { + const res = await fetch('/api/modpacks'); + const data = await res.json(); + // API 응답을 UI 형식에 맞게 변환 + const formatted = data.map(mp => ({ + id: mp.id, + version: mp.version, + name: mp.name, + minecraftVersion: mp.minecraft_version, + modLoader: mp.mod_loader, + changelog: mp.changelog || '', + fileSize: mp.file_size, + createdAt: mp.created_at, + contents: mp.contents || { mods: [], resourcepacks: [], shaderpacks: [] }, + })); + setModpacks(formatted); + } catch (error) { + console.error('모드팩 목록 로드 실패:', error); + } finally { + setLoading(false); + } + }; + fetchModpacks(); + }, []); + + if (loading) { + return ( +
+
로딩 중...
+
+ ); + } return (