✨ PC 버전 프론트엔드 UI 구현 - 홈, 멤버, 디스코그래피, 스케줄 페이지 및 더미 데이터
This commit is contained in:
parent
05b77140d6
commit
e2c1a6a774
16 changed files with 933 additions and 0 deletions
29
backend/server.js
Normal file
29
backend/server.js
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
import express from "express";
|
||||||
|
import path from "path";
|
||||||
|
import { fileURLToPath } from "url";
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
const PORT = process.env.PORT || 80;
|
||||||
|
|
||||||
|
// JSON 파싱
|
||||||
|
app.use(express.json());
|
||||||
|
|
||||||
|
// 정적 파일 서빙 (프론트엔드 빌드 결과물)
|
||||||
|
app.use(express.static(path.join(__dirname, "dist")));
|
||||||
|
|
||||||
|
// API 라우트 (추후 구현)
|
||||||
|
app.get("/api/health", (req, res) => {
|
||||||
|
res.json({ status: "ok", timestamp: new Date().toISOString() });
|
||||||
|
});
|
||||||
|
|
||||||
|
// SPA 폴백 - 모든 요청을 index.html로
|
||||||
|
app.get("*", (req, res) => {
|
||||||
|
res.sendFile(path.join(__dirname, "dist", "index.html"));
|
||||||
|
});
|
||||||
|
|
||||||
|
app.listen(PORT, () => {
|
||||||
|
console.log(`🌸 fromis_9 서버가 포트 ${PORT}에서 실행 중입니다`);
|
||||||
|
});
|
||||||
18
frontend/index.html
Normal file
18
frontend/index.html
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="ko">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>fromis_9 - 프로미스나인</title>
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||||
|
<link
|
||||||
|
href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@300;400;500;600;700&display=swap"
|
||||||
|
rel="stylesheet"
|
||||||
|
/>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/main.jsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
6
frontend/postcss.config.js
Normal file
6
frontend/postcss.config.js
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
export default {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
39
frontend/src/App.jsx
Normal file
39
frontend/src/App.jsx
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
import { BrowserRouter, Routes, Route } from 'react-router-dom';
|
||||||
|
import { BrowserView, MobileView } from 'react-device-detect';
|
||||||
|
|
||||||
|
// PC 페이지
|
||||||
|
import PCHome from './pages/pc/Home';
|
||||||
|
import PCMembers from './pages/pc/Members';
|
||||||
|
import PCDiscography from './pages/pc/Discography';
|
||||||
|
import PCSchedule from './pages/pc/Schedule';
|
||||||
|
|
||||||
|
// PC 레이아웃
|
||||||
|
import PCLayout from './components/pc/Layout';
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
return (
|
||||||
|
<BrowserRouter>
|
||||||
|
<BrowserView>
|
||||||
|
<PCLayout>
|
||||||
|
<Routes>
|
||||||
|
<Route path="/" element={<PCHome />} />
|
||||||
|
<Route path="/members" element={<PCMembers />} />
|
||||||
|
<Route path="/discography" element={<PCDiscography />} />
|
||||||
|
<Route path="/schedule" element={<PCSchedule />} />
|
||||||
|
</Routes>
|
||||||
|
</PCLayout>
|
||||||
|
</BrowserView>
|
||||||
|
<MobileView>
|
||||||
|
{/* 모바일 버전은 추후 구현 */}
|
||||||
|
<div className="flex items-center justify-center h-screen bg-gray-100 p-4">
|
||||||
|
<div className="text-center">
|
||||||
|
<p className="text-xl font-bold text-primary mb-2">fromis_9</p>
|
||||||
|
<p className="text-gray-500">모바일 버전은 준비 중입니다.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</MobileView>
|
||||||
|
</BrowserRouter>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default App;
|
||||||
72
frontend/src/components/pc/Footer.jsx
Normal file
72
frontend/src/components/pc/Footer.jsx
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
import { Instagram, Youtube, Twitter } from 'lucide-react';
|
||||||
|
import { socialLinks } from '../../data/dummy';
|
||||||
|
|
||||||
|
function Footer() {
|
||||||
|
return (
|
||||||
|
<footer className="bg-gray-900 text-white py-12">
|
||||||
|
<div className="max-w-7xl mx-auto px-6">
|
||||||
|
<div className="grid grid-cols-3 gap-8">
|
||||||
|
{/* 로고 및 설명 */}
|
||||||
|
<div>
|
||||||
|
<h3 className="text-2xl font-bold text-primary-light mb-4">fromis_9</h3>
|
||||||
|
<p className="text-gray-400 text-sm leading-relaxed">
|
||||||
|
인사드리겠습니다. 둘, 셋!<br />
|
||||||
|
이제는 약속해 소중히 간직해,<br />
|
||||||
|
당신의 아이돌로 성장하겠습니다!<br />
|
||||||
|
안녕하세요, 프로미스나인입니다.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 링크 */}
|
||||||
|
<div>
|
||||||
|
<h4 className="font-semibold mb-4">Quick Links</h4>
|
||||||
|
<ul className="space-y-2 text-sm text-gray-400">
|
||||||
|
<li><a href="/members" className="hover:text-primary-light transition-colors">멤버</a></li>
|
||||||
|
<li><a href="/discography" className="hover:text-primary-light transition-colors">디스코그래피</a></li>
|
||||||
|
<li><a href="/schedule" className="hover:text-primary-light transition-colors">스케줄</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* SNS */}
|
||||||
|
<div>
|
||||||
|
<h4 className="font-semibold mb-4">Follow Us</h4>
|
||||||
|
<div className="flex gap-4">
|
||||||
|
<a
|
||||||
|
href={socialLinks.youtube}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="text-gray-400 hover:text-red-500 transition-colors"
|
||||||
|
>
|
||||||
|
<Youtube size={24} />
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href={socialLinks.instagram}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="text-gray-400 hover:text-pink-500 transition-colors"
|
||||||
|
>
|
||||||
|
<Instagram size={24} />
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href={socialLinks.twitter}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="text-gray-400 hover:text-blue-400 transition-colors"
|
||||||
|
>
|
||||||
|
<Twitter size={24} />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 저작권 */}
|
||||||
|
<div className="border-t border-gray-800 mt-8 pt-8 text-center text-sm text-gray-500">
|
||||||
|
<p>© 2025 fromis_9 Fan Site. This is an unofficial fan-made website.</p>
|
||||||
|
<p className="mt-1">All rights belong to PLEDIS Entertainment / HYBE.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Footer;
|
||||||
72
frontend/src/components/pc/Header.jsx
Normal file
72
frontend/src/components/pc/Header.jsx
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
import { NavLink } from 'react-router-dom';
|
||||||
|
import { Instagram, Youtube, Twitter } from 'lucide-react';
|
||||||
|
import { socialLinks } from '../../data/dummy';
|
||||||
|
|
||||||
|
function Header() {
|
||||||
|
const navItems = [
|
||||||
|
{ path: '/', label: '홈' },
|
||||||
|
{ path: '/members', label: '멤버' },
|
||||||
|
{ path: '/discography', label: '디스코그래피' },
|
||||||
|
{ path: '/schedule', label: '스케줄' },
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<header className="bg-white shadow-sm sticky top-0 z-50">
|
||||||
|
<div className="max-w-7xl mx-auto px-6">
|
||||||
|
<div className="flex items-center justify-between h-16">
|
||||||
|
{/* 로고 */}
|
||||||
|
<NavLink to="/" className="flex items-center gap-2">
|
||||||
|
<span className="text-2xl font-bold text-primary">fromis_9</span>
|
||||||
|
</NavLink>
|
||||||
|
|
||||||
|
{/* 네비게이션 */}
|
||||||
|
<nav className="flex items-center gap-8">
|
||||||
|
{navItems.map((item) => (
|
||||||
|
<NavLink
|
||||||
|
key={item.path}
|
||||||
|
to={item.path}
|
||||||
|
className={({ isActive }) =>
|
||||||
|
`text-sm font-medium transition-colors hover:text-primary ${
|
||||||
|
isActive ? 'text-primary' : 'text-gray-600'
|
||||||
|
}`
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{item.label}
|
||||||
|
</NavLink>
|
||||||
|
))}
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
{/* SNS 링크 */}
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<a
|
||||||
|
href={socialLinks.youtube}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="text-gray-500 hover:text-red-600 transition-colors"
|
||||||
|
>
|
||||||
|
<Youtube size={20} />
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href={socialLinks.instagram}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="text-gray-500 hover:text-pink-600 transition-colors"
|
||||||
|
>
|
||||||
|
<Instagram size={20} />
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href={socialLinks.twitter}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="text-gray-500 hover:text-blue-500 transition-colors"
|
||||||
|
>
|
||||||
|
<Twitter size={20} />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Header;
|
||||||
14
frontend/src/components/pc/Layout.jsx
Normal file
14
frontend/src/components/pc/Layout.jsx
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
import Header from './Header';
|
||||||
|
import Footer from './Footer';
|
||||||
|
|
||||||
|
function Layout({ children }) {
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen flex flex-col">
|
||||||
|
<Header />
|
||||||
|
<main className="flex-1">{children}</main>
|
||||||
|
<Footer />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Layout;
|
||||||
125
frontend/src/data/dummy.js
Normal file
125
frontend/src/data/dummy.js
Normal file
|
|
@ -0,0 +1,125 @@
|
||||||
|
// 더미 멤버 데이터
|
||||||
|
export const members = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: "송하영",
|
||||||
|
birthDate: "1997.03.25",
|
||||||
|
position: "리더, 메인보컬, 메인댄서",
|
||||||
|
imageUrl:
|
||||||
|
"https://i.namu.wiki/i/M9XsM0yLkPOEwQJBAnVn2rMqo4kXRfv-AkvWNvgAX1m2IUWX1IECPIZ1sqH6tDMAmpqYQefaP1ydJz7hUZ6Zxw.webp",
|
||||||
|
instagram: "https://www.instagram.com/hayoung_0325/",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: "박지원",
|
||||||
|
birthDate: "1998.10.20",
|
||||||
|
position: "메인보컬",
|
||||||
|
imageUrl:
|
||||||
|
"https://i.namu.wiki/i/M9XsM0yLkPOEwQJBAnVn2rMqo4kXRfv-AkvWNvgAX1lE7WfQKMuTmQCCJcXhPLv65cQCTMHWFhzhjJZIEbAKbQ.webp",
|
||||||
|
instagram: "https://www.instagram.com/jiwon_1020/",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: "이채영",
|
||||||
|
birthDate: "2000.06.14",
|
||||||
|
position: "래퍼",
|
||||||
|
imageUrl:
|
||||||
|
"https://i.namu.wiki/i/M9XsM0yLkPOEwQJBAnVn2rMqo4kXRfv-AkvWNvgAX1lqvGHXpz1iuLHTnVmAEdbgahPaQEZA7VOxLLdB-pK_hQ.webp",
|
||||||
|
instagram: "https://www.instagram.com/chaeyoung_0614/",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
name: "이나경",
|
||||||
|
birthDate: "2000.05.01",
|
||||||
|
position: "리드보컬",
|
||||||
|
imageUrl:
|
||||||
|
"https://i.namu.wiki/i/M9XsM0yLkPOEwQJBAnVn2rMqo4kXRfv-AkvWNvgAX1kGZHhKV9nNDi9mSwBCKKl5QGcHNT8_EZxLKSv6QmtVmg.webp",
|
||||||
|
instagram: "https://www.instagram.com/nakyung_0501/",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
name: "백지헌",
|
||||||
|
birthDate: "2003.04.17",
|
||||||
|
position: "막내",
|
||||||
|
imageUrl:
|
||||||
|
"https://i.namu.wiki/i/M9XsM0yLkPOEwQJBAnVn2rMqo4kXRfv-AkvWNvgAX1n2pqk4sWJWC_FGvQpEmLxSJC1tuhIwq8cWnqt-HMv_gw.webp",
|
||||||
|
instagram: "https://www.instagram.com/jiheon_0417/",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// 더미 앨범 데이터
|
||||||
|
export const albums = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
title: "Unlock My World",
|
||||||
|
albumType: "정규",
|
||||||
|
releaseDate: "2023.06.05",
|
||||||
|
titleTrack: "Unlock My World",
|
||||||
|
coverUrl:
|
||||||
|
"https://i.namu.wiki/i/KDu-5K16r75g3eCGiYiOqRBNJLKfmimubzXR1bSY2C39dxGbAG1nZqKYn_uqxjShKmN0HRjqJjM1DQfbHwXhsQ.webp",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
title: "from our Memento Box",
|
||||||
|
albumType: "미니",
|
||||||
|
releaseDate: "2022.06.27",
|
||||||
|
titleTrack: "Stay This Way",
|
||||||
|
coverUrl:
|
||||||
|
"https://i.namu.wiki/i/RJ-v7VHvnbV2-M7S1YBn35LLQ1PV4rrLM-QE5qE0-C_p5xYx6vb0fB0O5oXPZsMo0kOWPLyE_45Cpt1kkQa5xg.webp",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
title: "Midnight Guest",
|
||||||
|
albumType: "미니",
|
||||||
|
releaseDate: "2022.01.17",
|
||||||
|
titleTrack: "DM",
|
||||||
|
coverUrl:
|
||||||
|
"https://i.namu.wiki/i/yPAOeX6xBp_Bxs0wNLX-4IWLvqoTJoiZPAcDFdFHiUXTM3YLKtfSbEOqr3ofAZGdPNnBxNBKXQ0bZKBJjWG1hg.webp",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
title: "Talk & Talk",
|
||||||
|
albumType: "싱글",
|
||||||
|
releaseDate: "2024.09.05",
|
||||||
|
titleTrack: "Supersonic",
|
||||||
|
coverUrl:
|
||||||
|
"https://i.namu.wiki/i/jYC5xE6SyC-eDlQWMHrx2OIKHKkJGkgj-_zCeMIgw8CbvU-d6c5kGE4Zy3cwkiZ7kpRG5Hmo5WfCN0uqUWpyiw.webp",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// 더미 스케줄 데이터
|
||||||
|
export const schedules = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
date: "2025-01-02",
|
||||||
|
time: "19:00",
|
||||||
|
title: "유튜브 [스프 : 스튜디오 프로미스나인 30화]",
|
||||||
|
platform: "YouTube",
|
||||||
|
members: ["전원"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
date: "2025-01-05",
|
||||||
|
time: "18:00",
|
||||||
|
title: "SBS 인기가요 출연",
|
||||||
|
platform: "SBS",
|
||||||
|
members: ["전원"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
date: "2025-01-10",
|
||||||
|
time: "20:00",
|
||||||
|
title: '팬미팅 "FLOVER DAY"',
|
||||||
|
platform: "올림픽공원",
|
||||||
|
members: ["전원"],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// 공식 SNS 링크
|
||||||
|
export const socialLinks = {
|
||||||
|
youtube: "https://www.youtube.com/c/officialfromis9",
|
||||||
|
instagram: "https://www.instagram.com/officialfromis9/",
|
||||||
|
twitter: "https://twitter.com/realfromis_9",
|
||||||
|
tiktok: "https://www.tiktok.com/@officialfromis_9",
|
||||||
|
fancafe: "https://cafe.daum.net/officialfromis9",
|
||||||
|
};
|
||||||
28
frontend/src/index.css
Normal file
28
frontend/src/index.css
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
/* 기본 스타일 */
|
||||||
|
body {
|
||||||
|
font-family: "Noto Sans KR", sans-serif;
|
||||||
|
background-color: #fafafa;
|
||||||
|
color: #1a1a1a;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 스크롤바 스타일 */
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
background: #f1f1f1;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background: #548360;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: #456e50;
|
||||||
|
}
|
||||||
10
frontend/src/main.jsx
Normal file
10
frontend/src/main.jsx
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom/client';
|
||||||
|
import App from './App';
|
||||||
|
import './index.css';
|
||||||
|
|
||||||
|
ReactDOM.createRoot(document.getElementById('root')).render(
|
||||||
|
<React.StrictMode>
|
||||||
|
<App />
|
||||||
|
</React.StrictMode>
|
||||||
|
);
|
||||||
104
frontend/src/pages/pc/Discography.jsx
Normal file
104
frontend/src/pages/pc/Discography.jsx
Normal file
|
|
@ -0,0 +1,104 @@
|
||||||
|
import { motion } from 'framer-motion';
|
||||||
|
import { Calendar, Music } from 'lucide-react';
|
||||||
|
import { albums } from '../../data/dummy';
|
||||||
|
|
||||||
|
function Discography() {
|
||||||
|
return (
|
||||||
|
<div className="py-16">
|
||||||
|
<div className="max-w-7xl mx-auto px-6">
|
||||||
|
{/* 헤더 */}
|
||||||
|
<div className="text-center mb-12">
|
||||||
|
<motion.h1
|
||||||
|
initial={{ opacity: 0, y: -20 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
className="text-4xl font-bold mb-4"
|
||||||
|
>
|
||||||
|
디스코그래피
|
||||||
|
</motion.h1>
|
||||||
|
<motion.p
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 1 }}
|
||||||
|
transition={{ delay: 0.2 }}
|
||||||
|
className="text-gray-500"
|
||||||
|
>
|
||||||
|
프로미스나인의 음악을 만나보세요
|
||||||
|
</motion.p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 앨범 그리드 */}
|
||||||
|
<div className="grid grid-cols-4 gap-8">
|
||||||
|
{albums.map((album, index) => (
|
||||||
|
<motion.div
|
||||||
|
key={album.id}
|
||||||
|
initial={{ opacity: 0, y: 30 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ delay: index * 0.1 }}
|
||||||
|
className="group bg-white rounded-2xl overflow-hidden shadow-lg hover:shadow-2xl transition-all duration-300"
|
||||||
|
>
|
||||||
|
{/* 앨범 커버 */}
|
||||||
|
<div className="relative aspect-square bg-gray-100 overflow-hidden">
|
||||||
|
<img
|
||||||
|
src={album.coverUrl}
|
||||||
|
alt={album.title}
|
||||||
|
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-500"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* 앨범 타입 배지 */}
|
||||||
|
<span className="absolute top-4 left-4 px-3 py-1 bg-primary text-white text-xs font-medium rounded-full">
|
||||||
|
{album.albumType}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{/* 호버 오버레이 */}
|
||||||
|
<div className="absolute inset-0 bg-black/50 opacity-0 group-hover:opacity-100 transition-opacity duration-300 flex items-center justify-center">
|
||||||
|
<div className="text-center text-white">
|
||||||
|
<Music size={40} className="mx-auto mb-2" />
|
||||||
|
<p className="text-sm">앨범 상세보기</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 앨범 정보 */}
|
||||||
|
<div className="p-6">
|
||||||
|
<h3 className="font-bold text-lg mb-1 truncate">{album.title}</h3>
|
||||||
|
<p className="text-primary text-sm font-medium mb-3">
|
||||||
|
{album.titleTrack}
|
||||||
|
</p>
|
||||||
|
<div className="flex items-center gap-2 text-sm text-gray-500">
|
||||||
|
<Calendar size={14} />
|
||||||
|
<span>{album.releaseDate}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 통계 */}
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 30 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ delay: 0.5 }}
|
||||||
|
className="mt-16 grid grid-cols-4 gap-6"
|
||||||
|
>
|
||||||
|
<div className="bg-gray-50 rounded-2xl p-6 text-center">
|
||||||
|
<p className="text-3xl font-bold text-primary mb-1">1</p>
|
||||||
|
<p className="text-gray-500 text-sm">정규 앨범</p>
|
||||||
|
</div>
|
||||||
|
<div className="bg-gray-50 rounded-2xl p-6 text-center">
|
||||||
|
<p className="text-3xl font-bold text-primary mb-1">2</p>
|
||||||
|
<p className="text-gray-500 text-sm">미니 앨범</p>
|
||||||
|
</div>
|
||||||
|
<div className="bg-gray-50 rounded-2xl p-6 text-center">
|
||||||
|
<p className="text-3xl font-bold text-primary mb-1">1</p>
|
||||||
|
<p className="text-gray-500 text-sm">싱글 앨범</p>
|
||||||
|
</div>
|
||||||
|
<div className="bg-gray-50 rounded-2xl p-6 text-center">
|
||||||
|
<p className="text-3xl font-bold text-primary mb-1">4+</p>
|
||||||
|
<p className="text-gray-500 text-sm">총 앨범</p>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Discography;
|
||||||
149
frontend/src/pages/pc/Home.jsx
Normal file
149
frontend/src/pages/pc/Home.jsx
Normal file
|
|
@ -0,0 +1,149 @@
|
||||||
|
import { motion } from 'framer-motion';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import { Calendar, Users, Disc3, ArrowRight } from 'lucide-react';
|
||||||
|
import { members, schedules, albums } from '../../data/dummy';
|
||||||
|
|
||||||
|
function Home() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{/* 히어로 섹션 */}
|
||||||
|
<section className="relative h-[600px] bg-gradient-to-br from-primary to-primary-dark overflow-hidden">
|
||||||
|
<div className="absolute inset-0 bg-black/20" />
|
||||||
|
<div className="relative max-w-7xl mx-auto px-6 h-full flex items-center">
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 30 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.8 }}
|
||||||
|
className="text-white"
|
||||||
|
>
|
||||||
|
<h1 className="text-6xl font-bold mb-4">fromis_9</h1>
|
||||||
|
<p className="text-2xl font-light mb-2">프로미스나인</p>
|
||||||
|
<p className="text-lg opacity-80 mb-8 leading-relaxed">
|
||||||
|
인사드리겠습니다. 둘, 셋!<br />
|
||||||
|
이제는 약속해 소중히 간직해,<br />
|
||||||
|
당신의 아이돌로 성장하겠습니다!
|
||||||
|
</p>
|
||||||
|
<Link
|
||||||
|
to="/members"
|
||||||
|
className="inline-flex items-center gap-2 bg-white text-primary px-6 py-3 rounded-full font-medium hover:bg-gray-100 transition-colors"
|
||||||
|
>
|
||||||
|
멤버 보기
|
||||||
|
<ArrowRight size={18} />
|
||||||
|
</Link>
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 장식 */}
|
||||||
|
<div className="absolute right-0 bottom-0 w-1/2 h-full opacity-10">
|
||||||
|
<div className="absolute right-10 top-20 w-64 h-64 rounded-full bg-white/30" />
|
||||||
|
<div className="absolute right-40 bottom-20 w-48 h-48 rounded-full bg-white/20" />
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* 퀵 링크 섹션 */}
|
||||||
|
<section className="py-16 bg-white">
|
||||||
|
<div className="max-w-7xl mx-auto px-6">
|
||||||
|
<div className="grid grid-cols-3 gap-8">
|
||||||
|
<Link
|
||||||
|
to="/members"
|
||||||
|
className="group p-8 bg-gray-50 rounded-2xl hover:bg-primary hover:text-white transition-all duration-300"
|
||||||
|
>
|
||||||
|
<Users size={40} className="mb-4 text-primary group-hover:text-white" />
|
||||||
|
<h3 className="text-xl font-bold mb-2">멤버</h3>
|
||||||
|
<p className="text-gray-500 group-hover:text-white/80">5명의 멤버를 만나보세요</p>
|
||||||
|
</Link>
|
||||||
|
<Link
|
||||||
|
to="/discography"
|
||||||
|
className="group p-8 bg-gray-50 rounded-2xl hover:bg-primary hover:text-white transition-all duration-300"
|
||||||
|
>
|
||||||
|
<Disc3 size={40} className="mb-4 text-primary group-hover:text-white" />
|
||||||
|
<h3 className="text-xl font-bold mb-2">디스코그래피</h3>
|
||||||
|
<p className="text-gray-500 group-hover:text-white/80">앨범과 음악을 확인하세요</p>
|
||||||
|
</Link>
|
||||||
|
<Link
|
||||||
|
to="/schedule"
|
||||||
|
className="group p-8 bg-gray-50 rounded-2xl hover:bg-primary hover:text-white transition-all duration-300"
|
||||||
|
>
|
||||||
|
<Calendar size={40} className="mb-4 text-primary group-hover:text-white" />
|
||||||
|
<h3 className="text-xl font-bold mb-2">스케줄</h3>
|
||||||
|
<p className="text-gray-500 group-hover:text-white/80">다가오는 일정을 확인하세요</p>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* 멤버 미리보기 */}
|
||||||
|
<section className="py-16 bg-gray-50">
|
||||||
|
<div className="max-w-7xl mx-auto px-6">
|
||||||
|
<div className="flex justify-between items-center mb-8">
|
||||||
|
<h2 className="text-3xl font-bold">멤버</h2>
|
||||||
|
<Link to="/members" className="text-primary hover:underline flex items-center gap-1">
|
||||||
|
전체보기 <ArrowRight size={16} />
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-5 gap-6">
|
||||||
|
{members.map((member, index) => (
|
||||||
|
<motion.div
|
||||||
|
key={member.id}
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ delay: index * 0.1 }}
|
||||||
|
className="bg-white rounded-2xl overflow-hidden shadow-sm hover:shadow-lg transition-shadow"
|
||||||
|
>
|
||||||
|
<div className="aspect-square bg-gray-100">
|
||||||
|
<img
|
||||||
|
src={member.imageUrl}
|
||||||
|
alt={member.name}
|
||||||
|
className="w-full h-full object-cover"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="p-4 text-center">
|
||||||
|
<h3 className="font-bold text-lg">{member.name}</h3>
|
||||||
|
<p className="text-sm text-gray-500">{member.position.split(',')[0]}</p>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* 스케줄 미리보기 */}
|
||||||
|
<section className="py-16 bg-white">
|
||||||
|
<div className="max-w-7xl mx-auto px-6">
|
||||||
|
<div className="flex justify-between items-center mb-8">
|
||||||
|
<h2 className="text-3xl font-bold">다가오는 스케줄</h2>
|
||||||
|
<Link to="/schedule" className="text-primary hover:underline flex items-center gap-1">
|
||||||
|
전체보기 <ArrowRight size={16} />
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-4">
|
||||||
|
{schedules.slice(0, 3).map((schedule) => (
|
||||||
|
<div
|
||||||
|
key={schedule.id}
|
||||||
|
className="flex items-center gap-6 p-6 bg-gray-50 rounded-xl hover:bg-gray-100 transition-colors"
|
||||||
|
>
|
||||||
|
<div className="text-center min-w-[80px]">
|
||||||
|
<p className="text-2xl font-bold text-primary">
|
||||||
|
{schedule.date.split('-')[2]}
|
||||||
|
</p>
|
||||||
|
<p className="text-sm text-gray-500">
|
||||||
|
{schedule.date.split('-')[1]}월
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex-1">
|
||||||
|
<h3 className="font-bold text-lg">{schedule.title}</h3>
|
||||||
|
<p className="text-gray-500">{schedule.platform} · {schedule.time}</p>
|
||||||
|
</div>
|
||||||
|
<div className="text-sm text-primary font-medium">
|
||||||
|
{schedule.members.join(', ')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Home;
|
||||||
108
frontend/src/pages/pc/Members.jsx
Normal file
108
frontend/src/pages/pc/Members.jsx
Normal file
|
|
@ -0,0 +1,108 @@
|
||||||
|
import { motion } from 'framer-motion';
|
||||||
|
import { Instagram, Calendar } from 'lucide-react';
|
||||||
|
import { members } from '../../data/dummy';
|
||||||
|
|
||||||
|
function Members() {
|
||||||
|
return (
|
||||||
|
<div className="py-16">
|
||||||
|
<div className="max-w-7xl mx-auto px-6">
|
||||||
|
{/* 헤더 */}
|
||||||
|
<div className="text-center mb-12">
|
||||||
|
<motion.h1
|
||||||
|
initial={{ opacity: 0, y: -20 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
className="text-4xl font-bold mb-4"
|
||||||
|
>
|
||||||
|
멤버
|
||||||
|
</motion.h1>
|
||||||
|
<motion.p
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 1 }}
|
||||||
|
transition={{ delay: 0.2 }}
|
||||||
|
className="text-gray-500"
|
||||||
|
>
|
||||||
|
프로미스나인의 5명의 멤버를 소개합니다
|
||||||
|
</motion.p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 멤버 그리드 */}
|
||||||
|
<div className="grid grid-cols-5 gap-8">
|
||||||
|
{members.map((member, index) => (
|
||||||
|
<motion.div
|
||||||
|
key={member.id}
|
||||||
|
initial={{ opacity: 0, y: 30 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ delay: index * 0.1 }}
|
||||||
|
className="group"
|
||||||
|
>
|
||||||
|
<div className="relative bg-white rounded-3xl overflow-hidden shadow-lg hover:shadow-2xl transition-all duration-300">
|
||||||
|
{/* 이미지 */}
|
||||||
|
<div className="aspect-[3/4] bg-gray-100 overflow-hidden">
|
||||||
|
<img
|
||||||
|
src={member.imageUrl}
|
||||||
|
alt={member.name}
|
||||||
|
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-500"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 정보 */}
|
||||||
|
<div className="p-6">
|
||||||
|
<h3 className="text-xl font-bold mb-1">{member.name}</h3>
|
||||||
|
<p className="text-primary text-sm font-medium mb-3">{member.position}</p>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-2 text-sm text-gray-500 mb-4">
|
||||||
|
<Calendar size={14} />
|
||||||
|
<span>{member.birthDate}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 인스타그램 링크 */}
|
||||||
|
<a
|
||||||
|
href={member.instagram}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="inline-flex items-center gap-2 text-sm text-gray-500 hover:text-pink-500 transition-colors"
|
||||||
|
>
|
||||||
|
<Instagram size={16} />
|
||||||
|
<span>Instagram</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 호버 효과 - 컬러 바 */}
|
||||||
|
<div className="absolute bottom-0 left-0 right-0 h-1 bg-primary transform scale-x-0 group-hover:scale-x-100 transition-transform duration-300" />
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 그룹 정보 */}
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 30 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ delay: 0.6 }}
|
||||||
|
className="mt-16 bg-gradient-to-r from-primary to-primary-dark rounded-3xl p-10 text-white"
|
||||||
|
>
|
||||||
|
<div className="grid grid-cols-4 gap-8 text-center">
|
||||||
|
<div>
|
||||||
|
<p className="text-4xl font-bold mb-2">2018</p>
|
||||||
|
<p className="text-white/70">데뷔 연도</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="text-4xl font-bold mb-2">5</p>
|
||||||
|
<p className="text-white/70">멤버 수</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="text-4xl font-bold mb-2">7+</p>
|
||||||
|
<p className="text-white/70">앨범 수</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="text-4xl font-bold mb-2">flover</p>
|
||||||
|
<p className="text-white/70">팬덤명</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Members;
|
||||||
126
frontend/src/pages/pc/Schedule.jsx
Normal file
126
frontend/src/pages/pc/Schedule.jsx
Normal file
|
|
@ -0,0 +1,126 @@
|
||||||
|
import { motion } from 'framer-motion';
|
||||||
|
import { Calendar, Clock, MapPin, Users } from 'lucide-react';
|
||||||
|
import { schedules } from '../../data/dummy';
|
||||||
|
|
||||||
|
function Schedule() {
|
||||||
|
// 스케줄을 날짜별로 그룹핑
|
||||||
|
const groupedSchedules = schedules.reduce((acc, schedule) => {
|
||||||
|
const date = schedule.date;
|
||||||
|
if (!acc[date]) {
|
||||||
|
acc[date] = [];
|
||||||
|
}
|
||||||
|
acc[date].push(schedule);
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
const formatDate = (dateStr) => {
|
||||||
|
const date = new Date(dateStr);
|
||||||
|
const days = ['일', '월', '화', '수', '목', '금', '토'];
|
||||||
|
return {
|
||||||
|
month: date.getMonth() + 1,
|
||||||
|
day: date.getDate(),
|
||||||
|
weekday: days[date.getDay()],
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="py-16">
|
||||||
|
<div className="max-w-7xl mx-auto px-6">
|
||||||
|
{/* 헤더 */}
|
||||||
|
<div className="text-center mb-12">
|
||||||
|
<motion.h1
|
||||||
|
initial={{ opacity: 0, y: -20 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
className="text-4xl font-bold mb-4"
|
||||||
|
>
|
||||||
|
스케줄
|
||||||
|
</motion.h1>
|
||||||
|
<motion.p
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 1 }}
|
||||||
|
transition={{ delay: 0.2 }}
|
||||||
|
className="text-gray-500"
|
||||||
|
>
|
||||||
|
프로미스나인의 다가오는 일정을 확인하세요
|
||||||
|
</motion.p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 스케줄 타임라인 */}
|
||||||
|
<div className="max-w-4xl mx-auto">
|
||||||
|
{Object.entries(groupedSchedules).map(([date, daySchedules], groupIndex) => {
|
||||||
|
const formatted = formatDate(date);
|
||||||
|
return (
|
||||||
|
<motion.div
|
||||||
|
key={date}
|
||||||
|
initial={{ opacity: 0, x: -30 }}
|
||||||
|
animate={{ opacity: 1, x: 0 }}
|
||||||
|
transition={{ delay: groupIndex * 0.1 }}
|
||||||
|
className="relative pl-24 pb-12 last:pb-0"
|
||||||
|
>
|
||||||
|
{/* 타임라인 라인 */}
|
||||||
|
<div className="absolute left-[44px] top-0 bottom-0 w-0.5 bg-gray-200" />
|
||||||
|
|
||||||
|
{/* 날짜 원 */}
|
||||||
|
<div className="absolute left-0 top-0 w-[88px] flex items-start">
|
||||||
|
<div className="w-12 h-12 rounded-full bg-primary text-white flex flex-col items-center justify-center text-sm font-bold">
|
||||||
|
<span className="text-xs">{formatted.month}월</span>
|
||||||
|
<span>{formatted.day}</span>
|
||||||
|
</div>
|
||||||
|
<span className="ml-2 mt-3 text-sm text-gray-400">{formatted.weekday}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 스케줄 카드들 */}
|
||||||
|
<div className="space-y-4">
|
||||||
|
{daySchedules.map((schedule, index) => (
|
||||||
|
<div
|
||||||
|
key={schedule.id}
|
||||||
|
className="bg-white rounded-2xl p-6 shadow-md hover:shadow-lg transition-shadow"
|
||||||
|
>
|
||||||
|
<h3 className="font-bold text-lg mb-3">{schedule.title}</h3>
|
||||||
|
|
||||||
|
<div className="flex flex-wrap gap-4 text-sm text-gray-500">
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<Clock size={14} className="text-primary" />
|
||||||
|
<span>{schedule.time}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<MapPin size={14} className="text-primary" />
|
||||||
|
<span>{schedule.platform}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<Users size={14} className="text-primary" />
|
||||||
|
<span>{schedule.members.join(', ')}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 빈 스케줄 메시지 (스케줄이 없을 때) */}
|
||||||
|
{Object.keys(groupedSchedules).length === 0 && (
|
||||||
|
<div className="text-center py-20">
|
||||||
|
<Calendar size={64} className="mx-auto text-gray-300 mb-4" />
|
||||||
|
<p className="text-gray-500">예정된 스케줄이 없습니다.</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 안내 */}
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 30 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ delay: 0.5 }}
|
||||||
|
className="mt-12 bg-gray-50 rounded-2xl p-6 text-center text-sm text-gray-500"
|
||||||
|
>
|
||||||
|
<p>스케줄은 DC Inside 갤러리에서 자동으로 수집됩니다.</p>
|
||||||
|
<p className="mt-1">일정은 변경될 수 있으니 공식 채널을 확인해 주세요.</p>
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Schedule;
|
||||||
23
frontend/tailwind.config.js
Normal file
23
frontend/tailwind.config.js
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
/** @type {import('tailwindcss').Config} */
|
||||||
|
export default {
|
||||||
|
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
colors: {
|
||||||
|
// 프로미스나인 팬덤 컬러
|
||||||
|
primary: {
|
||||||
|
DEFAULT: "#548360",
|
||||||
|
dark: "#456E50",
|
||||||
|
light: "#6A9A75",
|
||||||
|
},
|
||||||
|
// 보조 컬러
|
||||||
|
secondary: "#F5F5F5",
|
||||||
|
accent: "#FFD700",
|
||||||
|
},
|
||||||
|
fontFamily: {
|
||||||
|
sans: ['"Noto Sans KR"', "sans-serif"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: [],
|
||||||
|
};
|
||||||
10
frontend/vite.config.js
Normal file
10
frontend/vite.config.js
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { defineConfig } from "vite";
|
||||||
|
import react from "@vitejs/plugin-react";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [react()],
|
||||||
|
server: {
|
||||||
|
host: true,
|
||||||
|
port: 5173,
|
||||||
|
},
|
||||||
|
});
|
||||||
Loading…
Add table
Reference in a new issue