2026-01-21 17:04:18 +09:00
|
|
|
import { BrowserRouter, Routes, Route } from "react-router-dom";
|
2026-01-21 17:11:00 +09:00
|
|
|
import { cn, getTodayKST, formatFullDate } from "@/utils";
|
2026-01-21 17:39:48 +09:00
|
|
|
import { useUIStore } from "@/stores";
|
|
|
|
|
import { useIsMobile, useCategories, useScheduleData } from "@/hooks";
|
|
|
|
|
import { ErrorBoundary, Loading, ToastContainer, ScheduleCard } from "@/components";
|
2026-01-21 17:04:18 +09:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 프로미스나인 팬사이트 메인 앱
|
|
|
|
|
*
|
2026-01-21 17:39:48 +09:00
|
|
|
* Phase 6: 공통 컴포넌트 완료
|
|
|
|
|
* - ErrorBoundary: 에러 경계
|
|
|
|
|
* - Loading, FullPageLoading, InlineLoading: 로딩 스피너
|
|
|
|
|
* - Toast, ToastContainer: 토스트 알림
|
|
|
|
|
* - Lightbox: 이미지 라이트박스
|
|
|
|
|
* - ScheduleCard: 스케줄 카드 (list/card/compact)
|
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:22:38 +09:00
|
|
|
const isMobile = useIsMobile();
|
2026-01-21 17:39:48 +09:00
|
|
|
const { showSuccess, showError } = useUIStore();
|
2026-01-21 17:22:38 +09:00
|
|
|
|
|
|
|
|
// 커스텀 훅 사용
|
|
|
|
|
const { data: categories, isLoading: categoriesLoading } = useCategories();
|
2026-01-21 17:39:48 +09:00
|
|
|
const currentDate = new Date();
|
|
|
|
|
const { data: schedules, isLoading: schedulesLoading } = useScheduleData(
|
|
|
|
|
currentDate.getFullYear(),
|
|
|
|
|
currentDate.getMonth() + 1
|
|
|
|
|
);
|
2026-01-21 17:17:56 +09:00
|
|
|
|
2026-01-21 17:04:18 +09:00
|
|
|
return (
|
|
|
|
|
<BrowserRouter>
|
2026-01-21 17:39:48 +09:00
|
|
|
<ErrorBoundary>
|
|
|
|
|
<Routes>
|
|
|
|
|
<Route
|
|
|
|
|
path="/"
|
|
|
|
|
element={
|
|
|
|
|
<div className="min-h-screen bg-gray-50 p-4">
|
|
|
|
|
<div className="max-w-2xl mx-auto space-y-4">
|
|
|
|
|
<div className="text-center">
|
|
|
|
|
<h1 className="text-2xl font-bold text-primary mb-2">
|
|
|
|
|
fromis_9 Frontend Refactoring
|
|
|
|
|
</h1>
|
|
|
|
|
<p className="text-gray-600">Phase 6 완료 - 공통 컴포넌트</p>
|
|
|
|
|
<p className={cn("text-sm", isMobile ? "text-blue-500" : "text-green-500")}>
|
|
|
|
|
디바이스: {isMobile ? "모바일" : "PC"}
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
2026-01-21 17:07:56 +09:00
|
|
|
|
2026-01-21 17:39:48 +09:00
|
|
|
<div className="p-4 bg-white rounded-lg shadow text-sm space-y-3">
|
|
|
|
|
<p><strong>오늘:</strong> {formatFullDate(today)}</p>
|
2026-01-21 17:11:00 +09:00
|
|
|
|
2026-01-21 17:39:48 +09:00
|
|
|
<div className="border-t pt-3">
|
|
|
|
|
<p className="font-semibold mb-2">카테고리 ({categories?.length || 0}개)</p>
|
|
|
|
|
{categoriesLoading ? (
|
|
|
|
|
<Loading size="sm" />
|
|
|
|
|
) : (
|
|
|
|
|
<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>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="border-t pt-3">
|
|
|
|
|
<p className="font-semibold mb-2">토스트 테스트</p>
|
|
|
|
|
<div className="flex gap-2">
|
|
|
|
|
<button
|
|
|
|
|
onClick={() => showSuccess("성공 메시지!")}
|
|
|
|
|
className="px-3 py-1 bg-emerald-500 text-white rounded text-xs"
|
|
|
|
|
>
|
|
|
|
|
성공
|
|
|
|
|
</button>
|
|
|
|
|
<button
|
|
|
|
|
onClick={() => showError("에러 메시지!")}
|
|
|
|
|
className="px-3 py-1 bg-red-500 text-white rounded text-xs"
|
|
|
|
|
>
|
|
|
|
|
에러
|
|
|
|
|
</button>
|
2026-01-21 17:22:38 +09:00
|
|
|
</div>
|
2026-01-21 17:39:48 +09:00
|
|
|
</div>
|
2026-01-21 17:22:38 +09:00
|
|
|
</div>
|
2026-01-21 17:17:56 +09:00
|
|
|
|
2026-01-21 17:39:48 +09:00
|
|
|
{/* ScheduleCard 컴포넌트 테스트 */}
|
|
|
|
|
{schedulesLoading ? (
|
|
|
|
|
<div className="p-4 bg-white rounded-lg shadow">
|
|
|
|
|
<Loading size="sm" text="스케줄 로딩 중..." />
|
2026-01-21 17:11:00 +09:00
|
|
|
</div>
|
2026-01-21 17:39:48 +09:00
|
|
|
) : schedules?.length > 0 ? (
|
|
|
|
|
<>
|
|
|
|
|
{/* Public Variant (공개 페이지용) */}
|
|
|
|
|
<div className="p-4 bg-white rounded-lg shadow">
|
|
|
|
|
<p className="font-semibold mb-3 text-sm">variant="public" (공개 페이지용)</p>
|
|
|
|
|
<div className="space-y-3">
|
|
|
|
|
{schedules.slice(0, 2).map((schedule) => (
|
|
|
|
|
<ScheduleCard
|
|
|
|
|
key={schedule.id}
|
|
|
|
|
schedule={schedule}
|
|
|
|
|
variant="public"
|
|
|
|
|
onClick={() => showSuccess(`${schedule.title} 클릭`)}
|
|
|
|
|
/>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2026-01-21 17:11:00 +09:00
|
|
|
|
2026-01-21 17:39:48 +09:00
|
|
|
{/* Admin Variant (관리자 페이지용) */}
|
|
|
|
|
<div className="p-4 bg-white rounded-lg shadow">
|
|
|
|
|
<p className="font-semibold mb-3 text-sm">variant="admin" (관리자 페이지용)</p>
|
|
|
|
|
<div className="divide-y">
|
|
|
|
|
{schedules.slice(0, 2).map((schedule) => (
|
|
|
|
|
<ScheduleCard
|
|
|
|
|
key={schedule.id}
|
|
|
|
|
schedule={schedule}
|
|
|
|
|
variant="admin"
|
|
|
|
|
onClick={() => showSuccess(`${schedule.title} 클릭`)}
|
|
|
|
|
onEdit={(s) => showSuccess(`${s.title} 수정`)}
|
|
|
|
|
onDelete={(s) => showError(`${s.title} 삭제`)}
|
|
|
|
|
/>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
2026-01-21 17:22:38 +09:00
|
|
|
</div>
|
2026-01-21 17:39:48 +09:00
|
|
|
</>
|
|
|
|
|
) : (
|
|
|
|
|
<div className="p-4 bg-white rounded-lg shadow">
|
|
|
|
|
<p className="text-gray-500 text-sm">이번 달 스케줄이 없습니다.</p>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
2026-01-21 17:07:56 +09:00
|
|
|
</div>
|
2026-01-21 17:11:00 +09:00
|
|
|
|
2026-01-21 17:39:48 +09:00
|
|
|
{/* 토스트 컨테이너 */}
|
|
|
|
|
<ToastContainer />
|
2026-01-21 17:11:00 +09:00
|
|
|
</div>
|
2026-01-21 17:39:48 +09:00
|
|
|
}
|
|
|
|
|
/>
|
|
|
|
|
</Routes>
|
|
|
|
|
</ErrorBoundary>
|
2026-01-21 17:04:18 +09:00
|
|
|
</BrowserRouter>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default App;
|