2025-12-22 09:36:23 +09:00
|
|
|
/**
|
|
|
|
|
* 관리자 페이지
|
|
|
|
|
*/
|
|
|
|
|
|
2025-12-22 11:42:37 +09:00
|
|
|
import { useEffect, useState } from 'react';
|
|
|
|
|
import { useNavigate, useLocation } from 'react-router-dom';
|
2025-12-22 09:36:23 +09:00
|
|
|
import { useAuth } from '../contexts/AuthContext';
|
|
|
|
|
import { Shield, LogOut, Settings, Server, Users, Loader2 } from 'lucide-react';
|
2025-12-22 11:42:37 +09:00
|
|
|
import { motion, AnimatePresence } from 'framer-motion';
|
2025-12-22 09:36:23 +09:00
|
|
|
|
|
|
|
|
export default function Admin() {
|
|
|
|
|
const { isLoggedIn, isAdmin, user, loading, logout } = useAuth();
|
|
|
|
|
const navigate = useNavigate();
|
2025-12-22 11:42:37 +09:00
|
|
|
const location = useLocation();
|
|
|
|
|
const [toast, setToast] = useState(null);
|
2025-12-22 09:36:23 +09:00
|
|
|
|
|
|
|
|
// 권한 확인
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (!loading) {
|
|
|
|
|
if (!isLoggedIn) {
|
2025-12-22 11:42:37 +09:00
|
|
|
// 로그인 후 다시 admin으로 돌아올 수 있도록 현재 경로 전달
|
|
|
|
|
navigate('/login', { state: { from: location.pathname } });
|
2025-12-22 09:36:23 +09:00
|
|
|
} else if (!isAdmin) {
|
2025-12-22 11:42:37 +09:00
|
|
|
setToast('관리자 권한이 필요합니다.');
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
navigate('/');
|
|
|
|
|
}, 1500);
|
2025-12-22 09:36:23 +09:00
|
|
|
}
|
|
|
|
|
}
|
2025-12-22 11:42:37 +09:00
|
|
|
}, [isLoggedIn, isAdmin, loading, navigate, location.pathname]);
|
2025-12-22 09:36:23 +09:00
|
|
|
|
|
|
|
|
const handleLogout = () => {
|
|
|
|
|
logout();
|
|
|
|
|
navigate('/');
|
|
|
|
|
};
|
|
|
|
|
|
2025-12-22 11:42:37 +09:00
|
|
|
// 토스트 자동 숨기기
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (toast) {
|
|
|
|
|
const timer = setTimeout(() => setToast(null), 3000);
|
|
|
|
|
return () => clearTimeout(timer);
|
|
|
|
|
}
|
|
|
|
|
}, [toast]);
|
|
|
|
|
|
2025-12-22 09:36:23 +09:00
|
|
|
if (loading) {
|
|
|
|
|
return (
|
|
|
|
|
<div className="flex items-center justify-center min-h-[60vh]">
|
|
|
|
|
<Loader2 className="w-8 h-8 text-mc-green animate-spin" />
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!isLoggedIn || !isAdmin) {
|
2025-12-22 11:42:37 +09:00
|
|
|
return (
|
|
|
|
|
<>
|
|
|
|
|
{/* 토스트 알림 */}
|
|
|
|
|
<AnimatePresence>
|
|
|
|
|
{toast && (
|
|
|
|
|
<motion.div
|
|
|
|
|
initial={{ opacity: 0, y: 50 }}
|
|
|
|
|
animate={{ opacity: 1, y: 0 }}
|
|
|
|
|
exit={{ opacity: 0, y: 50 }}
|
|
|
|
|
className="fixed bottom-8 left-1/2 -translate-x-1/2 z-[200] bg-red-500/90 backdrop-blur-sm text-white px-6 py-3 rounded-xl text-center font-medium shadow-lg"
|
|
|
|
|
>
|
|
|
|
|
{toast}
|
|
|
|
|
</motion.div>
|
|
|
|
|
)}
|
|
|
|
|
</AnimatePresence>
|
|
|
|
|
<div className="flex items-center justify-center min-h-[60vh]">
|
|
|
|
|
<Loader2 className="w-8 h-8 text-mc-green animate-spin" />
|
|
|
|
|
</div>
|
|
|
|
|
</>
|
|
|
|
|
);
|
2025-12-22 09:36:23 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="p-6 max-w-4xl mx-auto">
|
|
|
|
|
{/* 헤더 */}
|
|
|
|
|
<div className="flex items-center justify-between mb-8">
|
|
|
|
|
<div className="flex items-center gap-3">
|
|
|
|
|
<div className="p-3 rounded-xl bg-yellow-500/20 border border-yellow-500/30">
|
|
|
|
|
<Shield className="w-6 h-6 text-yellow-500" />
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<h1 className="text-2xl font-bold text-white">관리자 페이지</h1>
|
|
|
|
|
<p className="text-sm text-zinc-400">서버 관리 및 설정</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<button
|
|
|
|
|
onClick={handleLogout}
|
|
|
|
|
className="flex items-center gap-2 px-4 py-2 bg-zinc-800 hover:bg-zinc-700 text-zinc-300 rounded-lg transition-colors"
|
|
|
|
|
>
|
|
|
|
|
<LogOut className="w-4 h-4" />
|
|
|
|
|
로그아웃
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* 사용자 정보 */}
|
|
|
|
|
<div className="bg-zinc-900 border border-zinc-800 rounded-2xl p-6 mb-6">
|
|
|
|
|
<h2 className="text-lg font-semibold text-white mb-4">로그인 정보</h2>
|
|
|
|
|
<div className="grid grid-cols-2 gap-4 text-sm">
|
|
|
|
|
<div>
|
|
|
|
|
<span className="text-zinc-500">이름</span>
|
|
|
|
|
<p className="text-white mt-1">{user?.name || '-'}</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<span className="text-zinc-500">이메일</span>
|
|
|
|
|
<p className="text-white mt-1">{user?.email}</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* 관리 기능 카드 */}
|
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
|
|
|
{/* 서버 상태 */}
|
|
|
|
|
<div className="bg-zinc-900 border border-zinc-800 rounded-2xl p-6">
|
|
|
|
|
<div className="flex items-center gap-3 mb-4">
|
|
|
|
|
<Server className="w-5 h-5 text-mc-green" />
|
|
|
|
|
<h2 className="text-lg font-semibold text-white">서버 상태</h2>
|
|
|
|
|
</div>
|
|
|
|
|
<p className="text-zinc-400 text-sm">
|
|
|
|
|
마인크래프트 서버 상태 모니터링 및 관리
|
|
|
|
|
</p>
|
|
|
|
|
<div className="mt-4 py-2 px-3 bg-mc-green/10 border border-mc-green/20 rounded-lg text-mc-green text-sm inline-block">
|
|
|
|
|
정상 작동 중
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* 플레이어 관리 */}
|
|
|
|
|
<div className="bg-zinc-900 border border-zinc-800 rounded-2xl p-6">
|
|
|
|
|
<div className="flex items-center gap-3 mb-4">
|
|
|
|
|
<Users className="w-5 h-5 text-blue-400" />
|
|
|
|
|
<h2 className="text-lg font-semibold text-white">플레이어 관리</h2>
|
|
|
|
|
</div>
|
|
|
|
|
<p className="text-zinc-400 text-sm">
|
|
|
|
|
접속 중인 플레이어 목록 및 관리 기능
|
|
|
|
|
</p>
|
|
|
|
|
<div className="mt-4 text-zinc-500 text-sm">
|
|
|
|
|
추후 업데이트 예정
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* 설정 */}
|
|
|
|
|
<div className="bg-zinc-900 border border-zinc-800 rounded-2xl p-6 md:col-span-2">
|
|
|
|
|
<div className="flex items-center gap-3 mb-4">
|
|
|
|
|
<Settings className="w-5 h-5 text-zinc-400" />
|
|
|
|
|
<h2 className="text-lg font-semibold text-white">설정</h2>
|
|
|
|
|
</div>
|
|
|
|
|
<p className="text-zinc-400 text-sm">
|
|
|
|
|
대시보드 설정 및 구성 관리
|
|
|
|
|
</p>
|
|
|
|
|
<div className="mt-4 text-zinc-500 text-sm">
|
|
|
|
|
추후 업데이트 예정
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|