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 (