2026-01-21 17:17:56 +09:00
|
|
|
import { useState } from "react";
|
2026-01-21 17:04:18 +09:00
|
|
|
import { BrowserRouter, Routes, Route } from "react-router-dom";
|
|
|
|
|
import { isMobile } from "react-device-detect";
|
2026-01-21 17:17:56 +09:00
|
|
|
import { useQuery } from "@tanstack/react-query";
|
2026-01-21 17:11:00 +09:00
|
|
|
import { cn, getTodayKST, formatFullDate } from "@/utils";
|
2026-01-21 17:17:56 +09:00
|
|
|
import { useAuthStore, useUIStore } from "@/stores";
|
|
|
|
|
import { memberApi, scheduleApi } from "@/api";
|
2026-01-21 17:04:18 +09:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 프로미스나인 팬사이트 메인 앱
|
|
|
|
|
*
|
2026-01-21 17:17:56 +09:00
|
|
|
* Phase 4: API 계층 완료
|
|
|
|
|
* - api/client.js: 기본 fetch 클라이언트 (공개/인증)
|
|
|
|
|
* - api/schedules.js: 스케줄 API
|
|
|
|
|
* - api/albums.js: 앨범 API
|
|
|
|
|
* - api/members.js: 멤버 API
|
|
|
|
|
* - api/auth.js: 인증 API
|
2026-01-21 17:04:18 +09:00
|
|
|
*/
|
|
|
|
|
function App() {
|
2026-01-21 17:07:56 +09:00
|
|
|
const today = getTodayKST();
|
2026-01-21 17:17:56 +09:00
|
|
|
const { isAuthenticated } = useAuthStore();
|
2026-01-21 17:11:00 +09:00
|
|
|
const { showSuccess, showError, toasts } = useUIStore();
|
2026-01-21 17:17:56 +09:00
|
|
|
const [apiTest, setApiTest] = useState(null);
|
2026-01-21 17:11:00 +09:00
|
|
|
|
2026-01-21 17:17:56 +09:00
|
|
|
// React Query로 멤버 데이터 조회
|
|
|
|
|
const { data: members, isLoading: membersLoading } = useQuery({
|
|
|
|
|
queryKey: ["members"],
|
|
|
|
|
queryFn: memberApi.getMembers,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// React Query로 카테고리 데이터 조회
|
|
|
|
|
const { data: categories, isLoading: categoriesLoading } = useQuery({
|
|
|
|
|
queryKey: ["categories"],
|
|
|
|
|
queryFn: scheduleApi.getCategories,
|
|
|
|
|
});
|
2026-01-21 17:11:00 +09:00
|
|
|
|
2026-01-21 17:17:56 +09:00
|
|
|
const handleTestApi = async () => {
|
|
|
|
|
try {
|
|
|
|
|
const data = await memberApi.getMembers();
|
|
|
|
|
setApiTest(`멤버 ${data.length}명 조회 성공!`);
|
|
|
|
|
showSuccess("API 호출 성공!");
|
|
|
|
|
} catch (error) {
|
|
|
|
|
setApiTest(`에러: ${error.message}`);
|
|
|
|
|
showError("API 호출 실패");
|
|
|
|
|
}
|
2026-01-21 17:11:00 +09:00
|
|
|
};
|
2026-01-21 17:07:56 +09:00
|
|
|
|
2026-01-21 17:04:18 +09:00
|
|
|
return (
|
|
|
|
|
<BrowserRouter>
|
|
|
|
|
<Routes>
|
|
|
|
|
<Route
|
|
|
|
|
path="/"
|
|
|
|
|
element={
|
2026-01-21 17:17:56 +09:00
|
|
|
<div className="min-h-screen flex items-center justify-center bg-gray-50 p-4">
|
|
|
|
|
<div className="text-center space-y-4 max-w-md w-full">
|
2026-01-21 17:04:18 +09:00
|
|
|
<h1 className="text-2xl font-bold text-primary mb-2">
|
|
|
|
|
fromis_9 Frontend Refactoring
|
|
|
|
|
</h1>
|
2026-01-21 17:17:56 +09:00
|
|
|
<p className="text-gray-600">Phase 4 완료 - API 계층</p>
|
2026-01-21 17:07:56 +09:00
|
|
|
<p className={cn("text-sm", isMobile ? "text-blue-500" : "text-green-500")}>
|
2026-01-21 17:04:18 +09:00
|
|
|
디바이스: {isMobile ? "모바일" : "PC"}
|
|
|
|
|
</p>
|
2026-01-21 17:07:56 +09:00
|
|
|
|
2026-01-21 17:11:00 +09:00
|
|
|
<div className="mt-6 p-4 bg-white rounded-lg shadow text-left text-sm space-y-3">
|
|
|
|
|
<p><strong>오늘:</strong> {formatFullDate(today)}</p>
|
2026-01-21 17:17:56 +09:00
|
|
|
<p><strong>인증:</strong> {isAuthenticated ? "✅" : "❌"}</p>
|
2026-01-21 17:11:00 +09:00
|
|
|
|
|
|
|
|
<div className="border-t pt-3">
|
2026-01-21 17:17:56 +09:00
|
|
|
<p className="font-semibold mb-2">API 테스트 (React Query)</p>
|
2026-01-21 17:11:00 +09:00
|
|
|
|
2026-01-21 17:17:56 +09:00
|
|
|
<div className="space-y-2">
|
|
|
|
|
<p>
|
|
|
|
|
<strong>멤버:</strong>{" "}
|
|
|
|
|
{membersLoading ? "로딩 중..." : `${members?.length || 0}명`}
|
|
|
|
|
</p>
|
|
|
|
|
{members && (
|
|
|
|
|
<div className="flex flex-wrap gap-1">
|
|
|
|
|
{members.map((m) => (
|
|
|
|
|
<span key={m.id} className="px-2 py-0.5 bg-gray-100 rounded text-xs">
|
|
|
|
|
{m.name}
|
|
|
|
|
</span>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
2026-01-21 17:11:00 +09:00
|
|
|
</div>
|
2026-01-21 17:17:56 +09:00
|
|
|
|
|
|
|
|
<div className="space-y-2 mt-3">
|
|
|
|
|
<p>
|
|
|
|
|
<strong>카테고리:</strong>{" "}
|
|
|
|
|
{categoriesLoading ? "로딩 중..." : `${categories?.length || 0}개`}
|
|
|
|
|
</p>
|
|
|
|
|
{categories && (
|
|
|
|
|
<div className="flex flex-wrap gap-1">
|
|
|
|
|
{categories.map((c) => (
|
|
|
|
|
<span
|
|
|
|
|
key={c.id}
|
|
|
|
|
className="px-2 py-0.5 rounded text-xs text-white"
|
|
|
|
|
style={{ backgroundColor: c.color }}
|
|
|
|
|
>
|
|
|
|
|
{c.name}
|
|
|
|
|
</span>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
2026-01-21 17:11:00 +09:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="border-t pt-3">
|
2026-01-21 17:17:56 +09:00
|
|
|
<p className="font-semibold mb-2">직접 API 호출 테스트</p>
|
|
|
|
|
<button
|
|
|
|
|
onClick={handleTestApi}
|
|
|
|
|
className="px-3 py-1 bg-primary text-white rounded text-xs"
|
|
|
|
|
>
|
|
|
|
|
멤버 API 호출
|
|
|
|
|
</button>
|
|
|
|
|
{apiTest && <p className="mt-2 text-xs text-gray-600">{apiTest}</p>}
|
2026-01-21 17:11:00 +09:00
|
|
|
</div>
|
2026-01-21 17:07:56 +09:00
|
|
|
</div>
|
2026-01-21 17:04:18 +09:00
|
|
|
</div>
|
2026-01-21 17:11:00 +09:00
|
|
|
|
|
|
|
|
{/* 토스트 표시 */}
|
|
|
|
|
<div className="fixed bottom-4 right-4 space-y-2">
|
|
|
|
|
{toasts.map((toast) => (
|
|
|
|
|
<div
|
|
|
|
|
key={toast.id}
|
|
|
|
|
className={cn(
|
|
|
|
|
"px-4 py-2 rounded shadow-lg text-white text-sm",
|
|
|
|
|
toast.type === "success" && "bg-green-500",
|
|
|
|
|
toast.type === "error" && "bg-red-500",
|
|
|
|
|
toast.type === "warning" && "bg-yellow-500",
|
|
|
|
|
toast.type === "info" && "bg-blue-500"
|
|
|
|
|
)}
|
|
|
|
|
>
|
|
|
|
|
{toast.message}
|
|
|
|
|
</div>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
2026-01-21 17:04:18 +09:00
|
|
|
</div>
|
|
|
|
|
}
|
|
|
|
|
/>
|
|
|
|
|
</Routes>
|
|
|
|
|
</BrowserRouter>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default App;
|