@@ -7,8 +7,9 @@ import { ArchitectureExplorer } from './components/ArchitectureExplorer';
77import { Tutorials } from './components/Tutorials' ;
88import { Community } from './components/Community' ;
99import { CANLog } from './components/CANLog' ;
10+ import { LoadingOverlay } from './components/LoadingOverlay' ;
1011import type { CANMessage } from './types' ;
11- import { Car } from 'lucide-react' ;
12+ import { Car , Users } from 'lucide-react' ;
1213
1314const 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
0 commit comments