Skip to content

Commit dee12e7

Browse files
committed
Showing Online users and loading efffect if backend server is not UP
1 parent 1892e82 commit dee12e7

File tree

3 files changed

+192
-87
lines changed

3 files changed

+192
-87
lines changed

backend/index.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -119,11 +119,14 @@ setInterval(() => {
119119

120120
const { handleUDSRequest } = require('./udsHandler');
121121

122+
// Track connected users
123+
let connectedUsers = 0;
124+
122125
io.on('connection', (socket) => {
123-
console.log('User connected:', socket.id);
126+
connectedUsers++;
127+
io.emit('user-count', connectedUsers);
124128

125129
socket.on('control', (data) => {
126-
console.log('Control received:', data);
127130
if (data.action === 'accelerate') {
128131
vehicleState.accelerating = data.pressed;
129132
} else if (data.action === 'brake') {
@@ -144,7 +147,8 @@ io.on('connection', (socket) => {
144147
});
145148

146149
socket.on('disconnect', () => {
147-
console.log('User disconnected:', socket.id);
150+
connectedUsers--;
151+
io.emit('user-count', connectedUsers);
148152
});
149153
});
150154

frontend/src/App.tsx

Lines changed: 127 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@ import { ArchitectureExplorer } from './components/ArchitectureExplorer';
77
import { Tutorials } from './components/Tutorials';
88
import { Community } from './components/Community';
99
import { CANLog } from './components/CANLog';
10+
import { LoadingOverlay } from './components/LoadingOverlay';
1011
import type { CANMessage } from './types';
11-
import { Car } from 'lucide-react';
12+
import { Car, Users } from 'lucide-react';
1213

1314
const SOCKET_URL = import.meta.env.VITE_SOCKET_URL || 'http://localhost:3000';
1415

@@ -21,13 +22,37 @@ function App() {
2122
const [headlights, setHeadlights] = useState('off');
2223
const [turnSignals, setTurnSignals] = useState('off');
2324
const [messages, setMessages] = useState<CANMessage[]>([]);
25+
const [activeUsers, setActiveUsers] = useState(0);
26+
const [isLoading, setIsLoading] = useState(true);
27+
const [loadingProgress, setLoadingProgress] = useState(0);
2428

2529
useEffect(() => {
2630
const newSocket = io(SOCKET_URL);
2731
setSocket(newSocket);
2832

33+
// Start 30-second loading timer
34+
const loadingTimer = setTimeout(() => {
35+
setIsLoading(false);
36+
}, 30000);
37+
38+
// Progress animation (increment every second)
39+
const progressInterval = setInterval(() => {
40+
setLoadingProgress(prev => {
41+
if (prev >= 100) return 100;
42+
return prev + (100 / 30); // Increment over 30 seconds
43+
});
44+
}, 1000);
45+
2946
newSocket.on('connect', () => {
3047
console.log('Connected to CAN Simulator Server');
48+
// Clear loading after successful connection
49+
clearTimeout(loadingTimer);
50+
clearInterval(progressInterval);
51+
setIsLoading(false);
52+
});
53+
54+
newSocket.on('user-count', (count: number) => {
55+
setActiveUsers(count);
3156
});
3257

3358
newSocket.on('can-message', (msg: CANMessage) => {
@@ -49,6 +74,8 @@ function App() {
4974
});
5075

5176
return () => {
77+
clearTimeout(loadingTimer);
78+
clearInterval(progressInterval);
5279
newSocket.disconnect();
5380
};
5481
}, []);
@@ -61,106 +88,122 @@ function App() {
6188
};
6289

6390
return (
64-
<div className="min-h-screen bg-white text-gray-900 font-sans selection:bg-cyan-500 selection:text-white">
65-
{/* Header */}
66-
<header className="bg-white border-b border-gray-200 p-4 sticky top-0 z-50 shadow-sm">
67-
<div className="container mx-auto flex items-center justify-between">
68-
<div className="flex items-center gap-3">
69-
<div className="bg-gradient-to-tr from-cyan-500 to-blue-600 p-2 rounded-lg shadow-lg shadow-cyan-500/20">
70-
<Car size={24} className="text-white" />
71-
</div>
72-
<div>
73-
<h1 className="text-xl font-bold text-gray-900">
74-
AutoLearn Studio
75-
</h1>
76-
<p className="text-xs text-gray-500 uppercase tracking-widest">Interactive Protocol Simulator</p>
91+
<>
92+
{/* Loading Overlay */}
93+
{isLoading && <LoadingOverlay progress={loadingProgress} />}
94+
95+
<div className="min-h-screen bg-white text-gray-900 font-sans selection:bg-cyan-500 selection:text-white">
96+
{/* Header */}
97+
<header className="bg-white border-b border-gray-200 p-4 sticky top-0 z-50 shadow-sm">
98+
<div className="container mx-auto flex items-center justify-between">
99+
<div className="flex items-center gap-3">
100+
<div className="bg-gradient-to-tr from-cyan-500 to-blue-600 p-2 rounded-lg shadow-lg shadow-cyan-500/20">
101+
<Car size={24} className="text-white" />
102+
</div>
103+
<div>
104+
<h1 className="text-xl font-bold text-gray-900">
105+
AutoLearn Studio
106+
</h1>
107+
<p className="text-xs text-gray-500 uppercase tracking-widest">Interactive Protocol Simulator</p>
108+
</div>
77109
</div>
78-
</div>
79-
<div className="flex gap-4 text-sm font-medium text-gray-400">
80-
<span
81-
onClick={() => setActiveView('simulator')}
82-
className={`px-4 py-2 rounded-lg transition-colors cursor-pointer ${activeView === 'simulator'
110+
<div className="flex gap-4 text-sm font-medium text-gray-400">
111+
<span
112+
onClick={() => setActiveView('simulator')}
113+
className={`px-4 py-2 rounded-lg transition-colors cursor-pointer ${activeView === 'simulator'
83114
? 'bg-cyan-500 text-white'
84115
: 'text-gray-600 hover:text-gray-900 hover:bg-gray-100'
85-
}`}
86-
>
87-
Simulator
88-
</span>
89-
<span
90-
onClick={() => setActiveView('diagnostics')}
91-
className={`px-4 py-2 rounded-lg transition-colors cursor-pointer ${activeView === 'diagnostics'
116+
}`}
117+
>
118+
Simulator
119+
</span>
120+
<span
121+
onClick={() => setActiveView('diagnostics')}
122+
className={`px-4 py-2 rounded-lg transition-colors cursor-pointer ${activeView === 'diagnostics'
92123
? 'bg-cyan-500 text-white'
93124
: 'text-gray-600 hover:text-gray-900 hover:bg-gray-100'
94-
}`}
95-
>
96-
Diagnostics
97-
</span>
98-
<span
99-
onClick={() => setActiveView('architecture')}
100-
className={`px-4 py-2 rounded-lg transition-colors cursor-pointer ${activeView === 'architecture'
125+
}`}
126+
>
127+
Diagnostics
128+
</span>
129+
<span
130+
onClick={() => setActiveView('architecture')}
131+
className={`px-4 py-2 rounded-lg transition-colors cursor-pointer ${activeView === 'architecture'
101132
? 'bg-cyan-500 text-white'
102133
: 'text-gray-600 hover:text-gray-900 hover:bg-gray-100'
103-
}`}
104-
>
105-
Architecture
106-
</span>
107-
<span
108-
onClick={() => setActiveView('tutorials')}
109-
className={`px-4 py-2 rounded-lg transition-colors cursor-pointer ${activeView === 'tutorials'
134+
}`}
135+
>
136+
Architecture
137+
</span>
138+
<span
139+
onClick={() => setActiveView('tutorials')}
140+
className={`px-4 py-2 rounded-lg transition-colors cursor-pointer ${activeView === 'tutorials'
110141
? 'bg-cyan-500 text-white'
111142
: 'text-gray-600 hover:text-gray-900 hover:bg-gray-100'
112-
}`}
113-
>
114-
Tutorials
115-
</span>
116-
<span
117-
onClick={() => setActiveView('community')}
118-
className={`px-4 py-2 rounded-lg transition-colors cursor-pointer ${activeView === 'community'
143+
}`}
144+
>
145+
Tutorials
146+
</span>
147+
<span
148+
onClick={() => setActiveView('community')}
149+
className={`px-4 py-2 rounded-lg transition-colors cursor-pointer ${activeView === 'community'
119150
? 'bg-cyan-500 text-white'
120151
: 'text-gray-600 hover:text-gray-900 hover:bg-gray-100'
121-
}`}
122-
>
123-
Community
124-
</span>
152+
}`}
153+
>
154+
<span>Community</span>
155+
</span>
156+
157+
{/* Active Users Counter (show only if >= 5) */}
158+
{activeUsers >= 5 && (
159+
<div className="flex items-center gap-2 bg-green-50 border border-green-200 px-3 py-1.5 rounded-full ml-4">
160+
<div className="w-2 h-2 bg-green-500 rounded-full animate-pulse" />
161+
<Users size={14} className="text-green-600" />
162+
<span className="text-green-700 font-semibold text-sm">
163+
{activeUsers} online
164+
</span>
165+
</div>
166+
)}
167+
</div>
125168
</div>
126-
</div>
127-
</header>
128-
129-
{/* Main Content */}
130-
<main className="container mx-auto p-6 h-[calc(100vh-80px)]">
131-
132-
{activeView === 'simulator' && (
133-
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6 h-full">
134-
{/* Left Column: Dashboard & Controls */}
135-
<div className="lg:col-span-2 flex flex-col gap-6">
136-
<Dashboard speed={speed} rpm={rpm} gear={gear} headlights={headlights} turnSignals={turnSignals} />
137-
<Controls onControl={handleControl} currentGear={gear} currentHeadlights={headlights} currentTurnSignals={turnSignals} />
138-
139-
{/* Info Card */}
140-
<div className="bg-gradient-to-br from-blue-50 to-cyan-50 border border-blue-200 rounded-xl p-6 shadow-sm">
141-
<h3 className="text-blue-900 text-sm font-bold uppercase tracking-wider mb-3">How it works</h3>
142-
<p className="text-gray-700 text-sm leading-relaxed">
143-
Press and hold <span className="text-emerald-600 font-bold">Accelerate</span> to increase engine RPM and vehicle speed.
144-
The backend simulates the vehicle physics and broadcasts <span className="text-blue-600 font-mono font-semibold">CAN Frames</span> via WebSocket.
145-
Watch the traffic log to see the raw data changing in real-time.
146-
</p>
169+
</header>
170+
171+
{/* Main Content */}
172+
<main className="container mx-auto p-6 h-[calc(100vh-80px)]">
173+
174+
{activeView === 'simulator' && (
175+
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6 h-full">
176+
{/* Left Column: Dashboard & Controls */}
177+
<div className="lg:col-span-2 flex flex-col gap-6">
178+
<Dashboard speed={speed} rpm={rpm} gear={gear} headlights={headlights} turnSignals={turnSignals} />
179+
<Controls onControl={handleControl} currentGear={gear} currentHeadlights={headlights} currentTurnSignals={turnSignals} />
180+
181+
{/* Info Card */}
182+
<div className="bg-gradient-to-br from-blue-50 to-cyan-50 border border-blue-200 rounded-xl p-6 shadow-sm">
183+
<h3 className="text-blue-900 text-sm font-bold uppercase tracking-wider mb-3">How it works</h3>
184+
<p className="text-gray-700 text-sm leading-relaxed">
185+
Press and hold <span className="text-emerald-600 font-bold">Accelerate</span> to increase engine RPM and vehicle speed.
186+
The backend simulates the vehicle physics and broadcasts <span className="text-blue-600 font-mono font-semibold">CAN Frames</span> via WebSocket.
187+
Watch the traffic log to see the raw data changing in real-time.
188+
</p>
189+
</div>
147190
</div>
148-
</div>
149191

150-
{/* Right Column: CAN Log */}
151-
<div className="lg:col-span-1 h-full min-h-[400px]">
152-
<CANLog messages={messages} />
192+
{/* Right Column: CAN Log */}
193+
<div className="lg:col-span-1 h-full min-h-[400px]">
194+
<CANLog messages={messages} />
195+
</div>
153196
</div>
154-
</div>
155-
)}
197+
)}
156198

157-
{activeView === 'diagnostics' && <UDSTester socket={socket} />}
158-
{activeView === 'architecture' && <ArchitectureExplorer />}
159-
{activeView === 'tutorials' && <Tutorials />}
160-
{activeView === 'community' && <Community />}
199+
{activeView === 'diagnostics' && <UDSTester socket={socket} />}
200+
{activeView === 'architecture' && <ArchitectureExplorer />}
201+
{activeView === 'tutorials' && <Tutorials />}
202+
{activeView === 'community' && <Community />}
161203

162-
</main>
163-
</div>
204+
</main>
205+
</div>
206+
</>
164207
);
165208
}
166209

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { Car } from 'lucide-react';
2+
3+
interface LoadingOverlayProps {
4+
progress: number;
5+
}
6+
7+
export const LoadingOverlay: React.FC<LoadingOverlayProps> = ({ progress }) => {
8+
return (
9+
<div className="fixed inset-0 bg-white/95 backdrop-blur-sm z-50 flex items-center justify-center">
10+
<div className="text-center">
11+
{/* Circular Progress */}
12+
<div className="relative w-32 h-32 mx-auto mb-6">
13+
<svg className="w-full h-full transform -rotate-90">
14+
{/* Background circle */}
15+
<circle
16+
cx="64"
17+
cy="64"
18+
r="56"
19+
stroke="currentColor"
20+
strokeWidth="8"
21+
fill="transparent"
22+
className="text-gray-200"
23+
/>
24+
{/* Progress circle */}
25+
<circle
26+
cx="64"
27+
cy="64"
28+
r="56"
29+
stroke="currentColor"
30+
strokeWidth="8"
31+
fill="transparent"
32+
strokeDasharray={352}
33+
strokeDashoffset={352 - (352 * progress) / 100}
34+
className="text-cyan-500 transition-all duration-1000 ease-out"
35+
/>
36+
</svg>
37+
{/* Center icon */}
38+
<div className="absolute inset-0 flex items-center justify-center">
39+
<Car size={40} className="text-cyan-500 animate-pulse" />
40+
</div>
41+
</div>
42+
43+
{/* Text */}
44+
<h3 className="text-xl font-bold text-gray-900 mb-2">
45+
Starting Backend...
46+
</h3>
47+
<p className="text-gray-600 text-sm mb-4">
48+
Waking up the server (this may take up to 30 seconds)
49+
</p>
50+
51+
{/* Progress percentage */}
52+
<div className="text-cyan-600 font-mono text-sm font-semibold">
53+
{Math.round(progress)}%
54+
</div>
55+
</div>
56+
</div>
57+
);
58+
};

0 commit comments

Comments
 (0)