Files
Cs2-Mapveto/client/src/pages/CreateMatch.jsx
T

126 lines
5.0 KiB
React

import { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { api } from '../lib/api.js';
import { FORMAT_LABEL } from '../lib/labels.js';
import { setAccess } from '../lib/session.js';
export default function CreateMatch({ config }) {
const navigate = useNavigate();
const [form, setForm] = useState({ teamA: '', teamB: '', mode: 'competitive', format: 'bo3', vetoStart: 'coin', password: '' });
const [error, setError] = useState(null);
const [busy, setBusy] = useState(false);
const pool = config?.pools.find((p) => p.mode === form.mode);
const set = (key) => (e) => setForm((f) => ({ ...f, [key]: e.target.value }));
// Wird ein Format für den neuen Modus nicht unterstützt, auf das erste unterstützte wechseln.
useEffect(() => {
if (pool && !pool.formats[form.format]?.supported) {
const first = Object.keys(pool.formats).find((f) => pool.formats[f].supported);
if (first) setForm((f) => ({ ...f, format: first }));
}
}, [pool, form.format]);
const submit = async (e) => {
e.preventDefault();
setBusy(true);
setError(null);
try {
const res = await api('/matches', { method: 'POST', body: form });
setAccess(res.id, { token: res.organizerToken, kind: 'organizer' });
navigate(`/m/${res.id}`);
} catch (err) {
setError(err.message);
setBusy(false);
}
};
if (!config) return <p className="muted">Lade Einstellungen …</p>;
return (
<form className="create" onSubmit={submit}>
<h1>Match erstellen</h1>
<p className="lead">Mit dem Erstellen der Lobby wird sofort ein Server angefordert, damit er während des Vetos hochfahren kann.</p>
<div className="create-teams">
<label className="field team-field-a">
<span>Team A</span>
<input value={form.teamA} onChange={set('teamA')} maxLength={32} required placeholder="z. B. Nordlicht" />
</label>
<span className="vs big">vs.</span>
<label className="field team-field-b">
<span>Team B</span>
<input value={form.teamB} onChange={set('teamB')} maxLength={32} required placeholder="z. B. Hafenkante" />
</label>
</div>
<fieldset className="choice">
<legend>Spielmodus</legend>
<div className="choice-row">
{config.pools.map((p) => (
<label key={p.mode} className={`choice-card ${form.mode === p.mode ? 'is-on' : ''}`}>
<input type="radio" name="mode" value={p.mode} checked={form.mode === p.mode} onChange={set('mode')} />
<strong>{p.teamSize} gegen {p.teamSize}</strong>
<span>{p.label}</span>
<small>{p.maps.length} Maps im Pool</small>
</label>
))}
</div>
</fieldset>
<fieldset className="choice">
<legend>Serie</legend>
<div className="choice-row">
{Object.entries(pool.formats).map(([f, info]) => (
<label key={f} className={`choice-card ${form.format === f ? 'is-on' : ''} ${info.supported ? '' : 'is-off'}`}
title={info.supported ? undefined : info.reason}>
<input type="radio" name="format" value={f} checked={form.format === f} onChange={set('format')} disabled={!info.supported} />
<strong>{FORMAT_LABEL[f]}</strong>
<span>{info.supported ? `${f.slice(2)} Map${f === 'bo1' ? '' : 's'}` : 'Mit diesem Map-Pool nicht möglich'}</span>
</label>
))}
</div>
{pool.formats[form.format]?.flow && <FlowPreview flow={pool.formats[form.format].flow} />}
</fieldset>
<label className="field">
<span>Wer beginnt das Veto?</span>
<select value={form.vetoStart} onChange={set('vetoStart')}>
<option value="coin">Münzwurf beim Veto-Start</option>
<option value="A">{form.teamA || 'Team A'}</option>
<option value="B">{form.teamB || 'Team B'}</option>
<option value="knife">Messerrunde: Organisator legt danach fest, wer beginnt</option>
</select>
</label>
{config.createRequiresPassword && (
<label className="field">
<span>Organisator-Passwort</span>
<input type="password" value={form.password} onChange={set('password')} required autoComplete="current-password" />
</label>
)}
{error && <div className="alert alert-error">{error}</div>}
<button className="btn btn-primary btn-lg" disabled={busy}>{busy ? 'Erstelle Lobby …' : 'Lobby erstellen'}</button>
</form>
);
}
const STEP_TEXT = { ban: 'Ban', pick: 'Pick', side: 'Seite', decider: 'Entscheidungsmap' };
function FlowPreview({ flow }) {
return (
<ol className="flow-preview" aria-label="Veto-Ablauf">
{flow.map((raw, i) => {
const [type, actor] = raw.split(':');
return (
<li key={i} className={`flow-step flow-${type} ${actor ? `flow-${actor}` : ''}`}>
<span>{STEP_TEXT[type]}</span>
{actor && <small>{actor === 'X' ? 'Erstes Team' : 'Zweites Team'}</small>}
</li>
);
})}
</ol>
);
}