import React, { useEffect, useState, Component, ErrorInfo, ReactNode, Suspense, } from "react"; import { QueryClientProvider } from "@tanstack/react-query"; import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; import { logger } from "@/utils/logger"; import { Routes, Route, useLocation } from "react-router-dom"; import { initializeStores } from "./stores/storeInitializer"; import { queryClient, isDevMode } from "./lib/query/queryClient"; import { Toaster } from "./components/ui/toaster"; import { SentryErrorBoundary, captureError, trackPageView } from "./lib/sentry"; import { initializePWA } from "./utils/pwa"; import { EnvTest } from "./components/debug/EnvTest"; // import { setupChunkErrorHandler, resetRetryCount } from "./utils/chunkErrorHandler"; // 임시 비활성화 import { createLazyComponent } from "./utils/lazyWithRetry"; import { isChunkLoadError, isClerkChunkError, handleChunkLoadError, } from "./utils/chunkErrorProtection"; import { ClerkProvider, ClerkDebugInfo, } from "./components/providers/ClerkProvider"; import { BudgetProvider } from "./contexts/budget/BudgetContext"; // 페이지 컴포넌트들을 개선된 레이지 로딩으로 변경 (ChunkLoadError 재시도 포함) const Index = createLazyComponent(() => import("./pages/Index")); const Login = createLazyComponent(() => import("./pages/Login")); const Register = createLazyComponent(() => import("./pages/Register")); const Settings = createLazyComponent(() => import("./pages/Settings")); const Transactions = createLazyComponent(() => import("./pages/Transactions")); const Analytics = createLazyComponent(() => import("./pages/Analytics")); const ProfileManagement = createLazyComponent( () => import("./pages/ProfileManagement") ); const NotFound = createLazyComponent(() => import("./pages/NotFound")); const PaymentMethods = createLazyComponent( () => import("./pages/PaymentMethods") ); const HelpSupport = createLazyComponent(() => import("./pages/HelpSupport")); const SecurityPrivacySettings = createLazyComponent( () => import("./pages/SecurityPrivacySettings") ); const NotificationSettings = createLazyComponent( () => import("./pages/NotificationSettings") ); const ForgotPassword = createLazyComponent( () => import("./pages/ForgotPassword") ); const PWADebugPage = createLazyComponent(() => import("./pages/PWADebugPage")); // 중요하지 않은 컴포넌트들도 개선된 레이지 로딩 적용 const BackgroundSync = createLazyComponent( () => import("./components/sync/BackgroundSync") ); const QueryCacheManager = createLazyComponent( () => import("./components/query/QueryCacheManager") ); const OfflineManager = createLazyComponent( () => import("./components/offline/OfflineManager") ); const SentryTestButton = createLazyComponent( () => import("./components/SentryTestButton") ); const ClerkDebugControl = createLazyComponent( () => import("./components/debug/ClerkDebugControl") ); // Clerk 인증 컴포넌트 (다시 활성화) const SignIn = createLazyComponent(() => import("./components/auth/SignIn").then((module) => ({ default: module.SignIn, })) ); const SignUp = createLazyComponent(() => import("./components/auth/SignUp").then((module) => ({ default: module.SignUp, })) ); // 간단한 오류 경계 컴포넌트 구현 interface ErrorBoundaryProps { children: ReactNode; fallback?: ReactNode; } interface ErrorBoundaryState { hasError: boolean; error: Error | null; } class ErrorBoundary extends Component { constructor(props: ErrorBoundaryProps) { super(props); this.state = { hasError: false, error: null }; } static getDerivedStateFromError(error: Error): ErrorBoundaryState { return { hasError: true, error }; } componentDidCatch(error: Error, errorInfo: ErrorInfo): void { logger.error("애플리케이션 오류:", { error: error.message, componentStack: errorInfo.componentStack, }); // ChunkLoadError 처리 if (isChunkLoadError(error)) { if (isClerkChunkError(error)) { logger.warn("Error Boundary에서 Clerk 청크 오류 감지. 자동 복구 시도"); // Clerk 자동 비활성화 sessionStorage.setItem("disableClerk", "true"); // 3초 후 새로고침 setTimeout(() => { const url = new URL(window.location.href); url.searchParams.set("noClerk", "true"); url.searchParams.set("_t", Date.now().toString()); window.location.href = url.toString(); }, 3000); return; } else { // 일반 청크 오류 처리 handleChunkLoadError(error); return; } } // Sentry에 에러 리포팅 (청크 오류가 아닌 경우만) captureError(error, { errorInfo }); } render(): ReactNode { if (this.state.hasError) { // ChunkLoadError인 경우 특별한 UI 표시 if (this.state.error && isChunkLoadError(this.state.error)) { const isClerkError = isClerkChunkError(this.state.error); return (
{isClerkError ? "🔧" : "⚠️"}

{isClerkError ? "Clerk 로딩 오류" : "앱 로딩 오류"}

{isClerkError ? "Supabase 인증으로 자동 전환 중입니다. 잠시만 기다려주세요..." : "앱을 복구하고 있습니다. 잠시만 기다려주세요..."}

{!isClerkError && ( )}
); } // 일반 오류 처리 return ( this.props.fallback || (

앱 로딩 중 오류가 발생했습니다

잠시 후 다시 시도해주세요.

) ); } return this.props.children; } } // 로딩 상태 표시 컴포넌트 const LoadingScreen: React.FC = () => (

Zellyy Finance

앱을 로딩하고 있습니다...

환경: {import.meta.env.MODE} | Clerk:{" "} {import.meta.env.VITE_CLERK_PUBLISHABLE_KEY ? "✓" : "✗"} | Supabase:{" "} {import.meta.env.VITE_SUPABASE_URL ? "✓" : "✗"}
); // 페이지 로딩 컴포넌트 (코드 스플리팅용) const PageLoadingSpinner: React.FC = () => (

페이지를 로딩하고 있습니다...

); // 페이지 전환 추적 컴포넌트 const PageTracker = () => { const location = useLocation(); useEffect(() => { // 페이지 이름 매핑 const getPageName = (pathname: string) => { const pageMap: Record = { "/": "홈", "/login": "로그인", "/register": "회원가입", "/forgot-password": "비밀번호 찾기", "/analytics": "분석", "/transactions": "거래내역", "/settings": "설정", "/profile": "프로필", "/help": "도움말", }; return pageMap[pathname] || pathname; }; const pageName = getPageName(location.pathname); trackPageView(pageName, location.pathname + location.search); }, [location]); return null; }; // 오류 화면 컴포넌트 const ErrorScreen: React.FC<{ error: Error | null; retry: () => void }> = ({ error, retry, }) => (
⚠️

애플리케이션 오류

{error?.message || "애플리케이션 로딩 중 오류가 발생했습니다."}

); // 기본 레이아웃 컴포넌트 - 인증 없이도 표시 가능 const BasicLayout: React.FC<{ children: React.ReactNode }> = ({ children }) => (
{children}
); function App() { const [appState, setAppState] = useState<"loading" | "error" | "ready">( "loading" ); const [error, setError] = useState(null); useEffect(() => { document.title = "Zellyy Finance"; // eslint-disable-next-line no-console console.log("🚀 App useEffect 실행됨"); // 프로덕션 환경에서 간단한 초기화 테스트 const simpleInitialize = async () => { try { // eslint-disable-next-line no-console console.log("🔧 간단한 초기화 시작"); // eslint-disable-next-line no-console console.log("환경:", import.meta.env.MODE); // eslint-disable-next-line no-console console.log("모든 환경변수:", import.meta.env); // eslint-disable-next-line no-console console.log( "VITE_CLERK_PUBLISHABLE_KEY:", import.meta.env.VITE_CLERK_PUBLISHABLE_KEY || "없음" ); // eslint-disable-next-line no-console console.log( "VITE_SUPABASE_URL:", import.meta.env.VITE_SUPABASE_URL || "없음" ); // 매우 간단한 초기화만 수행 await new Promise((resolve) => setTimeout(resolve, 100)); // eslint-disable-next-line no-console console.log("✅ 간단한 초기화 완료 - ready 상태로 변경"); setAppState("ready"); } catch (error) { console.error("❌ 간단한 초기화 실패:", error); setError(error instanceof Error ? error : new Error("초기화 실패")); setAppState("error"); } }; simpleInitialize(); // 컴포넌트 언마운트 시 정리 return () => { // eslint-disable-next-line no-console console.log("🧹 App 컴포넌트 정리"); }; }, []); // 재시도 기능 const handleRetry = async () => { setAppState("loading"); setError(null); try { // 재시도 시 PWA 및 스토어 재초기화 await initializePWA(); await initializeStores(); setAppState("ready"); } catch (error) { logger.error( "재시도 실패", error instanceof Error ? { message: error.message, stack: error.stack } : String(error) ); setError(error instanceof Error ? error : new Error("재시도 실패")); setAppState("error"); } }; // 로딩 상태 표시 if (appState === "loading") { return ( } > ); } // 오류 상태 표시 if (appState === "error") { return ; } return ( } showDialog={false} > }> } /> {/* Clerk 라우트 다시 활성화 */} } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> {/* React Query 캐시 관리 */} {/* 오프라인 상태 관리 */} {/* 백그라운드 자동 동기화 - 성능 최적화로 30초 간격으로 조정 */} {/* 개발환경에서 Sentry 테스트 버튼 */} {/* 개발환경에서 Clerk 상태 디버깅 */} {/* 개발환경에서 환경 변수 테스트 */} {isDevMode && } {/* Clerk 디버그 및 제어 */} {isDevMode && } ); } export default App;