test / client /src /App.jsx
akborana4's picture
Update client/src/App.jsx
068ccc0 verified
import React, { useEffect, useMemo, useState } from 'react';
import Room from './Room.jsx';
import { ToastProvider, useToasts } from './Toasts.jsx';
import { log } from './logger.js';
import { prettyError } from './utils.js';
function Lobby({ onEnter }) {
const { push } = useToasts();
const [name, setName] = useState('');
const [rooms, setRooms] = useState([]);
const [loading, setLoading] = useState(false);
const [creating, setCreating] = useState(false);
const [newRoomName, setNewRoomName] = useState('');
const [newRoomId, setNewRoomId] = useState('');
const fetchRooms = async () => {
try {
setLoading(true);
const res = await fetch('/api/rooms');
const json = await res.json();
setRooms(json.rooms || []);
setLoading(false);
} catch (e) {
setLoading(false);
log.error(e);
push(`Failed to fetch rooms: ${prettyError(e)}`, 'bad', 4000);
}
};
useEffect(() => {
fetchRooms();
const t = setInterval(fetchRooms, 5000);
return () => clearInterval(t);
}, []);
const canEnter = useMemo(() => name.trim().length >= 2, [name]);
return (
<div className="container">
<div className="panel" style={{ marginBottom: 16 }}>
<div className="row">
<div className="col">
<div className="section-title">Your name</div>
<input className="input" value={name} onChange={e => setName(e.target.value)} placeholder="DJ Neo" />
<div style={{ marginTop: 8, color: 'var(--muted)', fontSize: 13 }}>
Your name appears in the party member list.
</div>
</div>
<div className="col">
<div className="section-title">Create a new party</div>
<div className="row" style={{ gap: 8 }}>
<div className="col">
<input className="input" placeholder="Party name" value={newRoomName} onChange={e => setNewRoomName(e.target.value)} />
</div>
<div className="col">
<input className="input" placeholder="Party ID (letters/numbers)" value={newRoomId} onChange={e => setNewRoomId(e.target.value.replace(/\s+/g, '-'))} />
</div>
</div>
<div style={{ marginTop: 10, display: 'flex', gap: 10 }}>
<button
className="btn primary"
onClick={() => {
if (!canEnter) return push('Please enter your name', 'warn');
if (!newRoomId.trim()) return push('Please provide a Party ID', 'warn');
onEnter({ roomId: newRoomId.trim(), name: name.trim(), asHost: true, roomName: newRoomName.trim() || newRoomId.trim() });
}}
>Create & enter as host</button>
<button className="btn" onClick={fetchRooms} disabled={loading}>{loading ? 'Refreshing...' : 'Refresh rooms'}</button>
</div>
</div>
</div>
</div>
<div className="panel">
<div className="section-title">Active parties</div>
{rooms.length === 0 ? (
<div style={{ color: 'var(--muted)' }}>No active parties yet. Create one above.</div>
) : (
<div className="room-grid">
{rooms.map(r => (
<div key={r.id} className="room-card">
<div style={{ fontWeight: 700 }}>{r.name}</div>
<div className="meta" style={{ marginTop: 4 }}>
ID: <span className="kbd">{r.id}</span> &middot; Members: {r.members} &middot; {r.isPlaying ? 'Playing' : 'Paused'}
</div>
<div style={{ marginTop: 10, display: 'flex', gap: 8 }}>
<button
className="btn good"
onClick={() => {
if (!canEnter) return push('Please enter your name', 'warn');
onEnter({ roomId: r.id, name: name.trim(), asHost: false });
}}
>Join</button>
</div>
</div>
))}
</div>
)}
</div>
</div>
);
}
function Shell({ children }) {
return (
<div className="app-shell">
<div className="top-glow"></div>
<div className="header">
<div className="container">
<div className="brand">
<div className="logo"></div>
<div>Neon Party</div>
</div>
</div>
</div>
{children}
</div>
);
}
export default function App() {
const [session, setSession] = useState(null);
if (session) {
return (
<ToastProvider>
<Shell>
<Room {...session} />
</Shell>
</ToastProvider>
);
}
return (
<ToastProvider>
<Shell>
<Lobby onEnter={setSession} />
</Shell>
</ToastProvider>
);
}