/**
* 모드팩 페이지 - 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 ? (

) : (
)}
{/* 정보 */}
);
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 ? (

) : (
)}
);
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 && (
)}
);
}