Merge pull request #34 from BEDOLAGA-DEV/PEDZ

Refactor App component to implement lazy loading for user and admin p…
This commit is contained in:
PEDZEO
2026-01-19 01:56:41 +03:00
committed by GitHub
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 { useAuthStore } from './store/auth'
import Layout from './components/layout/Layout'
import PageLoader from './components/common/PageLoader'
import { saveReturnUrl } from './utils/token'
// 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 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 Contests from './pages/Contests'
import Polls from './pages/Polls'
import Info from './pages/Info'
import Wheel from './pages/Wheel'
import AdminWheel from './pages/AdminWheel'
import AdminTariffs from './pages/AdminTariffs'
import AdminServers from './pages/AdminServers'
import AdminPanel from './pages/AdminPanel'
import AdminDashboard from './pages/AdminDashboard'
import AdminBanSystem from './pages/AdminBanSystem'
import AdminBroadcasts from './pages/AdminBroadcasts'
import AdminPromocodes from './pages/AdminPromocodes'
import AdminCampaigns from './pages/AdminCampaigns'
import AdminUsers from './pages/AdminUsers'
import AdminPayments from './pages/AdminPayments'
import AdminPromoOffers from './pages/AdminPromoOffers'
import AdminRemnawave from './pages/AdminRemnawave'
// User pages - lazy load
const Dashboard = lazy(() => import('./pages/Dashboard'))
const Subscription = lazy(() => import('./pages/Subscription'))
const Balance = lazy(() => import('./pages/Balance'))
const Referral = lazy(() => import('./pages/Referral'))
const Support = lazy(() => import('./pages/Support'))
const Profile = lazy(() => import('./pages/Profile'))
const Contests = lazy(() => import('./pages/Contests'))
const Polls = lazy(() => import('./pages/Polls'))
const Info = lazy(() => import('./pages/Info'))
const Wheel = lazy(() => import('./pages/Wheel'))
// Admin pages - lazy load (only for admins)
const AdminPanel = lazy(() => import('./pages/AdminPanel'))
const AdminTickets = lazy(() => import('./pages/AdminTickets'))
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 }) {
const { isAuthenticated, isLoading } = useAuthStore()
@@ -73,6 +80,15 @@ function AdminRoute({ children }: { children: React.ReactNode }) {
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() {
return (
<Routes>
@@ -90,7 +106,7 @@ function App() {
path="/"
element={
<ProtectedRoute>
<Dashboard />
<LazyPage><Dashboard /></LazyPage>
</ProtectedRoute>
}
/>
@@ -98,7 +114,7 @@ function App() {
path="/subscription"
element={
<ProtectedRoute>
<Subscription />
<LazyPage><Subscription /></LazyPage>
</ProtectedRoute>
}
/>
@@ -106,7 +122,7 @@ function App() {
path="/balance"
element={
<ProtectedRoute>
<Balance />
<LazyPage><Balance /></LazyPage>
</ProtectedRoute>
}
/>
@@ -114,7 +130,7 @@ function App() {
path="/referral"
element={
<ProtectedRoute>
<Referral />
<LazyPage><Referral /></LazyPage>
</ProtectedRoute>
}
/>
@@ -122,7 +138,7 @@ function App() {
path="/support"
element={
<ProtectedRoute>
<Support />
<LazyPage><Support /></LazyPage>
</ProtectedRoute>
}
/>
@@ -130,7 +146,7 @@ function App() {
path="/profile"
element={
<ProtectedRoute>
<Profile />
<LazyPage><Profile /></LazyPage>
</ProtectedRoute>
}
/>
@@ -138,7 +154,7 @@ function App() {
path="/contests"
element={
<ProtectedRoute>
<Contests />
<LazyPage><Contests /></LazyPage>
</ProtectedRoute>
}
/>
@@ -146,7 +162,7 @@ function App() {
path="/polls"
element={
<ProtectedRoute>
<Polls />
<LazyPage><Polls /></LazyPage>
</ProtectedRoute>
}
/>
@@ -154,7 +170,7 @@ function App() {
path="/info"
element={
<ProtectedRoute>
<Info />
<LazyPage><Info /></LazyPage>
</ProtectedRoute>
}
/>
@@ -162,7 +178,7 @@ function App() {
path="/wheel"
element={
<ProtectedRoute>
<Wheel />
<LazyPage><Wheel /></LazyPage>
</ProtectedRoute>
}
/>
@@ -172,7 +188,7 @@ function App() {
path="/admin"
element={
<AdminRoute>
<AdminPanel />
<LazyPage><AdminPanel /></LazyPage>
</AdminRoute>
}
/>
@@ -180,7 +196,7 @@ function App() {
path="/admin/tickets"
element={
<AdminRoute>
<AdminTickets />
<LazyPage><AdminTickets /></LazyPage>
</AdminRoute>
}
/>
@@ -188,7 +204,7 @@ function App() {
path="/admin/settings"
element={
<AdminRoute>
<AdminSettings />
<LazyPage><AdminSettings /></LazyPage>
</AdminRoute>
}
/>
@@ -196,7 +212,7 @@ function App() {
path="/admin/apps"
element={
<AdminRoute>
<AdminApps />
<LazyPage><AdminApps /></LazyPage>
</AdminRoute>
}
/>
@@ -204,7 +220,7 @@ function App() {
path="/admin/wheel"
element={
<AdminRoute>
<AdminWheel />
<LazyPage><AdminWheel /></LazyPage>
</AdminRoute>
}
/>
@@ -212,7 +228,7 @@ function App() {
path="/admin/tariffs"
element={
<AdminRoute>
<AdminTariffs />
<LazyPage><AdminTariffs /></LazyPage>
</AdminRoute>
}
/>
@@ -220,7 +236,7 @@ function App() {
path="/admin/servers"
element={
<AdminRoute>
<AdminServers />
<LazyPage><AdminServers /></LazyPage>
</AdminRoute>
}
/>
@@ -228,7 +244,7 @@ function App() {
path="/admin/dashboard"
element={
<AdminRoute>
<AdminDashboard />
<LazyPage><AdminDashboard /></LazyPage>
</AdminRoute>
}
/>
@@ -236,7 +252,7 @@ function App() {
path="/admin/ban-system"
element={
<AdminRoute>
<AdminBanSystem />
<LazyPage><AdminBanSystem /></LazyPage>
</AdminRoute>
}
/>
@@ -244,7 +260,7 @@ function App() {
path="/admin/broadcasts"
element={
<AdminRoute>
<AdminBroadcasts />
<LazyPage><AdminBroadcasts /></LazyPage>
</AdminRoute>
}
/>
@@ -252,7 +268,7 @@ function App() {
path="/admin/promocodes"
element={
<AdminRoute>
<AdminPromocodes />
<LazyPage><AdminPromocodes /></LazyPage>
</AdminRoute>
}
/>
@@ -260,7 +276,7 @@ function App() {
path="/admin/campaigns"
element={
<AdminRoute>
<AdminCampaigns />
<LazyPage><AdminCampaigns /></LazyPage>
</AdminRoute>
}
/>
@@ -268,7 +284,7 @@ function App() {
path="/admin/users"
element={
<AdminRoute>
<AdminUsers />
<LazyPage><AdminUsers /></LazyPage>
</AdminRoute>
}
/>
@@ -276,7 +292,7 @@ function App() {
path="/admin/payments"
element={
<AdminRoute>
<AdminPayments />
<LazyPage><AdminPayments /></LazyPage>
</AdminRoute>
}
/>
@@ -284,7 +300,7 @@ function App() {
path="/admin/promo-offers"
element={
<AdminRoute>
<AdminPromoOffers />
<LazyPage><AdminPromoOffers /></LazyPage>
</AdminRoute>
}
/>
@@ -292,7 +308,7 @@ function App() {
path="/admin/remnawave"
element={
<AdminRoute>
<AdminRemnawave />
<LazyPage><AdminRemnawave /></LazyPage>
</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 {
type?: 'success' | 'error' | 'info' | 'warning'
@@ -29,22 +29,41 @@ export function useToast() {
export function ToastProvider({ children }: { children: ReactNode }) {
const [toasts, setToasts] = useState<Toast[]>([])
const timersRef = useRef<Map<number, ReturnType<typeof setTimeout>>>(new Map())
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 }
setToasts(prev => [...prev, toast])
setTimeout(() => {
const timer = setTimeout(() => {
setToasts(prev => prev.filter(t => t.id !== id))
timersRef.current.delete(id)
}, toast.duration)
timersRef.current.set(id, timer)
}, [])
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))
}, [])
// Cleanup all timers on unmount
useEffect(() => {
const timers = timersRef.current
return () => {
timers.forEach(timer => clearTimeout(timer))
timers.clear()
}
}, [])
return (
<ToastContext.Provider value={{ showToast }}>
{children}

View File

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