fromis_9/frontend-temp/src/App.jsx
caadiq 27c41b0af0 feat(frontend): Phase 5 - 커스텀 훅 구현
- useMediaQuery, useIsMobile, useIsDesktop: 반응형 레이아웃
- useScheduleData, useCategories: 스케줄/카테고리 데이터 조회
- useScheduleSearch: 무한 스크롤 검색
- useScheduleFiltering, useCategoryCounts: 필터링 및 정렬
- useCalendar: 캘린더 로직 (월 이동, 날짜 선택)
- useAdminAuth: 토큰 검증 및 리다이렉트
- utils/schedule.js: 스케줄 유틸리티 함수 추가
- constants: SEARCH_LIMIT, MIN_YEAR, MONTH_NAMES 추가

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-21 17:22:38 +09:00

146 lines
5.7 KiB
JavaScript

import { BrowserRouter, Routes, Route } from "react-router-dom";
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";
/**
* 프로미스나인 팬사이트 메인 앱
*
* Phase 5: 커스텀 훅 완료
* - useMediaQuery, useIsMobile, useIsDesktop
* - useScheduleData, useScheduleDetail, useCategories
* - useScheduleSearch (무한 스크롤)
* - useScheduleFiltering, useCategoryCounts
* - useCalendar
* - useAdminAuth
*/
function App() {
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,
});
return (
<BrowserRouter>
<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>
<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>
<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>
<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>
{/* 토스트 표시 */}
<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>
</BrowserRouter>
);
}
export default App;