1939574e0f
This commit updates the project to use Vite as the build tool, replacing the previous configuration. It also implements lazy loading for the HostView, PlayerView, and SpectatorView components to improve initial load performance. The spectator view's fastest buzzer calculation has been refactored for clarity and type safety. Dependencies have been updated to include Vite and its React plugin. The import maps in index.html have also been adjusted to reflect the new build tool.
110 lines
4.0 KiB
TypeScript
110 lines
4.0 KiB
TypeScript
import React, { useState, useEffect, Suspense } from 'react';
|
|
import { GameProvider } from './context/GameContext';
|
|
import { Monitor, Smartphone, LayoutDashboard, Loader2 } from 'lucide-react';
|
|
|
|
// Lazy load components to reduce initial bundle size
|
|
const HostView = React.lazy(() => import('./components/HostView').then(module => ({ default: module.HostView })));
|
|
const PlayerView = React.lazy(() => import('./components/PlayerView').then(module => ({ default: module.PlayerView })));
|
|
const SpectatorView = React.lazy(() => import('./components/SpectatorView').then(module => ({ default: module.SpectatorView })));
|
|
|
|
const App: React.FC = () => {
|
|
const [view, setView] = useState<'HOST' | 'PLAYER' | 'SPECTATOR'>('SPECTATOR');
|
|
|
|
// Simple hash routing for demo purposes
|
|
useEffect(() => {
|
|
const handleHashChange = () => {
|
|
const hash = window.location.hash;
|
|
if (hash === '#host') setView('HOST');
|
|
else if (hash === '#player') setView('PLAYER');
|
|
else setView('SPECTATOR');
|
|
};
|
|
|
|
handleHashChange();
|
|
window.addEventListener('hashchange', handleHashChange);
|
|
return () => window.removeEventListener('hashchange', handleHashChange);
|
|
}, []);
|
|
|
|
const navigate = (newView: 'HOST' | 'PLAYER' | 'SPECTATOR') => {
|
|
setView(newView);
|
|
window.location.hash = newView.toLowerCase();
|
|
};
|
|
|
|
const renderView = () => {
|
|
switch (view) {
|
|
case 'HOST': return <HostView />;
|
|
case 'PLAYER': return <PlayerView />;
|
|
case 'SPECTATOR': return <SpectatorView />;
|
|
default: return <SpectatorView />;
|
|
}
|
|
};
|
|
|
|
return (
|
|
<GameProvider>
|
|
<div className="flex flex-col h-screen bg-slate-950 overflow-hidden">
|
|
{/* Top Navigation Bar */}
|
|
<nav className="flex items-center justify-between px-4 py-3 bg-slate-900 border-b border-slate-800 shadow-md shrink-0 z-50">
|
|
<div className="flex items-center gap-3">
|
|
<div className="w-8 h-8 bg-gradient-to-tr from-indigo-600 to-purple-600 rounded-lg flex items-center justify-center font-black italic text-white shadow-lg border border-white/10">Q</div>
|
|
<span className="font-bold text-lg tracking-wide text-slate-100 hidden sm:block">QuizMaster Live</span>
|
|
</div>
|
|
|
|
<div className="flex bg-slate-800 p-1 rounded-lg border border-slate-700">
|
|
<NavButton
|
|
active={view === 'SPECTATOR'}
|
|
onClick={() => navigate('SPECTATOR')}
|
|
icon={<Monitor size={16} />}
|
|
label="Spectator"
|
|
/>
|
|
<NavButton
|
|
active={view === 'HOST'}
|
|
onClick={() => navigate('HOST')}
|
|
icon={<LayoutDashboard size={16} />}
|
|
label="Host"
|
|
/>
|
|
<NavButton
|
|
active={view === 'PLAYER'}
|
|
onClick={() => navigate('PLAYER')}
|
|
icon={<Smartphone size={16} />}
|
|
label="Player"
|
|
/>
|
|
</div>
|
|
</nav>
|
|
|
|
{/* Main Content Area */}
|
|
<div className="flex-1 relative w-full h-full overflow-hidden">
|
|
<Suspense fallback={
|
|
<div className="h-full w-full flex flex-col items-center justify-center text-slate-500 gap-4">
|
|
<Loader2 className="animate-spin w-12 h-12 text-indigo-500" />
|
|
<p>Loading Quiz Module...</p>
|
|
</div>
|
|
}>
|
|
{renderView()}
|
|
</Suspense>
|
|
</div>
|
|
</div>
|
|
</GameProvider>
|
|
);
|
|
};
|
|
|
|
interface NavButtonProps {
|
|
active: boolean;
|
|
onClick: () => void;
|
|
icon: React.ReactNode;
|
|
label: string;
|
|
}
|
|
|
|
const NavButton: React.FC<NavButtonProps> = ({ active, onClick, icon, label }) => (
|
|
<button
|
|
onClick={onClick}
|
|
className={`flex items-center gap-2 px-3 py-1.5 rounded-md text-sm font-medium transition-all ${
|
|
active
|
|
? 'bg-indigo-600 text-white shadow-sm'
|
|
: 'text-slate-400 hover:text-white hover:bg-slate-700'
|
|
}`}
|
|
>
|
|
{icon}
|
|
<span className="hidden md:inline">{label}</span>
|
|
</button>
|
|
);
|
|
|
|
export default App; |