2026-01-21 18:07:55 +09:00
|
|
|
import { BrowserRouter, Routes, Route } from "react-router-dom";
|
2026-01-21 20:11:21 +09:00
|
|
|
import { cn, getTodayKST, formatFullDate } from "@/utils";
|
|
|
|
|
import { useAuthStore, useUIStore } from "@/stores";
|
|
|
|
|
import { useIsMobile, useCategories, useCalendar } from "@/hooks";
|
|
|
|
|
import { memberApi } from "@/api";
|
|
|
|
|
import { useQuery } from "@tanstack/react-query";
|
2026-01-21 17:54:27 +09:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 프로미스나인 팬사이트 메인 앱
|
|
|
|
|
*
|
2026-01-21 20:11:21 +09:00
|
|
|
* Phase 5: 커스텀 훅 완료
|
|
|
|
|
* - useMediaQuery, useIsMobile, useIsDesktop
|
|
|
|
|
* - useScheduleData, useScheduleDetail, useCategories
|
|
|
|
|
* - useScheduleSearch (무한 스크롤)
|
|
|
|
|
* - useScheduleFiltering, useCategoryCounts
|
|
|
|
|
* - useCalendar
|
|
|
|
|
* - useAdminAuth
|
2026-01-21 17:54:27 +09:00
|
|
|
*/
|
|
|
|
|
function App() {
|
2026-01-21 20:11:21 +09:00
|
|
|
const today = getTodayKST();
|
|
|
|
|
const isMobile = useIsMobile();
|
|
|
|
|
const { isAuthenticated } = useAuthStore();
|
|
|
|
|
const { showSuccess, toasts } = useUIStore();
|
|
|
|
|
|
|
|
|
|
// 커스텀 훅 사용
|
|
|
|
|
const { data: categories, isLoading: categoriesLoading } = useCategories();
|
|
|
|
|
const calendar = useCalendar();
|
|
|
|
|
|
|
|
|
|
// 멤버 데이터 (기존 방식 유지)
|
|
|
|
|
const { data: members, isLoading: membersLoading } = useQuery({
|
|
|
|
|
queryKey: ["members"],
|
|
|
|
|
queryFn: memberApi.getMembers,
|
|
|
|
|
});
|
|
|
|
|
|
2026-01-21 17:04:18 +09:00
|
|
|
return (
|
|
|
|
|
<BrowserRouter>
|
2026-01-21 20:11:21 +09:00
|
|
|
<Routes>
|
|
|
|
|
<Route
|
|
|
|
|
path="/"
|
|
|
|
|
element={
|
|
|
|
|
<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">
|
|
|
|
|
<h1 className="text-2xl font-bold text-primary mb-2">
|
|
|
|
|
fromis_9 Frontend Refactoring
|
|
|
|
|
</h1>
|
|
|
|
|
<p className="text-gray-600">Phase 5 완료 - 커스텀 훅</p>
|
|
|
|
|
<p className={cn("text-sm", isMobile ? "text-blue-500" : "text-green-500")}>
|
|
|
|
|
디바이스: {isMobile ? "모바일" : "PC"} (useIsMobile 훅)
|
|
|
|
|
</p>
|
|
|
|
|
|
|
|
|
|
<div className="mt-6 p-4 bg-white rounded-lg shadow text-left text-sm space-y-3">
|
|
|
|
|
<p><strong>오늘:</strong> {formatFullDate(today)}</p>
|
|
|
|
|
<p><strong>인증:</strong> {isAuthenticated ? "✅" : "❌"}</p>
|
2026-01-21 17:11:00 +09:00
|
|
|
|
2026-01-21 20:11:21 +09:00
|
|
|
<div className="border-t pt-3">
|
|
|
|
|
<p className="font-semibold mb-2">useCategories 훅</p>
|
|
|
|
|
<p>
|
|
|
|
|
{categoriesLoading ? "로딩 중..." : `${categories?.length || 0}개 카테고리`}
|
|
|
|
|
</p>
|
|
|
|
|
{categories && (
|
|
|
|
|
<div className="flex flex-wrap gap-1 mt-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>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
2026-01-21 17:54:27 +09:00
|
|
|
|
2026-01-21 20:11:21 +09:00
|
|
|
<div className="border-t pt-3">
|
|
|
|
|
<p className="font-semibold mb-2">useCalendar 훅</p>
|
|
|
|
|
<p><strong>현재:</strong> {calendar.year}년 {calendar.monthName}</p>
|
|
|
|
|
<p><strong>선택:</strong> {calendar.selectedDate}</p>
|
|
|
|
|
<div className="flex gap-2 mt-2">
|
|
|
|
|
<button
|
|
|
|
|
onClick={calendar.goToPrevMonth}
|
|
|
|
|
disabled={!calendar.canGoPrevMonth}
|
|
|
|
|
className={cn(
|
|
|
|
|
"px-3 py-1 rounded text-xs",
|
|
|
|
|
calendar.canGoPrevMonth ? "bg-primary text-white" : "bg-gray-200 text-gray-400"
|
|
|
|
|
)}
|
|
|
|
|
>
|
|
|
|
|
이전
|
|
|
|
|
</button>
|
|
|
|
|
<button
|
|
|
|
|
onClick={calendar.goToToday}
|
|
|
|
|
className="px-3 py-1 bg-gray-500 text-white rounded text-xs"
|
|
|
|
|
>
|
|
|
|
|
오늘
|
|
|
|
|
</button>
|
|
|
|
|
<button
|
|
|
|
|
onClick={calendar.goToNextMonth}
|
|
|
|
|
className="px-3 py-1 bg-primary text-white rounded text-xs"
|
|
|
|
|
>
|
|
|
|
|
다음
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2026-01-21 18:07:55 +09:00
|
|
|
|
2026-01-21 20:11:21 +09:00
|
|
|
<div className="border-t pt-3">
|
|
|
|
|
<p className="font-semibold mb-2">멤버 데이터</p>
|
|
|
|
|
<p>{membersLoading ? "로딩 중..." : `${members?.length || 0}명`}</p>
|
|
|
|
|
{members && (
|
|
|
|
|
<div className="flex flex-wrap gap-1 mt-1">
|
|
|
|
|
{members.map((m) => (
|
|
|
|
|
<span key={m.id} className="px-2 py-0.5 bg-gray-100 rounded text-xs">
|
|
|
|
|
{m.name}
|
|
|
|
|
</span>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2026-01-21 18:07:55 +09:00
|
|
|
|
2026-01-21 20:11:21 +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>
|
|
|
|
|
</div>
|
|
|
|
|
}
|
|
|
|
|
/>
|
|
|
|
|
</Routes>
|
2026-01-21 17:04:18 +09:00
|
|
|
</BrowserRouter>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default App;
|