126 lines
5.0 KiB
React
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>
|
|
);
|
|
}
|