diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index d27d291..936f66a 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -8,6 +8,7 @@ import WorldsPage from './pages/WorldsPage'; import PlayersPage from './pages/PlayersPage'; import PlayerStatsPage from './pages/PlayerStatsPage'; import WorldMapPage from './pages/WorldMapPage'; +import Modpack from './pages/Modpack'; import LoginPage from './pages/LoginPage'; import RegisterPage from './pages/RegisterPage'; import VerifyEmailPage from './pages/VerifyEmailPage'; @@ -95,6 +96,7 @@ function App() { } /> } /> } /> + } /> } /> } /> diff --git a/frontend/src/components/Sidebar.jsx b/frontend/src/components/Sidebar.jsx index 1ffa0f6..ea42f84 100644 --- a/frontend/src/components/Sidebar.jsx +++ b/frontend/src/components/Sidebar.jsx @@ -1,6 +1,6 @@ import React, { useState, useRef, useEffect } from 'react'; import { NavLink, useLocation, Link, useNavigate } from 'react-router-dom'; -import { Home, Globe, Users, Menu, X, Gamepad2, Map, Shield, LogIn, LogOut, User, Settings, BarChart2 } from 'lucide-react'; +import { Home, Globe, Users, Menu, X, Gamepad2, Map, Shield, LogIn, LogOut, User, Settings, BarChart2, Package } from 'lucide-react'; import { motion, AnimatePresence } from 'framer-motion'; import { useAuth } from '../contexts/AuthContext'; import { io } from 'socket.io-client'; @@ -23,6 +23,7 @@ const Sidebar = ({ isMobile = false }) => { { path: '/players', icon: Users, label: '플레이어', matchPaths: ['/players', '/player/'] }, { path: '/worlds', icon: Globe, label: '월드 정보' }, { path: '/worldmap', icon: Map, label: '월드맵' }, + { path: '/modpack', icon: Package, label: '모드팩' }, ]; // 커스텀 활성 상태 확인 함수 diff --git a/frontend/src/pages/Admin.jsx b/frontend/src/pages/Admin.jsx index 457f9be..ed16c66 100644 --- a/frontend/src/pages/Admin.jsx +++ b/frontend/src/pages/Admin.jsx @@ -9,7 +9,7 @@ import { useAuth } from '../contexts/AuthContext'; import { Shield, ArrowLeft, ArrowDown, Loader2, Terminal, Users, Settings, Send, Ban, UserX, Crown, Sun, Moon, Cloud, CloudRain, CloudLightning, - ChevronDown, FileText, Download, Trash2, Check, RefreshCw, Eye, X + ChevronDown, FileText, Download, Trash2, Check, RefreshCw, Eye, X, Package, Upload, Plus, Pencil } from 'lucide-react'; import { motion, AnimatePresence } from 'framer-motion'; import { io } from 'socket.io-client'; @@ -60,7 +60,7 @@ export default function Admin({ isMobile = false }) { // 탭 상태 (URL 해시에서 초기값 로드) const getInitialTab = () => { const hash = window.location.hash.replace('#', ''); - return ['console', 'players', 'settings'].includes(hash) ? hash : 'console'; + return ['console', 'players', 'modpack', 'settings'].includes(hash) ? hash : 'console'; }; const [activeTab, setActiveTab] = useState(getInitialTab); @@ -122,6 +122,17 @@ export default function Admin({ isMobile = false }) { memory: { used: 0, max: 0 }, }); + // 모드팩 관리 상태 + 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' }, + ]); + // 권한 확인 useEffect(() => { if (!loading) { @@ -793,11 +804,12 @@ export default function Admin({ isMobile = false }) { const tabs = [ { id: 'console', label: '콘솔', icon: Terminal }, { id: 'players', label: '플레이어', icon: Users }, + { id: 'modpack', label: '모드팩', icon: Package }, { id: 'settings', label: '설정', icon: Settings }, ]; return ( -
+
{/* 토스트 */} {toast && ( @@ -835,23 +847,25 @@ export default function Admin({ isMobile = false }) {
)} - {/* 탭 네비게이션 */} -
- {tabs.map(tab => ( - - ))} -
+ {/* 탭 네비게이션 - 데스크톱 */} + {!isMobile && ( +
+ {tabs.map(tab => ( + + ))} +
+ )} {/* 탭 콘텐츠 */} @@ -1549,9 +1563,152 @@ export default function Admin({ isMobile = false }) {
)} + + {/* 모드팩 탭 */} + {activeTab === 'modpack' && ( + +
+
+

📦 모드팩 관리

+ +
+ + {/* 모드팩 목록 */} +
+ {modpacks.map((pack, i) => ( + isMobile ? ( + /* 모바일 레이아웃 - 세로 카드 */ +
+
+
+ {i === 0 && 최신} + v{pack.version} +
+
+ + +
+
+

{pack.name}

+

{pack.date} · {pack.size}

+
+ ) : ( + /* 데스크톱 레이아웃 - 가로 */ +
+
+
+ +
+
+
+ {pack.name} + v{pack.version} + {i === 0 && 최신} +
+ {pack.date} · {pack.size} +
+
+
+ + +
+
+ ) + ))} +
+ + {/* 빈 상태 */} + {modpacks.length === 0 && ( +
+ +

등록된 모드팩이 없습니다

+
+ )} +
+
+ )} + {/* 모바일 바텀 네비게이션 */} + {isMobile && ( +
+
+ {tabs.map(tab => ( + + ))} +
+
+ )} + {/* 플레이어 액션 다이얼로그 */} {showPlayerDialog && selectedPlayer && ( @@ -1774,6 +1931,87 @@ export default function Admin({ isMobile = false }) { )} + + {/* 모드팩 업로드/수정 다이얼로그 */} + + {showModpackDialog && ( + setShowModpackDialog(false)} + > + e.stopPropagation()} + > +

+ {modpackDialogMode === 'upload' ? '📦 모드팩 업로드' : '✏️ 모드팩 수정'} +

+ + {/* 파일 선택 (업로드 모드에서만) */} + {modpackDialogMode === 'upload' && ( +
+ +
+ +

클릭하여 파일 선택

+

또는 파일을 여기에 드래그

+
+
+ )} + + {/* 수정 모드에서 파일명 표시 */} + {modpackDialogMode === 'edit' && editingModpack && ( +
+
+ +
+

{editingModpack.name} v{editingModpack.version}

+

{editingModpack.size}

+
+
+
+ )} + + {/* 변경 로그 */} +
+ +