init
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
import { useState } from 'react';
|
||||
import { Logo } from '../components/Logo';
|
||||
import { Button } from '../components/ui/Button';
|
||||
import { Panel } from '../components/ui/Panel';
|
||||
import { controlsHelp } from '../config/keybindings';
|
||||
import type { Lobby } from '../net/types';
|
||||
import type { GameActions } from '../net/useGameConnection';
|
||||
import { itemVisuals } from '../theme/assets';
|
||||
import styles from './LobbyPage.module.css';
|
||||
|
||||
interface Props {
|
||||
lobby: Lobby;
|
||||
playerId: string;
|
||||
actions: GameActions;
|
||||
}
|
||||
|
||||
export function LobbyPage({ lobby, playerId, actions }: Props) {
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [copied, setCopied] = useState(false);
|
||||
const isHost = lobby.hostId === playerId;
|
||||
|
||||
const copyLink = async () => {
|
||||
const url = `${window.location.origin}${window.location.pathname}?code=${lobby.code}`;
|
||||
try {
|
||||
await navigator.clipboard.writeText(url);
|
||||
setCopied(true);
|
||||
window.setTimeout(() => setCopied(false), 1500);
|
||||
} catch {
|
||||
setError(url);
|
||||
}
|
||||
};
|
||||
|
||||
const start = async () => {
|
||||
setError(null);
|
||||
try {
|
||||
await actions.startGame();
|
||||
} catch (e) {
|
||||
setError(e instanceof Error ? e.message : String(e));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<main className={styles.page}>
|
||||
<header className={styles.header}>
|
||||
<Logo small />
|
||||
<Button variant="secondary" onClick={() => void actions.leaveLobby()}>
|
||||
Leave
|
||||
</Button>
|
||||
</header>
|
||||
|
||||
<Panel title="Lobby code" className={styles.codePanel}>
|
||||
<div className={styles.code}>{lobby.code}</div>
|
||||
<Button variant="secondary" onClick={copyLink}>
|
||||
{copied ? 'Copied!' : 'Copy invite link'}
|
||||
</Button>
|
||||
</Panel>
|
||||
|
||||
<div className={styles.grid}>
|
||||
<Panel title={`Players ${lobby.players.length}/${lobby.maxPlayers}`}>
|
||||
<ol className={styles.players}>
|
||||
{lobby.players.map((p) => (
|
||||
<li key={p.id} className={p.id === playerId ? styles.me : ''}>
|
||||
<span>{p.name}</span>
|
||||
{p.isHost && <span className={styles.badge}>HOST</span>}
|
||||
{p.id === playerId && <span className={styles.badge}>YOU</span>}
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
{isHost ? (
|
||||
<Button onClick={start} className={styles.start}>
|
||||
Start game
|
||||
</Button>
|
||||
) : (
|
||||
<p className={styles.waiting}>Waiting for the host to start...</p>
|
||||
)}
|
||||
{error && <p className={styles.error}>{error}</p>}
|
||||
</Panel>
|
||||
|
||||
{lobby.lastResults && (
|
||||
<Panel title="Last round">
|
||||
<table className={styles.results}>
|
||||
<tbody>
|
||||
{lobby.lastResults.map((r) => (
|
||||
<tr key={r.id}>
|
||||
<td className={r.place === 1 ? styles.winner : ''}>#{r.place}</td>
|
||||
<td>{r.name}</td>
|
||||
<td className={styles.num}>{r.score}</td>
|
||||
<td className={styles.num}>{r.lines}L</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</Panel>
|
||||
)}
|
||||
|
||||
<Panel title="Controls">
|
||||
<dl className={styles.controls}>
|
||||
{controlsHelp.map((c) => (
|
||||
<div key={c.action}>
|
||||
<dt>{c.keys}</dt>
|
||||
<dd>{c.action}</dd>
|
||||
</div>
|
||||
))}
|
||||
</dl>
|
||||
</Panel>
|
||||
|
||||
<Panel title="Items">
|
||||
<p className={styles.hint}>Items hide inside blocks. Clear their line to collect them.</p>
|
||||
<ul className={styles.items}>
|
||||
{Object.entries(itemVisuals).map(([id, v]) => (
|
||||
<li key={id}>
|
||||
<img src={v.icon} alt="" width={20} height={20} />
|
||||
<div>
|
||||
<div style={{ color: v.color }}>{v.name}</div>
|
||||
<div className={styles.itemDescription}>{v.description}</div>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</Panel>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user