Merge feature/einladungslinks-bei-teams in main

This commit is contained in:
mregosar committed 2026-11-01 20:09:36 +01:00
commit 35bdfc1f67
3 files changed
+44 -26

No files matched your search

+38 -26
View File
@@ -1,40 +1,52 @@
import CopyField from './CopyField.jsx';
function regenerate(act, target, label) {
if (!window.confirm(`Neuen Code für ${label} erzeugen? Der alte Link funktioniert dann nicht mehr. Bereits beigetretene Spieler bleiben im Team.`)) return;
act('codes/regenerate', { target }, `Neuer Code für ${label}`);
}
/** Einladungslink eines Teams, steht für den Organisator unten in der Team-Karte. */
export function TeamInvite({ state, side, act, busy }) {
const { codes, links } = state.organizer;
const open = !['finished', 'cancelled'].includes(state.status);
const name = state.teams[side].name;
return (
<div className={`invite team-invite team-${side}`}>
<div className="invite-head">
<strong>Einladung</strong>
<span className="invite-code">{codes[side]}</span>
{open && <button className="link-btn" disabled={!!busy} onClick={() => regenerate(act, side, name)}>Neuer Code</button>}
</div>
<CopyField value={links[side]} />
<p className="small muted">Führt nur in {name}. Nur an dieses Team weitergeben.</p>
</div>
);
}
export default function OrganizerPanel({ state, act, busy, matchId, token }) {
const { codes, links } = state.organizer;
const open = !['finished', 'cancelled'].includes(state.status);
const orgLink = `${window.location.origin}/m/${matchId}#org=${token}`;
const regen = (target, label) =>
window.confirm(`Neuen Code für ${label} erzeugen? Der alte Link funktioniert dann nicht mehr. Bereits beigetretene Spieler bleiben im Team.`) &&
act('codes/regenerate', { target }, `Neuer Code für ${label}`);
const label = 'Zuschauer (Stream, Kommentatoren)';
return (
<section className="panel organizer-panel">
<h2>Einladungen</h2>
<p className="small muted">Jeder Link führt nur in das jeweilige Team. Gib ihn ausschließlich an dieses Team weiter.</p>
{[
['A', state.teams.A.name, 'team-A'],
['B', state.teams.B.name, 'team-B'],
['spectator', 'Zuschauer (Stream, Kommentatoren)', 'spectator'],
].map(([key, label, cls]) => (
<div key={key} className={`invite ${cls}`}>
<div className="invite-head">
<strong>{label}</strong>
<span className="invite-code">{codes[key]}</span>
{open && <button className="link-btn" disabled={!!busy} onClick={() => regen(key, label)}>Neuer Code</button>}
</div>
<CopyField value={links[key]} />
{key === 'spectator' && (
<div className="stream-link">
<CopyField label="Stream-Übersicht (als Browserquelle in OBS, 1920×1080)" value={links.stream} />
<a className="btn btn-sm" href={links.stream} target="_blank" rel="noreferrer">Stream-Ansicht öffnen</a>
<CopyField label="Match-Übersicht (nur dieses Match, ohne Zuschauercode)" value={links.overview} />
<a className="btn btn-sm" href={links.overview} target="_blank" rel="noreferrer">Match-Übersicht öffnen</a>
</div>
)}
<p className="small muted">Die Einladungslinks der Teams stehen unten in der jeweiligen Team-Karte.</p>
<div className="invite spectator">
<div className="invite-head">
<strong>{label}</strong>
<span className="invite-code">{codes.spectator}</span>
{open && <button className="link-btn" disabled={!!busy} onClick={() => regenerate(act, 'spectator', label)}>Neuer Code</button>}
</div>
))}
<CopyField value={links.spectator} />
<div className="stream-link">
<CopyField label="Stream-Übersicht (als Browserquelle in OBS, 1920×1080)" value={links.stream} />
<a className="btn btn-sm" href={links.stream} target="_blank" rel="noreferrer">Stream-Ansicht öffnen</a>
<CopyField label="Match-Übersicht (nur dieses Match, ohne Zuschauercode)" value={links.overview} />
<a className="btn btn-sm" href={links.overview} target="_blank" rel="noreferrer">Match-Übersicht öffnen</a>
</div>
</div>
<details className="org-link">
<summary>Organisator-Zugang auf einem anderen Gerät öffnen</summary>
<p className="small muted">Dieser Link gibt volle Kontrolle über das Match. Nicht weitergeben.</p>
+2
View File
@@ -1,3 +1,4 @@
import { TeamInvite } from './OrganizerPanel.jsx';
export default function TeamCard({ team, side, state, viewer, isOrg, act, busy }) {
const isOwnTeam = viewer.team === side;
const inLobby = state.status === 'lobby';
@@ -59,6 +60,7 @@ export default function TeamCard({ team, side, state, viewer, isOrg, act, busy }
</div>
)}
{inLobby && !readiness.enough && <p className="small muted">Es fehlen noch {team.teamSize - readiness.count} Spieler.</p>}
{isOrg && state.organizer && <TeamInvite state={state} side={side} act={act} busy={busy} />}
</section>
);
}
+4
View File
@@ -775,3 +775,7 @@ input:focus, select:focus { border-color: var(--link); outline: none; }
.knife-connect { display: flex; flex-wrap: wrap; align-items: end; gap: 8px 12px; margin-top: 10px; }
.knife-connect > :first-child { flex: 1 1 260px; }
.knife-actions { margin-top: 10px; gap: 8px; flex-wrap: wrap; }
.team-invite { margin-top: 12px; padding-right: 0; }
.team-invite .copy-row { flex-wrap: wrap; }
.team-invite .copy-row code { flex: 1 1 100%; font-size: .78rem; white-space: normal; word-break: break-all; overflow: visible; }
.team-invite p { margin: 6px 0 0; }