Ein Aufrufer darf Organisator- und Spieler-Zugang kombinieren (Authorization + X-Player-Token). Berechtigungen prüfen jetzt einzelne Merkmale statt einer Rangstufe. In der Lobby gibt es „Selbst mitspielen?“, Teamlinks funktionieren auch im Organisator-Browser. Online-Status wird direkt nach dem Beitritt aktualisiert.
65 lines
2.9 KiB
React
65 lines
2.9 KiB
React
export default function TeamCard({ team, side, state, viewer, isOrg, act, busy }) {
|
||
const isOwnTeam = viewer.team === side;
|
||
const inLobby = state.status === 'lobby';
|
||
const me = team.players.find((p) => p.isYou);
|
||
const emptySlots = Math.max(0, team.teamSize - team.players.length);
|
||
const canManageCaptain = isOrg || (viewer.isCaptain && isOwnTeam);
|
||
const { readiness } = team;
|
||
|
||
return (
|
||
<section className={`team-card team-${side} ${readiness.ready ? 'is-ready' : ''}`} aria-label={`Team ${team.name}`}>
|
||
<header>
|
||
<h2>{team.name}</h2>
|
||
<span className={`ready-state ${readiness.ready ? 'on' : ''}`}>
|
||
{readiness.ready ? 'Bereit' : `${readiness.count}/${team.teamSize}`}
|
||
</span>
|
||
</header>
|
||
|
||
<ul className="roster">
|
||
{team.players.map((p) => (
|
||
<li key={p.id} className={`${p.isYou ? 'is-you' : ''}`}>
|
||
<span className={`presence ${p.online ? 'on' : ''}`} title={p.online ? 'Online' : 'Offline'} />
|
||
<span className="nick">
|
||
{p.nickname}
|
||
{p.isYou && <small> (du)</small>}
|
||
</span>
|
||
{p.isCaptain && <span className="captain" title="Captain – führt das Veto">Captain</span>}
|
||
{inLobby && <span className={`ready-dot ${p.ready ? 'on' : ''}`}>{p.ready ? 'bereit' : 'wartet'}</span>}
|
||
{(canManageCaptain && !p.isCaptain) || (isOrg && inLobby && !p.isYou) ? (
|
||
<span className="row-actions">
|
||
{canManageCaptain && !p.isCaptain && (
|
||
<button className="link-btn" disabled={!!busy} onClick={() => act('captain', { playerId: p.id }, `${p.nickname} ist jetzt Captain`)}>
|
||
Zum Captain machen
|
||
</button>
|
||
)}
|
||
{isOrg && inLobby && !p.isYou && (
|
||
<button className="link-btn danger" disabled={!!busy} onClick={() => act('kick', { playerId: p.id }, `${p.nickname} entfernt`)}>
|
||
Entfernen
|
||
</button>
|
||
)}
|
||
</span>
|
||
) : null}
|
||
</li>
|
||
))}
|
||
{Array.from({ length: emptySlots }, (_, i) => (
|
||
<li key={`empty-${i}`} className="slot-empty">Freier Platz</li>
|
||
))}
|
||
</ul>
|
||
|
||
{me && inLobby && (
|
||
<div className="team-actions">
|
||
<button className={`btn ${me.ready ? '' : 'btn-primary'}`} disabled={!!busy}
|
||
onClick={() => act('ready', { ready: !me.ready })}>
|
||
{me.ready ? 'Doch nicht bereit' : 'Ich bin bereit'}
|
||
</button>
|
||
<button className="link-btn" disabled={!!busy}
|
||
onClick={() => window.confirm('Lobby wirklich verlassen?') && act('leave', null, 'Du hast das Team verlassen')}>
|
||
Team verlassen
|
||
</button>
|
||
</div>
|
||
)}
|
||
{inLobby && !readiness.enough && <p className="small muted">Es fehlen noch {team.teamSize - readiness.count} Spieler.</p>}
|
||
</section>
|
||
);
|
||
}
|