import { lazy, Suspense, type ComponentType } from 'react'; import { Routes, Route, Navigate, useLocation, useParams } from 'react-router'; import { useAuthStore } from './store/auth'; /** * Wrapper around React.lazy that auto-reloads the page when a chunk fails to load * (e.g. after a new deploy with different chunk hashes). */ function lazyWithRetry>(factory: () => Promise<{ default: T }>) { return lazy(() => factory().catch(() => { const key = 'chunk_reload_ts'; const last = Number(sessionStorage.getItem(key) || '0'); if (Date.now() - last > 30_000) { sessionStorage.setItem(key, String(Date.now())); window.location.reload(); } // Re-throw so ErrorBoundary catches it if reload guard prevents loop return factory(); }), ); } import { useBlockingStore } from './store/blocking'; import Layout from './components/layout/Layout'; import PageLoader from './components/common/PageLoader'; import { MaintenanceScreen, ChannelSubscriptionScreen, BlacklistedScreen, AccountDeletedScreen, ServiceUnavailableScreen, } from './components/blocking'; import { ErrorBoundary } from './components/ErrorBoundary'; import { PermissionRoute } from '@/components/auth/PermissionRoute'; import { saveReturnUrl } from './utils/token'; import { useAnalyticsCounters } from './hooks/useAnalyticsCounters'; import { useSiteVerification } from './hooks/useSiteVerification'; // Auth pages - load immediately (small) import Login from './pages/Login'; import TelegramCallback from './pages/TelegramCallback'; import TelegramRedirect from './pages/TelegramRedirect'; import DeepLinkRedirect from './pages/DeepLinkRedirect'; import VerifyEmail from './pages/VerifyEmail'; import ResetPassword from './pages/ResetPassword'; import PublicLegal from './pages/PublicLegal'; import OAuthCallback from './pages/OAuthCallback'; // Dashboard - load eagerly (default route, LCP-critical) import Dashboard from './pages/Dashboard'; // User pages - lazy load const Subscriptions = lazyWithRetry(() => import('./pages/Subscriptions')); const Subscription = lazyWithRetry(() => import('./pages/Subscription')); const SubscriptionPurchase = lazyWithRetry(() => import('./pages/SubscriptionPurchase')); const Balance = lazyWithRetry(() => import('./pages/Balance')); const SavedCards = lazyWithRetry(() => import('./pages/SavedCards')); const Referral = lazyWithRetry(() => import('./pages/Referral')); const Support = lazyWithRetry(() => import('./pages/Support')); const Profile = lazyWithRetry(() => import('./pages/Profile')); const Contests = lazyWithRetry(() => import('./pages/Contests')); const Polls = lazyWithRetry(() => import('./pages/Polls')); const Info = lazyWithRetry(() => import('./pages/Info')); const Wheel = lazyWithRetry(() => import('./pages/Wheel')); const GiftSubscription = lazyWithRetry(() => import('./pages/GiftSubscription')); const GiftResult = lazyWithRetry(() => import('./pages/GiftResult')); const Connection = lazyWithRetry(() => import('./pages/Connection')); const ConnectionQR = lazyWithRetry(() => import('./pages/ConnectionQR')); const QuickPurchase = lazyWithRetry(() => import('./pages/QuickPurchase')); const PurchaseSuccess = lazyWithRetry(() => import('./pages/PurchaseSuccess')); const GiftClaim = lazyWithRetry(() => import('./pages/GiftClaim')); const RenewSubscription = lazyWithRetry(() => import('./pages/RenewSubscription')); const AutoLogin = lazyWithRetry(() => import('./pages/AutoLogin')); const TopUpMethodSelect = lazyWithRetry(() => import('./pages/TopUpMethodSelect')); const TopUpAmount = lazyWithRetry(() => import('./pages/TopUpAmount')); const TopUpResult = lazyWithRetry(() => import('./pages/TopUpResult')); const ConnectedAccounts = lazyWithRetry(() => import('./pages/ConnectedAccounts')); const LinkTelegramCallback = lazyWithRetry(() => import('./pages/LinkTelegramCallback')); const MergeAccounts = lazyWithRetry(() => import('./pages/MergeAccounts')); // Admin pages - lazy load (only for admins) const AdminPanel = lazyWithRetry(() => import('./pages/AdminPanel')); const AdminTickets = lazyWithRetry(() => import('./pages/AdminTickets')); const AdminTicketSettings = lazyWithRetry(() => import('./pages/AdminTicketSettings')); const AdminSettings = lazyWithRetry(() => import('./pages/AdminSettings')); const AdminApps = lazyWithRetry(() => import('./pages/AdminApps')); const AdminWheel = lazyWithRetry(() => import('./pages/AdminWheel')); const AdminTariffs = lazyWithRetry(() => import('./pages/AdminTariffs')); const AdminTariffCreate = lazyWithRetry(() => import('./pages/AdminTariffCreate')); const AdminServers = lazyWithRetry(() => import('./pages/AdminServers')); const AdminServerEdit = lazyWithRetry(() => import('./pages/AdminServerEdit')); const AdminDashboard = lazyWithRetry(() => import('./pages/AdminDashboard')); const AdminBanSystem = lazyWithRetry(() => import('./pages/AdminBanSystem')); const AdminBroadcasts = lazyWithRetry(() => import('./pages/AdminBroadcasts')); const AdminBroadcastCreate = lazyWithRetry(() => import('./pages/AdminBroadcastCreate')); const AdminPromocodes = lazyWithRetry(() => import('./pages/AdminPromocodes')); const AdminPromocodeCreate = lazyWithRetry(() => import('./pages/AdminPromocodeCreate')); const AdminCoupons = lazyWithRetry(() => import('./pages/AdminCoupons')); const AdminCouponCreate = lazyWithRetry(() => import('./pages/AdminCouponCreate')); const AdminCouponDetail = lazyWithRetry(() => import('./pages/AdminCouponDetail')); const CouponStatus = lazyWithRetry(() => import('./pages/CouponStatus')); const AdminPromocodeStats = lazyWithRetry(() => import('./pages/AdminPromocodeStats')); const AdminPromoGroups = lazyWithRetry(() => import('./pages/AdminPromoGroups')); const AdminPromoGroupCreate = lazyWithRetry(() => import('./pages/AdminPromoGroupCreate')); const AdminCampaigns = lazyWithRetry(() => import('./pages/AdminCampaigns')); const AdminCampaignCreate = lazyWithRetry(() => import('./pages/AdminCampaignCreate')); const AdminCampaignStats = lazyWithRetry(() => import('./pages/AdminCampaignStats')); const AdminCampaignEdit = lazyWithRetry(() => import('./pages/AdminCampaignEdit')); const AdminPartners = lazyWithRetry(() => import('./pages/AdminPartners')); const AdminPartnerSettings = lazyWithRetry(() => import('./pages/AdminPartnerSettings')); const AdminPartnerDetail = lazyWithRetry(() => import('./pages/AdminPartnerDetail')); const AdminApplicationReview = lazyWithRetry(() => import('./pages/AdminApplicationReview')); const AdminPartnerCommission = lazyWithRetry(() => import('./pages/AdminPartnerCommission')); const AdminPartnerRevoke = lazyWithRetry(() => import('./pages/AdminPartnerRevoke')); const AdminPartnerCampaignAssign = lazyWithRetry( () => import('./pages/AdminPartnerCampaignAssign'), ); const AdminWithdrawals = lazyWithRetry(() => import('./pages/AdminWithdrawals')); const AdminWithdrawalDetail = lazyWithRetry(() => import('./pages/AdminWithdrawalDetail')); const AdminWithdrawalReject = lazyWithRetry(() => import('./pages/AdminWithdrawalReject')); const ReferralPartnerApply = lazyWithRetry(() => import('./pages/ReferralPartnerApply')); const ReferralWithdrawalRequest = lazyWithRetry(() => import('./pages/ReferralWithdrawalRequest')); const AdminUsers = lazyWithRetry(() => import('./pages/AdminUsers')); const AdminPayments = lazyWithRetry(() => import('./pages/AdminPayments')); const AdminPaymentMethods = lazyWithRetry(() => import('./pages/AdminPaymentMethods')); const AdminPaymentMethodEdit = lazyWithRetry(() => import('./pages/AdminPaymentMethodEdit')); const AdminPromoOffers = lazyWithRetry(() => import('./pages/AdminPromoOffers')); const AdminPromoOfferTemplateEdit = lazyWithRetry( () => import('./pages/AdminPromoOfferTemplateEdit'), ); const AdminPromoOfferSend = lazyWithRetry(() => import('./pages/AdminPromoOfferSend')); const AdminRemnawave = lazyWithRetry(() => import('./pages/AdminRemnawave')); const AdminRemnawaveSquadDetail = lazyWithRetry(() => import('./pages/AdminRemnawaveSquadDetail')); const AdminEmailTemplates = lazyWithRetry(() => import('./pages/AdminEmailTemplates')); const AdminTrafficUsage = lazyWithRetry(() => import('./pages/AdminTrafficUsage')); const AdminBulkActions = lazyWithRetry(() => import('./pages/AdminBulkActions')); const AdminSalesStats = lazyWithRetry(() => import('./pages/AdminSalesStats')); const AdminUpdates = lazyWithRetry(() => import('./pages/AdminUpdates')); const AdminUserDetail = lazyWithRetry(() => import('./pages/AdminUserDetail')); const AdminBroadcastDetail = lazyWithRetry(() => import('./pages/AdminBroadcastDetail')); const AdminPinnedMessages = lazyWithRetry(() => import('./pages/AdminPinnedMessages')); const AdminPinnedMessageCreate = lazyWithRetry(() => import('./pages/AdminPinnedMessageCreate')); const AdminChannelSubscriptions = lazyWithRetry(() => import('./pages/AdminChannelSubscriptions')); const AdminRoles = lazyWithRetry(() => import('./pages/AdminRoles')); const AdminRoleEdit = lazyWithRetry(() => import('./pages/AdminRoleEdit')); const AdminRoleAssign = lazyWithRetry(() => import('./pages/AdminRoleAssign')); const AdminPolicies = lazyWithRetry(() => import('./pages/AdminPolicies')); const AdminPolicyEdit = lazyWithRetry(() => import('./pages/AdminPolicyEdit')); const AdminAuditLog = lazyWithRetry(() => import('./pages/AdminAuditLog')); const AdminLandings = lazyWithRetry(() => import('./pages/AdminLandings')); const AdminLandingEditor = lazyWithRetry(() => import('./pages/AdminLandingEditor')); const AdminLandingStats = lazyWithRetry(() => import('./pages/AdminLandingStats')); const AdminReferralNetwork = lazyWithRetry(() => import('./pages/ReferralNetwork')); // News pages const NewsArticlePage = lazyWithRetry(() => import('./pages/NewsArticle')); const AdminNews = lazyWithRetry(() => import('./pages/AdminNews')); const AdminNewsCreate = lazyWithRetry(() => import('./pages/AdminNewsCreate')); // Info pages const InfoPageView = lazyWithRetry(() => import('./pages/InfoPageView')); const AdminInfoPages = lazyWithRetry(() => import('./pages/AdminInfoPages')); const AdminInfoPageEditor = lazyWithRetry(() => import('./pages/AdminInfoPageEditor')); const AdminLegalPages = lazyWithRetry(() => import('./pages/AdminLegalPages')); function ProtectedRoute({ children, withLayout = true, }: { children: React.ReactNode; withLayout?: boolean; }) { const isAuthenticated = useAuthStore((state) => state.isAuthenticated); const isLoading = useAuthStore((state) => state.isLoading); const location = useLocation(); if (isLoading) { return ; } if (!isAuthenticated) { saveReturnUrl(); return ; } return withLayout ? {children} : <>{children}; } function AdminRoute({ children }: { children: React.ReactNode }) { const isAuthenticated = useAuthStore((state) => state.isAuthenticated); const isLoading = useAuthStore((state) => state.isLoading); const isAdmin = useAuthStore((state) => state.isAdmin); const location = useLocation(); if (isLoading) { return ; } if (!isAuthenticated) { saveReturnUrl(); return ; } if (!isAdmin) { return ; } return {children}; } // Suspense + error boundary wrapper for lazy routes. The boundary lives // OUTSIDE Suspense so chunk-load failures (caught by lazyWithRetry's reload // path) and render-time exceptions both surface in the page-level fallback // instead of crashing the entire shell via the top-level boundary. function LazyPage({ children }: { children: React.ReactNode }) { return ( }>{children} ); } function BlockingOverlay() { const blockingType = useBlockingStore((state) => state.blockingType); if (blockingType === 'maintenance') { return ; } if (blockingType === 'channel_subscription') { return ; } if (blockingType === 'blacklisted') { return ; } if (blockingType === 'account_deleted') { return ; } if (blockingType === 'backend_unavailable') { return ; } return null; } /** Redirect /subscription/:id → /subscriptions/:id preserving the param */ function LegacySubscriptionRedirect() { const { subscriptionId } = useParams<{ subscriptionId: string }>(); return ; } function App() { useAnalyticsCounters(); // Pulls site-verification tokens (Antilopay apay-tag etc.) from the bot // backend and injects matching tags into document.head. useSiteVerification(); return ( <> {/* Public routes */} } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> {/* Protected routes */} } /> } /> } /> } /> {/* Legacy redirects for backward compatibility */} } /> } /> } /> } /> } /> } /> } /> {/* Path-based method variant: some providers (Lava) reject return URLs that carry a query string, so the method is encoded in the path instead of ?method=. */} } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> {/* Admin routes */} } /> } /> } /> {/* Deep-link target for admin ticket notification buttons (bot issue #2988): opens a specific ticket directly. Static "/settings" above out-ranks this dynamic segment in react-router, so there is no conflict. */} } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> {/* RBAC routes */} } /> } /> } /> } /> } /> } /> } /> {/* News admin routes */} } /> } /> } /> {/* Info pages admin routes */} } /> } /> } /> } /> } /> {/* Catch all */} } /> ); } export default App;