Skip to content

Commit bcc9a40

Browse files
committed
feat: Implement lazy loading for admin components and add loading spinner; enhance Vite config for chunking
1 parent be96562 commit bcc9a40

File tree

7 files changed

+160
-40
lines changed

7 files changed

+160
-40
lines changed

client-test/src/App.tsx

Lines changed: 41 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Navigate, BrowserRouter as Router, Routes, Route, useLocation, useNavigate } from 'react-router-dom';
22
import { Toaster } from 'react-hot-toast';
3-
import { useEffect } from 'react';
3+
import { useEffect, Suspense, lazy } from 'react';
44
import { setNavigateFunction } from './lib/axios';
55
import Home from './components/Home';
66
import LoginPage from './components/Login';
@@ -13,24 +13,27 @@ import ProfilePage from './components/ProfilePage';
1313
import ResetPassword from './components/ResetPassword.tsx';
1414
import ForgotPassword from './components/ForgotPassword';
1515
import EditProfile from './components/ProfilePage/profileEdit';
16-
import AddChallenge from './components/Admin/addChallenges';
17-
import UserDashboard from './components/Admin/Users';
1816
import Challenges from './components/Challenges';
19-
import { Sidebar } from './components/Admin/sidebar';
2017
import Footer from './components/footer';
2118
import { Navbar } from './components/Navbar';
2219
import SolutionPage from './components/Challenges/solutionPage';
2320
import NotFoundPage from './components/Error404';
24-
import AdminLogin from './components/Admin/index';
25-
import Dashboard from './components/Admin/Dashboard.tsx';
26-
import { useAdminStore } from './context/AdminContext.tsx';
27-
import AdminChallenges from './components/Admin/AdminChallenges.tsx';
2821
import RouteSEO from './components/RouteSEO';
2922
import ProblemSet from './components/ProblemSet';
3023
import CategoryProblems from './components/ProblemSet/CategoryProblems';
3124
import { ServerErrorRoute, NetworkErrorRoute, UnauthorizedRoute } from './components/ErrorRoutes';
3225
import { ForbiddenPage } from './components/EnhancedErrorPages';
33-
import ChallengeDetail from './components/Admin/ChallengeDetails.tsx';
26+
import { useAdminStore } from './context/AdminContext.tsx';
27+
import LoadingSpinner from './components/LoadingSpinner';
28+
29+
// Lazy load Admin components
30+
const AddChallenge = lazy(() => import('./components/Admin/addChallenges'));
31+
const UserDashboard = lazy(() => import('./components/Admin/Users'));
32+
const Sidebar = lazy(() => import('./components/Admin/sidebar').then(module => ({ default: module.Sidebar })));
33+
const AdminLogin = lazy(() => import('./components/Admin/index'));
34+
const Dashboard = lazy(() => import('./components/Admin/Dashboard.tsx'));
35+
const AdminChallenges = lazy(() => import('./components/Admin/AdminChallenges.tsx'));
36+
const ChallengeDetail = lazy(() => import('./components/Admin/ChallengeDetails.tsx'));
3437

3538
function UserApp() {
3639
return (
@@ -73,37 +76,43 @@ function AdminApp() {
7376
if (!token) {
7477
return (
7578
<div className="flex-grow mt-16 md:mt-0">
76-
<Routes>
77-
<Route path="/codingclubadmin" element={<AdminLogin />} />
78-
<Route path="*" element={<Navigate to="/codingclubadmin" replace />} />
79-
</Routes>
79+
<Suspense fallback={<LoadingSpinner />}>
80+
<Routes>
81+
<Route path="/codingclubadmin" element={<AdminLogin />} />
82+
<Route path="*" element={<Navigate to="/codingclubadmin" replace />} />
83+
</Routes>
84+
</Suspense>
8085
<Toaster position="bottom-right" toastOptions={{ duration: 2000 }} />
8186
</div>
8287
);
8388
}
8489
return (
8590
<div className="flex min-h-screen">
86-
<div className="w-64 min-h-screen fixed left-0 top-0 bg-gray-800 text-white hidden md:block">
87-
<Sidebar />
88-
</div>
91+
<Suspense fallback={<LoadingSpinner />}>
92+
<div className="w-64 min-h-screen fixed left-0 top-0 bg-gray-800 text-white hidden md:block">
93+
<Sidebar />
94+
</div>
95+
</Suspense>
8996
<div className="flex-grow md:ml-64 pl-0 md:pl-4 mt-16 md:mt-0">
9097
<Toaster position="bottom-right" toastOptions={{ duration: 2000 }} />
91-
<Routes>
92-
{/* <Route path="/codingclubadmin" element={<AdminLogin />} /> */}
93-
<Route path="/codingclubadmin" element={<Dashboard />} />
94-
<Route path="/codingclubadmin/users" element={<UserDashboard />} />
95-
<Route path="/codingclubadmin/challenges" element={<AdminChallenges />} />
96-
<Route path="/codingclubadmin/challenges/:id" element={<ChallengeDetail />} />
97-
<Route path="/codingclubadmin/users/profile/:username" element={<ProfilePage />} />
98-
<Route path="/codingclubadmin/addchallenge" element={<AddChallenge />} />
99-
<Route path="/codingclubadmin/leaderboard" element={<Leaderboard />} />
100-
{/* Enhanced Error Pages for Admin */}
101-
<Route path="/codingclubadmin/server-error" element={<ServerErrorRoute />} />
102-
<Route path="/codingclubadmin/network-error" element={<NetworkErrorRoute />} />
103-
<Route path="/codingclubadmin/unauthorized" element={<UnauthorizedRoute />} />
104-
<Route path="/codingclubadmin/forbidden" element={<ForbiddenPage />} />
105-
<Route path="/codingclubadmin/*" element={<NotFoundPage />} />
106-
</Routes>
98+
<Suspense fallback={<LoadingSpinner />}>
99+
<Routes>
100+
{/* <Route path="/codingclubadmin" element={<AdminLogin />} /> */}
101+
<Route path="/codingclubadmin" element={<Dashboard />} />
102+
<Route path="/codingclubadmin/users" element={<UserDashboard />} />
103+
<Route path="/codingclubadmin/challenges" element={<AdminChallenges />} />
104+
<Route path="/codingclubadmin/challenges/:id" element={<ChallengeDetail />} />
105+
<Route path="/codingclubadmin/users/profile/:username" element={<ProfilePage />} />
106+
<Route path="/codingclubadmin/addchallenge" element={<AddChallenge />} />
107+
<Route path="/codingclubadmin/leaderboard" element={<Leaderboard />} />
108+
{/* Enhanced Error Pages for Admin */}
109+
<Route path="/codingclubadmin/server-error" element={<ServerErrorRoute />} />
110+
<Route path="/codingclubadmin/network-error" element={<NetworkErrorRoute />} />
111+
<Route path="/codingclubadmin/unauthorized" element={<UnauthorizedRoute />} />
112+
<Route path="/codingclubadmin/forbidden" element={<ForbiddenPage />} />
113+
<Route path="/codingclubadmin/*" element={<NotFoundPage />} />
114+
</Routes>
115+
</Suspense>
107116
</div>
108117
</div>
109118
);

client-test/src/components/Admin/AdminChallenges.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,12 @@ interface Challenge {
4343
totalUsers?: number;
4444
}
4545

46-
interface Statistics {
47-
totalProblems: number;
48-
averageSolveRate: number;
49-
topPerformer: string;
50-
lowestPerformer: string;
51-
}
46+
// interface Statistics {
47+
// totalProblems: number;
48+
// averageSolveRate: number;
49+
// topPerformer: string;
50+
// lowestPerformer: string;
51+
// }
5252

5353
// Button Component
5454
const Button = memo(

client-test/src/components/Admin/ChallengeDetails.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useEffect, useState } from 'react';
1+
import { useEffect, useState } from 'react';
22
import { useParams, useNavigate } from 'react-router-dom';
33
import axios from 'axios';
44
import { ChevronLeft, Edit, Trash2, Check, Zap } from 'lucide-react';
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import React from 'react';
2+
3+
const LoadingSpinner: React.FC = () => {
4+
return (
5+
<div className="flex flex-col items-center justify-center min-h-screen bg-gradient-to-br from-blue-50 via-white to-purple-50">
6+
{/* Main container */}
7+
<div className="relative">
8+
{/* Outer ring */}
9+
<div className="w-24 h-24 border-4 border-blue-200 rounded-full animate-spin">
10+
<div className="absolute top-0 left-0 w-24 h-24 border-4 border-transparent border-t-blue-500 rounded-full animate-spin"></div>
11+
</div>
12+
13+
{/* Inner pulsing circle */}
14+
<div className="absolute inset-4 bg-gradient-to-r from-blue-400 to-purple-500 rounded-full animate-pulse">
15+
<div className="w-full h-full bg-white rounded-full opacity-20 animate-ping"></div>
16+
</div>
17+
18+
{/* Center dot */}
19+
<div className="absolute inset-8 bg-gradient-to-r from-purple-500 to-pink-500 rounded-full animate-bounce"></div>
20+
</div>
21+
22+
{/* Loading text with typewriter effect */}
23+
<div className="mt-8 text-center">
24+
<div className="text-2xl font-bold text-gray-700 mb-2">
25+
<span className="inline-block animate-pulse">Loading</span>
26+
<span className="inline-block animate-bounce delay-100">.</span>
27+
<span className="inline-block animate-bounce delay-200">.</span>
28+
<span className="inline-block animate-bounce delay-300">.</span>
29+
</div>
30+
<div className="text-sm text-gray-500 animate-fade-in">
31+
Preparing your coding adventure...
32+
</div>
33+
</div>
34+
35+
{/* Floating particles */}
36+
<div className="absolute inset-0 overflow-hidden pointer-events-none">
37+
{[...Array(6)].map((_, i) => (
38+
<div
39+
key={i}
40+
className={`absolute w-2 h-2 bg-blue-400 rounded-full animate-float-${i % 3}`}
41+
style={{
42+
left: `${20 + i * 15}%`,
43+
top: `${30 + (i % 2) * 40}%`,
44+
animationDelay: `${i * 0.5}s`,
45+
animationDuration: `${3 + i * 0.5}s`
46+
}}
47+
></div>
48+
))}
49+
</div>
50+
</div>
51+
);
52+
};
53+
54+
export default LoadingSpinner;

client-test/src/context/AdminContext.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ interface Challenge {
2929
interface PaginationInfo {
3030
currentPage: number
3131
totalPages: number
32+
totalUsers: number
3233
total: number
3334
limit: number
3435
hasNextPage: boolean

client-test/src/styles.css

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,4 +131,30 @@
131131
.dark .bg-grid-pattern {
132132
background-image: linear-gradient(to right, rgba(255, 255, 255, 0.05) 1px, transparent 1px),
133133
linear-gradient(to bottom, rgba(255, 255, 255, 0.05) 1px, transparent 1px);
134-
}
134+
}
135+
136+
/* Custom animations for loading spinner */
137+
@keyframes float-0 {
138+
0%, 100% { transform: translateY(0px) translateX(0px); opacity: 0.7; }
139+
50% { transform: translateY(-20px) translateX(10px); opacity: 1; }
140+
}
141+
142+
@keyframes float-1 {
143+
0%, 100% { transform: translateY(0px) translateX(0px); opacity: 0.7; }
144+
50% { transform: translateY(-15px) translateX(-10px); opacity: 1; }
145+
}
146+
147+
@keyframes float-2 {
148+
0%, 100% { transform: translateY(0px) translateX(0px); opacity: 0.7; }
149+
50% { transform: translateY(-25px) translateX(5px); opacity: 1; }
150+
}
151+
152+
@keyframes fade-in {
153+
0% { opacity: 0; transform: translateY(10px); }
154+
100% { opacity: 1; transform: translateY(0); }
155+
}
156+
157+
.animate-float-0 { animation: float-0 4s ease-in-out infinite; }
158+
.animate-float-1 { animation: float-1 3.5s ease-in-out infinite; }
159+
.animate-float-2 { animation: float-2 5s ease-in-out infinite; }
160+
.animate-fade-in { animation: fade-in 1s ease-out; }

client-test/vite.config.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,35 @@ export default defineConfig({
1313
server: {
1414
host: 'localhost',
1515
port: 5173
16+
},
17+
build: {
18+
rollupOptions: {
19+
output: {
20+
manualChunks: {
21+
// Separate admin components into their own chunk
22+
admin: [
23+
'./src/components/Admin/AdminChallenges.tsx',
24+
'./src/components/Admin/Users.tsx',
25+
'./src/components/Admin/Dashboard.tsx',
26+
'./src/components/Admin/addChallenges.tsx',
27+
'./src/components/Admin/ChallengeDetails.tsx',
28+
'./src/components/Admin/sidebar.tsx',
29+
'./src/components/Admin/index.tsx',
30+
'./src/components/Admin/home.tsx',
31+
'./src/components/Admin/datepicker.tsx',
32+
'./src/components/Admin/shared/AnimatedCounter.tsx',
33+
'./src/components/Admin/shared/Badge.tsx',
34+
'./src/components/Admin/shared/Button.tsx',
35+
'./src/components/Admin/shared/SkeletonLoader.tsx',
36+
'./src/components/Admin/shared/types.ts'
37+
],
38+
// Separate vendor libraries
39+
vendor: ['react', 'react-dom', 'react-router-dom'],
40+
// UI libraries
41+
ui: ['react-hot-toast', 'lucide-react', 'react-icons']
42+
}
43+
}
44+
},
45+
chunkSizeWarningLimit: 1000 // Increase limit to 1000kb
1646
}
1747
})

0 commit comments

Comments
 (0)