/** * 모드팩 페이지 - GitHub Release 스타일 */ 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 formatFileSize = (bytes) => { if (bytes < 1024) return `${bytes} B`; if (bytes < 1048576) return `${(bytes / 1024).toFixed(1)} KB`; if (bytes < 1073741824) return `${(bytes / 1048576).toFixed(1)} MB`; return `${(bytes / 1073741824).toFixed(2)} GB`; }; // 날짜 포맷 const formatDate = (dateString) => { const date = new Date(dateString); return date.toLocaleDateString('ko-KR', { year: 'numeric', month: 'long', day: 'numeric', }); }; // 모드팩 카드 컴포넌트 const ModpackCard = ({ modpack, isLatest }) => { const [showChangelog, setShowChangelog] = useState(false); const [showContents, setShowContents] = useState(false); const totalMods = modpack.contents.mods.length; const totalResourcepacks = modpack.contents.resourcepacks.length; const totalShaderpacks = modpack.contents.shaderpacks.length; return (
{/* 헤더 */}
{/* 상단: 아이콘 + 버전 정보 + 다운로드 버튼 */}

v{modpack.version}

{isLatest && ( 최신 )}

{modpack.name}

{/* PC: 오른쪽에 다운로드 버튼 */} 다운로드
{/* 모바일: 전체 너비 다운로드 버튼 */} 다운로드 {/* 메타 정보 */}
MC {modpack.minecraftVersion}
{modpack.modLoader}
{formatFileSize(modpack.fileSize)}
{formatDate(modpack.createdAt)}
{/* 포함 콘텐츠 요약 */}
{totalMods > 0 && ( 모드 {totalMods}개 )} {totalResourcepacks > 0 && ( 리소스팩 {totalResourcepacks}개 )} {totalShaderpacks > 0 && ( 쉐이더 {totalShaderpacks}개 )}
{/* 변경 로그 토글 */} {/* 변경 로그 내용 */} {showChangelog && (
{modpack.changelog.split('\n').map((line, i) => { if (line.startsWith('###')) { return

{line.replace('### ', '')}

; } if (line.startsWith('- ')) { return

• {line.replace('- ', '')}

; } return null; })}
)}
{/* 포함 콘텐츠 토글 */} {/* 포함 콘텐츠 내용 */} {showContents && (
{/* 모드 */} {modpack.contents.mods.length > 0 && (

모드 ({modpack.contents.mods.length}개)

{[...modpack.contents.mods].sort((a, b) => { const titleA = (typeof a === 'object' ? a.title : a).toLowerCase(); const titleB = (typeof b === 'object' ? b.title : b).toLowerCase(); return titleA.localeCompare(titleB); }).map((mod, i) => { // mod가 객체인 경우 (Modrinth 연동 후) vs 문자열인 경우 (현재) const isObject = typeof mod === 'object'; const title = isObject ? mod.title : mod; const version = isObject ? mod.version : null; const iconUrl = isObject ? mod.icon_url : null; const slug = isObject ? mod.slug : null; const content = (
{/* 아이콘 */}
{iconUrl ? ( ) : ( )}
{/* 정보 */}

{title}

{version || ''}

); return slug ? ( {content} ) : (
{content}
); })}
)} {/* 리소스팩 */} {modpack.contents.resourcepacks.length > 0 && (

리소스팩 ({modpack.contents.resourcepacks.length}개)

{[...modpack.contents.resourcepacks].sort((a, b) => { const titleA = (typeof a === 'object' ? a.title : a).toLowerCase(); const titleB = (typeof b === 'object' ? b.title : b).toLowerCase(); return titleA.localeCompare(titleB); }).map((pack, i) => { const isObject = typeof pack === 'object'; const title = isObject ? pack.title : pack; const version = isObject ? pack.version : null; const iconUrl = isObject ? pack.icon_url : null; const slug = isObject ? pack.slug : null; const content = (
{iconUrl ? ( ) : ( )}

{title}

{version || ''}

); return slug ? ( {content} ) : (
{content}
); })}
)} {/* 쉐이더 */} {modpack.contents.shaderpacks.length > 0 && (

쉐이더 ({modpack.contents.shaderpacks.length}개)

{[...modpack.contents.shaderpacks].sort((a, b) => { const titleA = (typeof a === 'object' ? a.title : a).toLowerCase(); const titleB = (typeof b === 'object' ? b.title : b).toLowerCase(); return titleA.localeCompare(titleB); }).map((shader, i) => { const isObject = typeof shader === 'object'; const title = isObject ? shader.title : shader; const version = isObject ? shader.version : null; const iconUrl = isObject ? shader.icon_url : null; const slug = isObject ? shader.slug : null; const content = (
{iconUrl ? ( ) : ( )}

{title}

{version &&

{version}

}
); return slug ? ( {content} ) : (
{content}
); })}
)}
)}
); }; export default function Modpack() { 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 (
{/* 헤더 */}

모드팩

서버 접속에 필요한 모드팩을 다운로드하세요

{/* 모드팩 목록 */}
{modpacks.map((modpack, index) => ( ))}
{/* 빈 상태 (모드팩이 없을 때) */} {modpacks.length === 0 && (

등록된 모드팩이 없습니다

)}
); }