feat: 멤버 데이터 API 연동
- 백엔드: MariaDB 연결 설정 (lib/db.js) - 백엔드: /api/members 라우트 추가 (routes/members.js) - 프론트엔드: Members 페이지 API 연동 - 프론트엔드: Home 멤버 섹션 API 연동 - 로딩 상태 및 에러 처리 추가
This commit is contained in:
parent
91270c2c8b
commit
6ee8e3598a
5 changed files with 102 additions and 7 deletions
15
backend/lib/db.js
Normal file
15
backend/lib/db.js
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
import mysql from "mysql2/promise";
|
||||||
|
|
||||||
|
// MariaDB 연결 풀 생성
|
||||||
|
const pool = mysql.createPool({
|
||||||
|
host: process.env.DB_HOST || "mariadb",
|
||||||
|
port: parseInt(process.env.DB_PORT) || 3306,
|
||||||
|
user: process.env.DB_USER || "fromis9",
|
||||||
|
password: process.env.DB_PASSWORD,
|
||||||
|
database: process.env.DB_NAME || "fromis9",
|
||||||
|
waitForConnections: true,
|
||||||
|
connectionLimit: 10,
|
||||||
|
queueLimit: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default pool;
|
||||||
35
backend/routes/members.js
Normal file
35
backend/routes/members.js
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
import express from "express";
|
||||||
|
import pool from "../lib/db.js";
|
||||||
|
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
// 전체 멤버 조회
|
||||||
|
router.get("/", async (req, res) => {
|
||||||
|
try {
|
||||||
|
const [rows] = await pool.query(
|
||||||
|
"SELECT id, name, name_en, birth_date, position, image_url, instagram FROM members ORDER BY id"
|
||||||
|
);
|
||||||
|
res.json(rows);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("멤버 조회 오류:", error);
|
||||||
|
res.status(500).json({ error: "멤버 정보를 가져오는데 실패했습니다." });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 특정 멤버 조회
|
||||||
|
router.get("/:id", async (req, res) => {
|
||||||
|
try {
|
||||||
|
const [rows] = await pool.query("SELECT * FROM members WHERE id = ?", [
|
||||||
|
req.params.id,
|
||||||
|
]);
|
||||||
|
if (rows.length === 0) {
|
||||||
|
return res.status(404).json({ error: "멤버를 찾을 수 없습니다." });
|
||||||
|
}
|
||||||
|
res.json(rows[0]);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("멤버 조회 오류:", error);
|
||||||
|
res.status(500).json({ error: "멤버 정보를 가져오는데 실패했습니다." });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default router;
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import express from "express";
|
import express from "express";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import { fileURLToPath } from "url";
|
import { fileURLToPath } from "url";
|
||||||
|
import membersRouter from "./routes/members.js";
|
||||||
|
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
const __dirname = path.dirname(__filename);
|
const __dirname = path.dirname(__filename);
|
||||||
|
|
@ -14,11 +15,13 @@ app.use(express.json());
|
||||||
// 정적 파일 서빙 (프론트엔드 빌드 결과물)
|
// 정적 파일 서빙 (프론트엔드 빌드 결과물)
|
||||||
app.use(express.static(path.join(__dirname, "dist")));
|
app.use(express.static(path.join(__dirname, "dist")));
|
||||||
|
|
||||||
// API 라우트 (추후 구현)
|
// API 라우트
|
||||||
app.get("/api/health", (req, res) => {
|
app.get("/api/health", (req, res) => {
|
||||||
res.json({ status: "ok", timestamp: new Date().toISOString() });
|
res.json({ status: "ok", timestamp: new Date().toISOString() });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.use("/api/members", membersRouter);
|
||||||
|
|
||||||
// SPA 폴백 - 모든 요청을 index.html로
|
// SPA 폴백 - 모든 요청을 index.html로
|
||||||
app.get("*", (req, res) => {
|
app.get("*", (req, res) => {
|
||||||
res.sendFile(path.join(__dirname, "dist", "index.html"));
|
res.sendFile(path.join(__dirname, "dist", "index.html"));
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,19 @@
|
||||||
|
import { useState, useEffect } from 'react';
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { Calendar, Users, Disc3, ArrowRight } from 'lucide-react';
|
import { Calendar, Users, Disc3, ArrowRight } from 'lucide-react';
|
||||||
import { members, schedules, albums } from '../../data/dummy';
|
import { schedules, albums } from '../../data/dummy';
|
||||||
|
|
||||||
function Home() {
|
function Home() {
|
||||||
|
const [members, setMembers] = useState([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetch('/api/members')
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => setMembers(data))
|
||||||
|
.catch(error => console.error('멤버 데이터 로드 오류:', error));
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{/* 히어로 섹션 */}
|
{/* 히어로 섹션 */}
|
||||||
|
|
@ -92,14 +102,14 @@ function Home() {
|
||||||
>
|
>
|
||||||
<div className="aspect-square bg-gray-100">
|
<div className="aspect-square bg-gray-100">
|
||||||
<img
|
<img
|
||||||
src={member.imageUrl}
|
src={member.image_url}
|
||||||
alt={member.name}
|
alt={member.name}
|
||||||
className="w-full h-full object-cover"
|
className="w-full h-full object-cover"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="p-4 text-center">
|
<div className="p-4 text-center">
|
||||||
<h3 className="font-bold text-lg">{member.name}</h3>
|
<h3 className="font-bold text-lg">{member.name}</h3>
|
||||||
<p className="text-sm text-gray-500">{member.position.split(',')[0]}</p>
|
<p className="text-sm text-gray-500">{member.position?.split(',')[0]}</p>
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
))}
|
))}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,39 @@
|
||||||
|
import { useState, useEffect } from 'react';
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import { Instagram, Calendar } from 'lucide-react';
|
import { Instagram, Calendar } from 'lucide-react';
|
||||||
import { members } from '../../data/dummy';
|
|
||||||
|
|
||||||
function Members() {
|
function Members() {
|
||||||
|
const [members, setMembers] = useState([]);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetch('/api/members')
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
setMembers(data);
|
||||||
|
setLoading(false);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('멤버 데이터 로드 오류:', error);
|
||||||
|
setLoading(false);
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// 날짜 포맷팅 함수
|
||||||
|
const formatDate = (dateStr) => {
|
||||||
|
if (!dateStr) return '';
|
||||||
|
const date = new Date(dateStr);
|
||||||
|
return `${date.getFullYear()}.${String(date.getMonth() + 1).padStart(2, '0')}.${String(date.getDate()).padStart(2, '0')}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<div className="py-16 flex justify-center items-center min-h-[60vh]">
|
||||||
|
<div className="animate-spin rounded-full h-12 w-12 border-4 border-primary border-t-transparent"></div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="py-16">
|
<div className="py-16">
|
||||||
<div className="max-w-7xl mx-auto px-6">
|
<div className="max-w-7xl mx-auto px-6">
|
||||||
|
|
@ -39,7 +70,7 @@ function Members() {
|
||||||
{/* 이미지 */}
|
{/* 이미지 */}
|
||||||
<div className="aspect-[3/4] bg-gray-100 overflow-hidden">
|
<div className="aspect-[3/4] bg-gray-100 overflow-hidden">
|
||||||
<img
|
<img
|
||||||
src={member.imageUrl}
|
src={member.image_url}
|
||||||
alt={member.name}
|
alt={member.name}
|
||||||
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-500"
|
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-500"
|
||||||
/>
|
/>
|
||||||
|
|
@ -52,7 +83,7 @@ function Members() {
|
||||||
|
|
||||||
<div className="flex items-center gap-2 text-sm text-gray-500 mb-4">
|
<div className="flex items-center gap-2 text-sm text-gray-500 mb-4">
|
||||||
<Calendar size={14} />
|
<Calendar size={14} />
|
||||||
<span>{member.birthDate}</span>
|
<span>{formatDate(member.birth_date)}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 인스타그램 링크 */}
|
{/* 인스타그램 링크 */}
|
||||||
|
|
@ -106,3 +137,4 @@ function Members() {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Members;
|
export default Members;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue