Files
BuzzMasterPro/App.tsx
T
Philip e50e6f83dc feat: Initialize BuzzMaster project structure
Sets up the foundational project structure for the BuzzMaster Live Quiz Platform. This includes:

- **Project Initialization:** Creates `package.json` with necessary dependencies (React, React Router DOM, Vite, TypeScript).
- **Vite Configuration:** Configures Vite for development and building, including server settings and environment variable handling.
- **HTML Entry Point:** Sets up `index.html` with basic structure, Tailwind CSS, Google Fonts, and ESM import maps.
- **React Entry Point:** Configures `index.tsx` to render the main `App` component.
- **TypeScript Configuration:** Defines `tsconfig.json` for the project.
- **Git Ignore:** Adds standard files and directories to `.gitignore`.
- **README and Metadata:** Includes a basic `README.md` and `metadata.json` describing the project.
- **Type Definitions:** Establishes core type definitions in `types.ts` for game state, user roles, players, teams, and questions.
- **App Component:** Creates a basic `App.tsx` component with routing and initial game state management, including logic for local storage fallback and API sync.
- **Component Stubs:** Adds placeholder components for `PlayerView`, `AdminDashboard`, and `SpectatorView`.
2026-05-18 16:07:02 -07:00

153 lines
5.8 KiB
TypeScript

import React, { useState, useEffect } from 'react';
import { HashRouter, Routes, Route, useNavigate } from 'react-router-dom';
import PlayerView from './components/PlayerView';
import AdminDashboard from './components/AdminDashboard';
import SpectatorView from './components/SpectatorView';
import { GameData, GameState, UserRole } from './types';
import { api } from './services/api';
const INITIAL_GAME_DATA: GameData = {
id: 'game-123',
name: 'Trivia Night 2024',
state: GameState.LOBBY,
currentQuestionIndex: 0,
questions: [
{ id: 'q1', text: 'What is the capital of France?', answer: 'Paris', points: 100 },
{ id: 'q2', text: 'Which planet is known as the Red Planet?', answer: 'Mars', points: 150 },
{ id: 'q3', text: 'Who wrote "Romeo and Juliet"?', answer: 'William Shakespeare', points: 200 },
],
players: [],
teams: [],
activeBuzzerQueue: [],
countdownValue: 3,
};
const App: React.FC = () => {
const [game, setGame] = useState<GameData>(() => {
const saved = localStorage.getItem(`game_${INITIAL_GAME_DATA.id}`);
return saved ? JSON.parse(saved) : INITIAL_GAME_DATA;
});
const [currentUser, setCurrentUser] = useState<any>(null);
const [isSynced, setIsSynced] = useState(false);
const [useLocal, setUseLocal] = useState(false);
useEffect(() => {
const interval = setInterval(async () => {
try {
const data = await api.getGameStatus(game.id);
if (data) {
setGame(prev => ({ ...prev, ...data }));
setIsSynced(true);
setUseLocal(false);
} else {
setIsSynced(false);
setUseLocal(true);
}
} catch (e) {
setIsSynced(false);
setUseLocal(true);
}
}, 3000);
return () => clearInterval(interval);
}, [game.id]);
const updateGame = async (newData: Partial<GameData>) => {
const updated = { ...game, ...newData };
setGame(updated);
// This updates localStorage internally and attempts the API call
await api.updateGameState(game.id, newData);
};
return (
<div className="min-h-screen w-full bg-slate-950 overflow-hidden text-white">
<HashRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route
path="/player"
element={<PlayerView game={game} updateGame={updateGame} currentUser={currentUser} setCurrentUser={setCurrentUser} />}
/>
<Route
path="/admin"
element={<AdminDashboard game={game} updateGame={updateGame} />}
/>
<Route
path="/spectator"
element={<SpectatorView game={game} />}
/>
</Routes>
</HashRouter>
<div className="fixed bottom-4 right-4 flex items-center gap-2 px-3 py-1 bg-black/60 backdrop-blur-md rounded-full border border-white/10 text-[10px] uppercase tracking-widest z-50">
<div className={`w-2 h-2 rounded-full ${isSynced ? 'bg-green-500 shadow-[0_0_8px_#22c55e]' : useLocal ? 'bg-orange-500' : 'bg-red-500 animate-pulse'}`}></div>
{isSynced ? 'Container Synced' : useLocal ? 'Local Cache Mode' : 'Connecting...'}
</div>
</div>
);
};
const Home: React.FC = () => {
const navigate = useNavigate();
return (
<div className="flex flex-col items-center justify-center min-h-screen p-6 relative">
<div className="absolute top-10 text-xs font-mono opacity-30 tracking-[0.2em]">BUZZMASTER // SYSTEM_DOCKER_V1</div>
<div className="relative mb-16">
<h1 className="text-7xl font-black text-transparent bg-clip-text bg-gradient-to-r from-cyan-400 via-blue-500 to-indigo-600 italic tracking-tighter">
BUZZMASTER <span className="text-white not-italic font-thin">PRO</span>
</h1>
<div className="absolute -bottom-2 right-0 text-[10px] font-bold bg-blue-600 px-2 py-0.5 rounded text-white tracking-widest">
ENTERPRISE
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 w-full max-w-6xl">
<RoleCard
title="Host"
desc="Command Center"
node="admin-node-01"
color="bg-indigo-600"
onClick={() => navigate('/admin')}
/>
<RoleCard
title="Player"
desc="Client Interface"
node="client-endpoint"
color="bg-sky-600"
onClick={() => navigate('/player')}
/>
<RoleCard
title="Display"
desc="Public Broadcast"
node="render-engine"
color="bg-emerald-600"
onClick={() => navigate('/spectator')}
/>
</div>
<div className="mt-20 opacity-20 text-[10px] font-mono max-w-xl text-center">
DOCKER_SERVICE_DISCOVERY: ENABLED | DB_DRIVER: MARIADB_10.11 | PERSISTENCE_STRATEGY: HYBRID_LOCAL_REMOTE
</div>
</div>
);
};
const RoleCard: React.FC<{ title: string; desc: string; node: string; color: string; onClick: () => void }> = ({ title, desc, node, color, onClick }) => (
<button
onClick={onClick}
className={`${color} p-10 rounded-[2.5rem] shadow-2xl hover:-translate-y-2 active:scale-95 transition-all text-left border border-white/20 group relative overflow-hidden`}
>
<div className="absolute -right-6 -bottom-6 opacity-10 group-hover:scale-150 group-hover:rotate-12 transition-transform duration-500">
<svg width="160" height="160" viewBox="0 0 24 24" fill="white"><path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"/></svg>
</div>
<div className="relative z-10">
<div className="text-[10px] font-mono opacity-50 mb-4 tracking-widest">{node}</div>
<h2 className="text-4xl font-black mb-1 uppercase tracking-tighter italic">{title}</h2>
<p className="text-sm font-medium opacity-70 group-hover:opacity-100 transition-opacity">{desc}</p>
</div>
</button>
);
export default App;