import { ANALYTICS_EVENTS, en } from "@snapotter/shared"; import { Component, type ErrorInfo, lazy, type ReactNode, Suspense, useEffect } from "react"; import { BrowserRouter, Navigate, Route, Routes, useLocation } from "react-router-dom"; import { Toaster, toast } from "sonner"; import { ConnectionMonitor } from "./components/common/connection-monitor"; import { KeyboardShortcutProvider } from "./components/common/keyboard-shortcut-provider"; import { MigrationBanner } from "./components/common/migration-banner"; import { RouteAnnouncer } from "./components/common/route-announcer"; import { UsageSurveyOverlay } from "./components/onboarding/usage-survey-overlay"; import { I18nProvider } from "./contexts/i18n-context"; import { useAuth } from "./hooks/use-auth"; import { useMobile } from "./hooks/use-mobile"; import { initAnalytics, isAnalyticsActive, optOut, track } from "./lib/analytics"; import { AUTH_GUARD_UNGATED_PATHS } from "./lib/auth-routes"; import { useAnalyticsStore } from "./stores/analytics-store"; // Lazy-load all pages so each page's JS (and its icons/deps) is only // downloaded when the user navigates there, shrinking the main bundle. const AutomatePage = lazy(() => import("./pages/automate-page").then((m) => ({ default: m.AutomatePage })), ); const ChangePasswordPage = lazy(() => import("./pages/change-password-page").then((m) => ({ default: m.ChangePasswordPage })), ); const FilesPage = lazy(() => import("./pages/files-page").then((m) => ({ default: m.FilesPage }))); const HomePage = lazy(() => import("./pages/home-page").then((m) => ({ default: m.HomePage }))); const LoginPage = lazy(() => import("./pages/login-page").then((m) => ({ default: m.LoginPage }))); const PrivacyPolicyPage = lazy(() => import("./pages/privacy-policy-page").then((m) => ({ default: m.PrivacyPolicyPage })), ); const EditorPage = lazy(() => import("./pages/editor-page").then((m) => ({ default: m.EditorPage })), ); const ToolPage = lazy(() => import("./pages/tool-page").then((m) => ({ default: m.ToolPage }))); const NotFoundPage = lazy(() => import("./pages/not-found-page").then((m) => ({ default: m.NotFoundPage })), ); class ErrorBoundary extends Component< { children: ReactNode }, { hasError: boolean; error: Error | null } > { constructor(props: { children: ReactNode }) { super(props); this.state = { hasError: false, error: null }; } static getDerivedStateFromError(error: Error) { return { hasError: true, error }; } componentDidCatch(error: Error, info: ErrorInfo) { console.error("Uncaught render error:", error, info.componentStack); if (!isAnalyticsActive()) return; // respect the runtime opt-out // Mirror the crash class only (no PII). track() and Sentry are best-effort. track(ANALYTICS_EVENTS.TOOL_CLIENT_ERROR, { error_name: error.name }); import("@sentry/react") .then((Sentry) => { Sentry.captureReactException(error, info); }) .catch(() => {}); } render() { if (this.state.hasError) { return (
{this.state.error?.message || en.common.unexpectedError}
{en.common.loading}