feat: 모드팩 배포 시스템 UI 구현

- 사용자 페이지 (/modpack): GitHub Release 스타일 UI
  - 버전별 카드, 변경 로그/콘텐츠 접이식 표시
  - framer-motion 애니메이션 적용

- 관리자 콘솔: 모드팩 탭 추가
  - 목록 조회, 업로드/수정/삭제 다이얼로그
  - 모바일/데스크톱 분기 처리 (세로 카드 / 가로 레이아웃)
  - 모바일 바텀 네비게이션

- Sidebar: 모드팩 메뉴 추가 (월드맵 아래)
This commit is contained in:
caadiq 2025-12-23 16:19:13 +09:00
parent 81ed6ebf9c
commit 7532bff8aa

View file

@ -132,6 +132,7 @@ export default function Admin({ isMobile = false }) {
{ 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 [modpackDeleteTarget, setModpackDeleteTarget] = useState(null); //
//
useEffect(() => {
@ -1614,12 +1615,7 @@ export default function Admin({ isMobile = false }) {
<Pencil size={14} />
</button>
<button
onClick={() => {
if (confirm(`${pack.name} v${pack.version}을(를) 삭제하시겠습니까?`)) {
setModpacks(prev => prev.filter(p => p.id !== pack.id));
setToast('모드팩이 삭제되었습니다.');
}
}}
onClick={() => setModpackDeleteTarget(pack)}
className="p-1.5 text-zinc-400 hover:text-red-400 transition-colors"
>
<Trash2 size={14} />
@ -1659,12 +1655,7 @@ export default function Admin({ isMobile = false }) {
<Pencil size={16} />
</button>
<button
onClick={() => {
if (confirm(`${pack.name} v${pack.version}을(를) 삭제하시겠습니까?`)) {
setModpacks(prev => prev.filter(p => p.id !== pack.id));
setToast('모드팩이 삭제되었습니다.');
}
}}
onClick={() => setModpackDeleteTarget(pack)}
className="p-2 text-zinc-400 hover:text-red-400 transition-colors"
title="삭제"
>
@ -2012,6 +2003,55 @@ export default function Admin({ isMobile = false }) {
</motion.div>
)}
</AnimatePresence>
{/* 모드팩 삭제 확인 다이얼로그 */}
<AnimatePresence>
{modpackDeleteTarget && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center z-[100] p-4"
onClick={() => setModpackDeleteTarget(null)}
>
<motion.div
initial={{ scale: 0.9, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
exit={{ scale: 0.9, opacity: 0 }}
className="bg-zinc-900 border border-zinc-800 rounded-2xl p-6 max-w-sm w-full"
onClick={e => e.stopPropagation()}
>
<div className="text-center mb-6">
<div className="w-12 h-12 bg-red-500/20 rounded-full flex items-center justify-center mx-auto mb-3">
<Trash2 className="text-red-400" size={24} />
</div>
<h3 className="text-white text-lg font-bold mb-2">모드팩 삭제</h3>
<p className="text-zinc-400 text-sm">
<span className="text-white font-medium">{modpackDeleteTarget.name} v{modpackDeleteTarget.version}</span>() 삭제하시겠습니까?
</p>
</div>
<div className="flex gap-3">
<button
onClick={() => setModpackDeleteTarget(null)}
className="flex-1 px-4 py-2.5 bg-zinc-800 hover:bg-zinc-700 text-white rounded-xl font-medium transition-colors"
>
취소
</button>
<button
onClick={() => {
setModpacks(prev => prev.filter(p => p.id !== modpackDeleteTarget.id));
setToast('모드팩이 삭제되었습니다.');
setModpackDeleteTarget(null);
}}
className="flex-1 px-4 py-2.5 bg-red-500 hover:bg-red-600 text-white rounded-xl font-medium transition-colors"
>
삭제
</button>
</div>
</motion.div>
</motion.div>
)}
</AnimatePresence>
</div>
);
}