Multiple admins
Adds Player.is_admin (creator starts with it; backfilled by migration). Admin-key-authenticated POST /admin/set-admin grants/revokes it from the admin panel; the creator's privileges can't be revoked. Admin-gated backend routes (dev mode, skip character creation) and frontend controls (corner menu, lobby start, scene start, end story) now check is_admin instead of is_creator, and admins get the admin_key in their state blob so granted players can open the admin panel. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -37,7 +37,8 @@
|
||||
<div class="player-chip {p.id === state.player.id ? 'current-user' : ''}">
|
||||
<span class="avatar-icon">🐀</span>
|
||||
<span class="name">{p.name}</span>
|
||||
{#if p.is_creator}<span class="creator-badge">Creator</span>{/if}
|
||||
{#if p.is_creator}<span class="creator-badge">Creator</span>
|
||||
{:else if p.is_admin}<span class="creator-badge">Admin</span>{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
@@ -54,9 +55,9 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if state.player.is_creator}
|
||||
{#if state.player.is_admin}
|
||||
<div class="link-item admin-link-item">
|
||||
<h4 class="gold-text">⚠️ Creator Admin Magic Link:</h4>
|
||||
<h4 class="gold-text">⚠️ Admin Magic Link:</h4>
|
||||
<p class="info-text">Save this admin link! You can use it to recover character sheets if another player loses their connection or link.</p>
|
||||
<div class="copy-container">
|
||||
<input type="text" readonly value="{window.location.origin}/#/game/{state.game.id}/admin" class="copy-input admin-input" id="admin-link-copy">
|
||||
@@ -72,7 +73,7 @@
|
||||
</div>
|
||||
|
||||
<div class="lobby-action">
|
||||
{#if state.player.is_creator}
|
||||
{#if state.player.is_admin}
|
||||
{#if state.players.length >= 1}
|
||||
<button on:click={startGame}
|
||||
disabled={starting}
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
$: allRolesAssigned = state.players.every(p => p.role !== null || p.needs_reroll);
|
||||
$: deepPlayer = state.players.find(p => p.role === 'deep');
|
||||
$: piratPlayer = state.players.find(p => p.role === 'pirat');
|
||||
$: canStart = allRolesAssigned && deepPlayer && piratPlayer && (state.player.is_creator || state.player.role === 'deep');
|
||||
$: canStart = allRolesAssigned && deepPlayer && piratPlayer && (state.player.is_admin || state.player.role === 'deep');
|
||||
</script>
|
||||
|
||||
<div class="scene-setup-view text-center">
|
||||
|
||||
@@ -344,7 +344,7 @@
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
{#if state.player.is_creator && state.game.phase === 'between_scenes'}
|
||||
{#if state.player.is_admin && state.game.phase === 'between_scenes'}
|
||||
<div class="end-story-box">
|
||||
<p class="info-text" style="font-size: 0.85rem;">All good stories end. When the crew agrees the legend is complete:</p>
|
||||
<button on:click={finishGame} class="btn btn-danger">🏴☠️ End the Story</button>
|
||||
|
||||
@@ -40,6 +40,19 @@
|
||||
data = null;
|
||||
}
|
||||
}
|
||||
|
||||
async function setAdmin(playerId, makeAdmin) {
|
||||
try {
|
||||
await apiRequest(`/game/${gameId}/admin/set-admin`, 'POST', {
|
||||
key: adminKey,
|
||||
target_player_id: playerId,
|
||||
is_admin: makeAdmin,
|
||||
});
|
||||
await loadAdminData();
|
||||
} catch (err) {
|
||||
error = err.message;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="admin-page">
|
||||
@@ -71,6 +84,7 @@
|
||||
<th>Role</th>
|
||||
<th>Status</th>
|
||||
<th>Rank</th>
|
||||
<th>Admin</th>
|
||||
<th>Re-join Link</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -92,6 +106,15 @@
|
||||
{/if}
|
||||
</td>
|
||||
<td>{p.rank}</td>
|
||||
<td>
|
||||
{#if p.is_creator}
|
||||
🗝 Creator
|
||||
{:else if p.is_admin}
|
||||
<button on:click={() => setAdmin(p.id, false)} class="btn btn-secondary btn-small">Revoke Admin</button>
|
||||
{:else}
|
||||
<button on:click={() => setAdmin(p.id, true)} class="btn btn-secondary btn-small">Grant Admin</button>
|
||||
{/if}
|
||||
</td>
|
||||
<td>
|
||||
<div class="link-copy-action">
|
||||
<a href={rejoinLink(p.id)} class="btn btn-secondary btn-small">Open</a>
|
||||
|
||||
@@ -94,9 +94,14 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Admins get the admin key in their state blob; stash it so the Admin page works for them too
|
||||
$: if (state?.player?.is_admin && state?.game?.admin_key) {
|
||||
localStorage.setItem(`admin_key_${gameId}`, state.game.admin_key);
|
||||
}
|
||||
|
||||
$: {
|
||||
const items = [];
|
||||
if (state?.player?.is_creator) {
|
||||
if (state?.player?.is_admin) {
|
||||
items.push({
|
||||
label: `🧪 Dev Mode: ${state.game.dev_mode ? 'On' : 'Off'}`,
|
||||
action: toggleDevMode,
|
||||
|
||||
Reference in New Issue
Block a user