Frontend, README und Konfigurationsvorlage
React-Oberfläche mit Übersicht, Match-Erstellung, Beitritt über Team- und Zuschauercodes, Teamkarten, visuellem Map-Veto, Serie und Serverpanel. README beschreibt Einrichtung, Umgebungsvariablen, Rollen, Veto-Abläufe, Serveradapter und CSTV-Zuschauerzugang. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
104aa1a67b
commit
568ccf9b09
24 files changed
+1938
-1
No files matched your search
@@ -0,0 +1,97 @@
|
||||
import { useState } from 'react';
|
||||
import { Link, useNavigate } from 'react-router-dom';
|
||||
import { api } from '../lib/api.js';
|
||||
import { FORMAT_LABEL, serverLabel, timeAgo } from '../lib/labels.js';
|
||||
import { getAccess } from '../lib/session.js';
|
||||
import { useOverview } from '../lib/useMatch.js';
|
||||
|
||||
const GROUPS = [
|
||||
{ key: 'open', title: 'Offen', empty: 'Keine offenen Lobbys. Erstelle ein Match, um zu starten.' },
|
||||
{ key: 'running', title: 'Laufend', empty: 'Gerade läuft kein Match.' },
|
||||
{ key: 'closed', title: 'Abgeschlossen', empty: 'Noch keine abgeschlossenen Matches.' },
|
||||
];
|
||||
|
||||
export default function Overview() {
|
||||
const { matches } = useOverview();
|
||||
return (
|
||||
<div className="overview">
|
||||
<section className="overview-head">
|
||||
<div>
|
||||
<h1>Match-Lobbys</h1>
|
||||
<p className="lead">Teams treten mit ihrem Einladungscode bei, die Captains führen das Map-Veto, der Server wird automatisch eingerichtet.</p>
|
||||
</div>
|
||||
<CodeEntry />
|
||||
</section>
|
||||
|
||||
{matches === null ? (
|
||||
<p className="muted">Lade Matches …</p>
|
||||
) : (
|
||||
<div className="groups">
|
||||
{GROUPS.map((g) => {
|
||||
const list = matches.filter((m) => m.group === g.key);
|
||||
return (
|
||||
<section key={g.key} className={`group group-${g.key}`}>
|
||||
<h2>{g.title} <span className="count">{list.length}</span></h2>
|
||||
{list.length === 0 ? <p className="muted small">{g.empty}</p> : (
|
||||
<ul className="match-list">
|
||||
{list.map((m) => <MatchRow key={m.id} match={m} />)}
|
||||
</ul>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function MatchRow({ match: m }) {
|
||||
const access = getAccess(m.id);
|
||||
const server = serverLabel(m.server);
|
||||
return (
|
||||
<li>
|
||||
<Link to={`/m/${m.id}`} className="match-row">
|
||||
<div className="match-row-teams">
|
||||
<span className="team-a">{m.teamA}</span>
|
||||
<span className="vs">vs.</span>
|
||||
<span className="team-b">{m.teamB}</span>
|
||||
</div>
|
||||
<div className="match-row-meta">
|
||||
<span>{m.modeLabel}</span>
|
||||
<span>{FORMAT_LABEL[m.format]}</span>
|
||||
{m.group === 'open' && <span>{m.players.A + m.players.B}/{m.players.teamSize * 2} Spieler</span>}
|
||||
<span className={`tone-${server.tone}`}>{m.statusLabel}</span>
|
||||
{access && <span className="mine">Dein Zugang</span>}
|
||||
<span className="muted">{timeAgo(m.createdAt)}</span>
|
||||
</div>
|
||||
</Link>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
function CodeEntry() {
|
||||
const [code, setCode] = useState('');
|
||||
const [error, setError] = useState(null);
|
||||
const navigate = useNavigate();
|
||||
const submit = async (e) => {
|
||||
e.preventDefault();
|
||||
setError(null);
|
||||
try {
|
||||
const res = await api('/access', { method: 'POST', body: { code } });
|
||||
navigate(res.access === 'spectator' ? `/watch/${code.trim()}` : `/join/${code.trim()}`);
|
||||
} catch (err) {
|
||||
setError(err.message);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<form className="code-entry" onSubmit={submit}>
|
||||
<label htmlFor="code">Einladungs- oder Zuschauercode</label>
|
||||
<div className="row">
|
||||
<input id="code" value={code} onChange={(e) => setCode(e.target.value)} placeholder="ABCD-EFGH" autoComplete="off" spellCheck="false" />
|
||||
<button className="btn" disabled={code.replace(/[^a-z0-9]/gi, '').length < 8}>Beitreten</button>
|
||||
</div>
|
||||
{error && <p className="field-error">{error}</p>}
|
||||
</form>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user