diff --git a/src/App.tsx b/src/App.tsx index 48f2c17..d827e18 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -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 {children} } +// Suspense wrapper for lazy components +function LazyPage({ children }: { children: React.ReactNode }) { + return ( + }> + {children} + + ) +} + function App() { return ( @@ -90,7 +106,7 @@ function App() { path="/" element={ - + } /> @@ -98,7 +114,7 @@ function App() { path="/subscription" element={ - + } /> @@ -106,7 +122,7 @@ function App() { path="/balance" element={ - + } /> @@ -114,7 +130,7 @@ function App() { path="/referral" element={ - + } /> @@ -122,7 +138,7 @@ function App() { path="/support" element={ - + } /> @@ -130,7 +146,7 @@ function App() { path="/profile" element={ - + } /> @@ -138,7 +154,7 @@ function App() { path="/contests" element={ - + } /> @@ -146,7 +162,7 @@ function App() { path="/polls" element={ - + } /> @@ -154,7 +170,7 @@ function App() { path="/info" element={ - + } /> @@ -162,7 +178,7 @@ function App() { path="/wheel" element={ - + } /> @@ -172,7 +188,7 @@ function App() { path="/admin" element={ - + } /> @@ -180,7 +196,7 @@ function App() { path="/admin/tickets" element={ - + } /> @@ -188,7 +204,7 @@ function App() { path="/admin/settings" element={ - + } /> @@ -196,7 +212,7 @@ function App() { path="/admin/apps" element={ - + } /> @@ -204,7 +220,7 @@ function App() { path="/admin/wheel" element={ - + } /> @@ -212,7 +228,7 @@ function App() { path="/admin/tariffs" element={ - + } /> @@ -220,7 +236,7 @@ function App() { path="/admin/servers" element={ - + } /> @@ -228,7 +244,7 @@ function App() { path="/admin/dashboard" element={ - + } /> @@ -236,7 +252,7 @@ function App() { path="/admin/ban-system" element={ - + } /> @@ -244,7 +260,7 @@ function App() { path="/admin/broadcasts" element={ - + } /> @@ -252,7 +268,7 @@ function App() { path="/admin/promocodes" element={ - + } /> @@ -260,7 +276,7 @@ function App() { path="/admin/campaigns" element={ - + } /> @@ -268,7 +284,7 @@ function App() { path="/admin/users" element={ - + } /> @@ -276,7 +292,7 @@ function App() { path="/admin/payments" element={ - + } /> @@ -284,7 +300,7 @@ function App() { path="/admin/promo-offers" element={ - + } /> @@ -292,7 +308,7 @@ function App() { path="/admin/remnawave" element={ - + } /> diff --git a/src/components/Toast.tsx b/src/components/Toast.tsx index 7ce53e7..950025a 100644 --- a/src/components/Toast.tsx +++ b/src/components/Toast.tsx @@ -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([]) + const timersRef = useRef>>(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 ( {children} diff --git a/src/hooks/useWebSocket.ts b/src/hooks/useWebSocket.ts index 04bb2cf..da267a6 100644 --- a/src/hooks/useWebSocket.ts +++ b/src/hooks/useWebSocket.ts @@ -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])