Refactor App component to implement lazy loading for user and admin pages, enhancing performance and reducing initial load time. Introduce LazyPage wrapper for suspense handling during component loading.

This commit is contained in:
PEDZEO
2026-01-19 01:51:52 +03:00
parent 3fc22d2b4d
commit 5f123a39d9
3 changed files with 98 additions and 61 deletions

View File

@@ -1,39 +1,46 @@
import { lazy, Suspense } from 'react'
import { Routes, Route, Navigate, useLocation } from 'react-router-dom' import { Routes, Route, Navigate, useLocation } from 'react-router-dom'
import { useAuthStore } from './store/auth' import { useAuthStore } from './store/auth'
import Layout from './components/layout/Layout' import Layout from './components/layout/Layout'
import PageLoader from './components/common/PageLoader' import PageLoader from './components/common/PageLoader'
import { saveReturnUrl } from './utils/token' import { saveReturnUrl } from './utils/token'
// Auth pages - load immediately (small)
import Login from './pages/Login' import Login from './pages/Login'
import TelegramCallback from './pages/TelegramCallback' import TelegramCallback from './pages/TelegramCallback'
import TelegramRedirect from './pages/TelegramRedirect' import TelegramRedirect from './pages/TelegramRedirect'
import DeepLinkRedirect from './pages/DeepLinkRedirect' import DeepLinkRedirect from './pages/DeepLinkRedirect'
import Dashboard from './pages/Dashboard'
import Subscription from './pages/Subscription'
import Balance from './pages/Balance'
import Referral from './pages/Referral'
import Support from './pages/Support'
import Profile from './pages/Profile'
import AdminTickets from './pages/AdminTickets'
import AdminSettings from './pages/AdminSettings'
import AdminApps from './pages/AdminApps'
import VerifyEmail from './pages/VerifyEmail' import VerifyEmail from './pages/VerifyEmail'
import Contests from './pages/Contests'
import Polls from './pages/Polls' // User pages - lazy load
import Info from './pages/Info' const Dashboard = lazy(() => import('./pages/Dashboard'))
import Wheel from './pages/Wheel' const Subscription = lazy(() => import('./pages/Subscription'))
import AdminWheel from './pages/AdminWheel' const Balance = lazy(() => import('./pages/Balance'))
import AdminTariffs from './pages/AdminTariffs' const Referral = lazy(() => import('./pages/Referral'))
import AdminServers from './pages/AdminServers' const Support = lazy(() => import('./pages/Support'))
import AdminPanel from './pages/AdminPanel' const Profile = lazy(() => import('./pages/Profile'))
import AdminDashboard from './pages/AdminDashboard' const Contests = lazy(() => import('./pages/Contests'))
import AdminBanSystem from './pages/AdminBanSystem' const Polls = lazy(() => import('./pages/Polls'))
import AdminBroadcasts from './pages/AdminBroadcasts' const Info = lazy(() => import('./pages/Info'))
import AdminPromocodes from './pages/AdminPromocodes' const Wheel = lazy(() => import('./pages/Wheel'))
import AdminCampaigns from './pages/AdminCampaigns'
import AdminUsers from './pages/AdminUsers' // Admin pages - lazy load (only for admins)
import AdminPayments from './pages/AdminPayments' const AdminPanel = lazy(() => import('./pages/AdminPanel'))
import AdminPromoOffers from './pages/AdminPromoOffers' const AdminTickets = lazy(() => import('./pages/AdminTickets'))
import AdminRemnawave from './pages/AdminRemnawave' const AdminSettings = lazy(() => import('./pages/AdminSettings'))
const AdminApps = lazy(() => import('./pages/AdminApps'))
const AdminWheel = lazy(() => import('./pages/AdminWheel'))
const AdminTariffs = lazy(() => import('./pages/AdminTariffs'))
const AdminServers = lazy(() => import('./pages/AdminServers'))
const AdminDashboard = lazy(() => import('./pages/AdminDashboard'))
const AdminBanSystem = lazy(() => import('./pages/AdminBanSystem'))
const AdminBroadcasts = lazy(() => import('./pages/AdminBroadcasts'))
const AdminPromocodes = lazy(() => import('./pages/AdminPromocodes'))
const AdminCampaigns = lazy(() => import('./pages/AdminCampaigns'))
const AdminUsers = lazy(() => import('./pages/AdminUsers'))
const AdminPayments = lazy(() => import('./pages/AdminPayments'))
const AdminPromoOffers = lazy(() => import('./pages/AdminPromoOffers'))
const AdminRemnawave = lazy(() => import('./pages/AdminRemnawave'))
function ProtectedRoute({ children }: { children: React.ReactNode }) { function ProtectedRoute({ children }: { children: React.ReactNode }) {
const { isAuthenticated, isLoading } = useAuthStore() const { isAuthenticated, isLoading } = useAuthStore()
@@ -73,6 +80,15 @@ function AdminRoute({ children }: { children: React.ReactNode }) {
return <Layout>{children}</Layout> return <Layout>{children}</Layout>
} }
// Suspense wrapper for lazy components
function LazyPage({ children }: { children: React.ReactNode }) {
return (
<Suspense fallback={<PageLoader variant="dark" />}>
{children}
</Suspense>
)
}
function App() { function App() {
return ( return (
<Routes> <Routes>
@@ -90,7 +106,7 @@ function App() {
path="/" path="/"
element={ element={
<ProtectedRoute> <ProtectedRoute>
<Dashboard /> <LazyPage><Dashboard /></LazyPage>
</ProtectedRoute> </ProtectedRoute>
} }
/> />
@@ -98,7 +114,7 @@ function App() {
path="/subscription" path="/subscription"
element={ element={
<ProtectedRoute> <ProtectedRoute>
<Subscription /> <LazyPage><Subscription /></LazyPage>
</ProtectedRoute> </ProtectedRoute>
} }
/> />
@@ -106,7 +122,7 @@ function App() {
path="/balance" path="/balance"
element={ element={
<ProtectedRoute> <ProtectedRoute>
<Balance /> <LazyPage><Balance /></LazyPage>
</ProtectedRoute> </ProtectedRoute>
} }
/> />
@@ -114,7 +130,7 @@ function App() {
path="/referral" path="/referral"
element={ element={
<ProtectedRoute> <ProtectedRoute>
<Referral /> <LazyPage><Referral /></LazyPage>
</ProtectedRoute> </ProtectedRoute>
} }
/> />
@@ -122,7 +138,7 @@ function App() {
path="/support" path="/support"
element={ element={
<ProtectedRoute> <ProtectedRoute>
<Support /> <LazyPage><Support /></LazyPage>
</ProtectedRoute> </ProtectedRoute>
} }
/> />
@@ -130,7 +146,7 @@ function App() {
path="/profile" path="/profile"
element={ element={
<ProtectedRoute> <ProtectedRoute>
<Profile /> <LazyPage><Profile /></LazyPage>
</ProtectedRoute> </ProtectedRoute>
} }
/> />
@@ -138,7 +154,7 @@ function App() {
path="/contests" path="/contests"
element={ element={
<ProtectedRoute> <ProtectedRoute>
<Contests /> <LazyPage><Contests /></LazyPage>
</ProtectedRoute> </ProtectedRoute>
} }
/> />
@@ -146,7 +162,7 @@ function App() {
path="/polls" path="/polls"
element={ element={
<ProtectedRoute> <ProtectedRoute>
<Polls /> <LazyPage><Polls /></LazyPage>
</ProtectedRoute> </ProtectedRoute>
} }
/> />
@@ -154,7 +170,7 @@ function App() {
path="/info" path="/info"
element={ element={
<ProtectedRoute> <ProtectedRoute>
<Info /> <LazyPage><Info /></LazyPage>
</ProtectedRoute> </ProtectedRoute>
} }
/> />
@@ -162,7 +178,7 @@ function App() {
path="/wheel" path="/wheel"
element={ element={
<ProtectedRoute> <ProtectedRoute>
<Wheel /> <LazyPage><Wheel /></LazyPage>
</ProtectedRoute> </ProtectedRoute>
} }
/> />
@@ -172,7 +188,7 @@ function App() {
path="/admin" path="/admin"
element={ element={
<AdminRoute> <AdminRoute>
<AdminPanel /> <LazyPage><AdminPanel /></LazyPage>
</AdminRoute> </AdminRoute>
} }
/> />
@@ -180,7 +196,7 @@ function App() {
path="/admin/tickets" path="/admin/tickets"
element={ element={
<AdminRoute> <AdminRoute>
<AdminTickets /> <LazyPage><AdminTickets /></LazyPage>
</AdminRoute> </AdminRoute>
} }
/> />
@@ -188,7 +204,7 @@ function App() {
path="/admin/settings" path="/admin/settings"
element={ element={
<AdminRoute> <AdminRoute>
<AdminSettings /> <LazyPage><AdminSettings /></LazyPage>
</AdminRoute> </AdminRoute>
} }
/> />
@@ -196,7 +212,7 @@ function App() {
path="/admin/apps" path="/admin/apps"
element={ element={
<AdminRoute> <AdminRoute>
<AdminApps /> <LazyPage><AdminApps /></LazyPage>
</AdminRoute> </AdminRoute>
} }
/> />
@@ -204,7 +220,7 @@ function App() {
path="/admin/wheel" path="/admin/wheel"
element={ element={
<AdminRoute> <AdminRoute>
<AdminWheel /> <LazyPage><AdminWheel /></LazyPage>
</AdminRoute> </AdminRoute>
} }
/> />
@@ -212,7 +228,7 @@ function App() {
path="/admin/tariffs" path="/admin/tariffs"
element={ element={
<AdminRoute> <AdminRoute>
<AdminTariffs /> <LazyPage><AdminTariffs /></LazyPage>
</AdminRoute> </AdminRoute>
} }
/> />
@@ -220,7 +236,7 @@ function App() {
path="/admin/servers" path="/admin/servers"
element={ element={
<AdminRoute> <AdminRoute>
<AdminServers /> <LazyPage><AdminServers /></LazyPage>
</AdminRoute> </AdminRoute>
} }
/> />
@@ -228,7 +244,7 @@ function App() {
path="/admin/dashboard" path="/admin/dashboard"
element={ element={
<AdminRoute> <AdminRoute>
<AdminDashboard /> <LazyPage><AdminDashboard /></LazyPage>
</AdminRoute> </AdminRoute>
} }
/> />
@@ -236,7 +252,7 @@ function App() {
path="/admin/ban-system" path="/admin/ban-system"
element={ element={
<AdminRoute> <AdminRoute>
<AdminBanSystem /> <LazyPage><AdminBanSystem /></LazyPage>
</AdminRoute> </AdminRoute>
} }
/> />
@@ -244,7 +260,7 @@ function App() {
path="/admin/broadcasts" path="/admin/broadcasts"
element={ element={
<AdminRoute> <AdminRoute>
<AdminBroadcasts /> <LazyPage><AdminBroadcasts /></LazyPage>
</AdminRoute> </AdminRoute>
} }
/> />
@@ -252,7 +268,7 @@ function App() {
path="/admin/promocodes" path="/admin/promocodes"
element={ element={
<AdminRoute> <AdminRoute>
<AdminPromocodes /> <LazyPage><AdminPromocodes /></LazyPage>
</AdminRoute> </AdminRoute>
} }
/> />
@@ -260,7 +276,7 @@ function App() {
path="/admin/campaigns" path="/admin/campaigns"
element={ element={
<AdminRoute> <AdminRoute>
<AdminCampaigns /> <LazyPage><AdminCampaigns /></LazyPage>
</AdminRoute> </AdminRoute>
} }
/> />
@@ -268,7 +284,7 @@ function App() {
path="/admin/users" path="/admin/users"
element={ element={
<AdminRoute> <AdminRoute>
<AdminUsers /> <LazyPage><AdminUsers /></LazyPage>
</AdminRoute> </AdminRoute>
} }
/> />
@@ -276,7 +292,7 @@ function App() {
path="/admin/payments" path="/admin/payments"
element={ element={
<AdminRoute> <AdminRoute>
<AdminPayments /> <LazyPage><AdminPayments /></LazyPage>
</AdminRoute> </AdminRoute>
} }
/> />
@@ -284,7 +300,7 @@ function App() {
path="/admin/promo-offers" path="/admin/promo-offers"
element={ element={
<AdminRoute> <AdminRoute>
<AdminPromoOffers /> <LazyPage><AdminPromoOffers /></LazyPage>
</AdminRoute> </AdminRoute>
} }
/> />
@@ -292,7 +308,7 @@ function App() {
path="/admin/remnawave" path="/admin/remnawave"
element={ element={
<AdminRoute> <AdminRoute>
<AdminRemnawave /> <LazyPage><AdminRemnawave /></LazyPage>
</AdminRoute> </AdminRoute>
} }
/> />

View File

@@ -1,4 +1,4 @@
import { createContext, useContext, useState, useCallback, ReactNode } from 'react' import { createContext, useContext, useState, useCallback, useRef, useEffect, ReactNode } from 'react'
interface ToastOptions { interface ToastOptions {
type?: 'success' | 'error' | 'info' | 'warning' type?: 'success' | 'error' | 'info' | 'warning'
@@ -29,22 +29,41 @@ export function useToast() {
export function ToastProvider({ children }: { children: ReactNode }) { export function ToastProvider({ children }: { children: ReactNode }) {
const [toasts, setToasts] = useState<Toast[]>([]) const [toasts, setToasts] = useState<Toast[]>([])
const timersRef = useRef<Map<number, ReturnType<typeof setTimeout>>>(new Map())
const showToast = useCallback((options: ToastOptions) => { const showToast = useCallback((options: ToastOptions) => {
const id = Date.now() const id = Date.now() + Math.random() // Avoid ID collision
const toast: Toast = { id, duration: 5000, type: 'info', ...options } const toast: Toast = { id, duration: 5000, type: 'info', ...options }
setToasts(prev => [...prev, toast]) setToasts(prev => [...prev, toast])
setTimeout(() => { const timer = setTimeout(() => {
setToasts(prev => prev.filter(t => t.id !== id)) setToasts(prev => prev.filter(t => t.id !== id))
timersRef.current.delete(id)
}, toast.duration) }, toast.duration)
timersRef.current.set(id, timer)
}, []) }, [])
const removeToast = useCallback((id: number) => { const removeToast = useCallback((id: number) => {
// Clear timer when manually removing
const timer = timersRef.current.get(id)
if (timer) {
clearTimeout(timer)
timersRef.current.delete(id)
}
setToasts(prev => prev.filter(t => t.id !== id)) setToasts(prev => prev.filter(t => t.id !== id))
}, []) }, [])
// Cleanup all timers on unmount
useEffect(() => {
const timers = timersRef.current
return () => {
timers.forEach(timer => clearTimeout(timer))
timers.clear()
}
}, [])
return ( return (
<ToastContext.Provider value={{ showToast }}> <ToastContext.Provider value={{ showToast }}>
{children} {children}

View File

@@ -1,6 +1,8 @@
import { useEffect, useRef, useCallback, useState } from 'react' import { useEffect, useRef, useCallback, useState } from 'react'
import { useAuthStore } from '../store/auth' import { useAuthStore } from '../store/auth'
const isDev = import.meta.env.DEV
export interface WSMessage { export interface WSMessage {
type: string type: string
ticket_id?: number ticket_id?: number
@@ -80,7 +82,7 @@ export function useWebSocket(options: UseWebSocketOptions = {}) {
wsRef.current = ws wsRef.current = ws
ws.onopen = () => { ws.onopen = () => {
console.log('[WS] Connected') if (isDev) console.log('[WS] Connected')
setIsConnected(true) setIsConnected(true)
reconnectAttemptsRef.current = 0 reconnectAttemptsRef.current = 0
optionsRef.current.onConnect?.() optionsRef.current.onConnect?.()
@@ -104,12 +106,12 @@ export function useWebSocket(options: UseWebSocketOptions = {}) {
optionsRef.current.onMessage?.(message) optionsRef.current.onMessage?.(message)
} catch (e) { } catch (e) {
console.error('[WS] Failed to parse message:', e) if (isDev) console.error('[WS] Failed to parse message:', e)
} }
} }
ws.onclose = (event) => { ws.onclose = (event) => {
console.log('[WS] Disconnected:', event.code, event.reason) if (isDev) console.log('[WS] Disconnected:', event.code, event.reason)
setIsConnected(false) setIsConnected(false)
optionsRef.current.onDisconnect?.() optionsRef.current.onDisconnect?.()
@@ -121,7 +123,7 @@ export function useWebSocket(options: UseWebSocketOptions = {}) {
// Attempt to reconnect if not closed intentionally // Attempt to reconnect if not closed intentionally
if (event.code !== 1000 && reconnectAttemptsRef.current < maxReconnectAttempts) { if (event.code !== 1000 && reconnectAttemptsRef.current < maxReconnectAttempts) {
const delay = Math.min(1000 * Math.pow(2, reconnectAttemptsRef.current), 30000) const delay = Math.min(1000 * Math.pow(2, reconnectAttemptsRef.current), 30000)
console.log(`[WS] Reconnecting in ${delay}ms (attempt ${reconnectAttemptsRef.current + 1})`) if (isDev) console.log(`[WS] Reconnecting in ${delay}ms (attempt ${reconnectAttemptsRef.current + 1})`)
reconnectTimeoutRef.current = setTimeout(() => { reconnectTimeoutRef.current = setTimeout(() => {
reconnectAttemptsRef.current++ reconnectAttemptsRef.current++
@@ -131,10 +133,10 @@ export function useWebSocket(options: UseWebSocketOptions = {}) {
} }
ws.onerror = (error) => { ws.onerror = (error) => {
console.error('[WS] Error:', error) if (isDev) console.error('[WS] Error:', error)
} }
} catch (e) { } catch (e) {
console.error('[WS] Failed to connect:', e) if (isDev) console.error('[WS] Failed to connect:', e)
} }
}, [accessToken, isAuthenticated, cleanup]) }, [accessToken, isAuthenticated, cleanup])