Require suggestion before entering vote phase
This commit is contained in:
2
API.md
2
API.md
@@ -13,7 +13,7 @@ GET /api/state — returns currentPhase (for caller), votesFinal, resultsOpen, u
|
|||||||
GET /api/me — id, displayName, username, isAdmin, currentPhase, votesFinal
|
GET /api/me — id, displayName, username, isAdmin, currentPhase, votesFinal
|
||||||
|
|
||||||
## Player (requires auth)
|
## Player (requires auth)
|
||||||
POST /api/me/phase/next — advance caller to next phase (Suggest→Vote→Results; Results gated by resultsOpen)
|
POST /api/me/phase/next — advance caller to next phase (Suggest→Vote requires at least one own suggestion; Vote→Results is gated by resultsOpen)
|
||||||
POST /api/me/phase/prev — admin-only move caller backward (Results→Vote→Suggest)
|
POST /api/me/phase/prev — admin-only move caller backward (Results→Vote→Suggest)
|
||||||
|
|
||||||
## Suggestions (requires auth + phase gating)
|
## Suggestions (requires auth + phase gating)
|
||||||
|
|||||||
@@ -61,6 +61,17 @@ public static class StateEndpoints
|
|||||||
var reconciled = EndpointHelpers.ReconcilePlayerPhase(player, appState.ResultsOpen);
|
var reconciled = EndpointHelpers.ReconcilePlayerPhase(player, appState.ResultsOpen);
|
||||||
var next = NextPhase(player.CurrentPhase);
|
var next = NextPhase(player.CurrentPhase);
|
||||||
|
|
||||||
|
if (next == Phase.Vote)
|
||||||
|
{
|
||||||
|
var hasSuggestions = await db.Suggestions.AnyAsync(s => s.PlayerId == player.Id);
|
||||||
|
if (!hasSuggestions)
|
||||||
|
{
|
||||||
|
if (reconciled)
|
||||||
|
await db.SaveChangesAsync();
|
||||||
|
return EndpointHelpers.BadRequestError("Add at least one suggestion before entering the Vote phase.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (next == Phase.Results && !appState.ResultsOpen)
|
if (next == Phase.Results && !appState.ResultsOpen)
|
||||||
{
|
{
|
||||||
if (reconciled)
|
if (reconciled)
|
||||||
@@ -108,4 +119,3 @@ public static class StateEndpoints
|
|||||||
_ => Phase.Suggest
|
_ => Phase.Suggest
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,13 +15,13 @@ public class AdminTests
|
|||||||
await using var factory = new TestWebApplicationFactory();
|
await using var factory = new TestWebApplicationFactory();
|
||||||
var admin = factory.CreateClientWithCookies();
|
var admin = factory.CreateClientWithCookies();
|
||||||
await admin.RegisterAsync("admin", admin: true);
|
await admin.RegisterAsync("admin", admin: true);
|
||||||
await admin.PostAsJsonAsync("/api/me/phase/next", new { }); // move to Vote
|
await admin.AdvanceToVoteAsync("Admin seed"); // move to Vote
|
||||||
|
|
||||||
var p1 = factory.CreateClientWithCookies();
|
var p1 = factory.CreateClientWithCookies();
|
||||||
await p1.RegisterAsync("alice");
|
await p1.RegisterAsync("alice");
|
||||||
var p2 = factory.CreateClientWithCookies();
|
var p2 = factory.CreateClientWithCookies();
|
||||||
await p2.RegisterAsync("bob");
|
await p2.RegisterAsync("bob");
|
||||||
await p2.PostAsJsonAsync("/api/me/phase/next", new { });
|
await p2.AdvanceToVoteAsync("Bob seed");
|
||||||
|
|
||||||
var s1 = await p1.CreateSuggestionAsync("A");
|
var s1 = await p1.CreateSuggestionAsync("A");
|
||||||
await p1.PostAsJsonAsync("/api/me/phase/next", new { });
|
await p1.PostAsJsonAsync("/api/me/phase/next", new { });
|
||||||
@@ -111,7 +111,7 @@ public class AdminTests
|
|||||||
var b = await player.CreateSuggestionAsync("Game B");
|
var b = await player.CreateSuggestionAsync("Game B");
|
||||||
|
|
||||||
await player.PostAsJsonAsync("/api/me/phase/next", new { });
|
await player.PostAsJsonAsync("/api/me/phase/next", new { });
|
||||||
await admin.PostAsJsonAsync("/api/me/phase/next", new { });
|
await admin.AdvanceToVoteAsync("Admin link seed");
|
||||||
|
|
||||||
var same = await admin.PostAsJsonAsync("/api/admin/link-suggestions", new
|
var same = await admin.PostAsJsonAsync("/api/admin/link-suggestions", new
|
||||||
{
|
{
|
||||||
@@ -147,7 +147,7 @@ public class AdminTests
|
|||||||
var a = await player.CreateSuggestionAsync("Game A");
|
var a = await player.CreateSuggestionAsync("Game A");
|
||||||
var b = await player.CreateSuggestionAsync("Game B");
|
var b = await player.CreateSuggestionAsync("Game B");
|
||||||
await player.PostAsJsonAsync("/api/me/phase/next", new { });
|
await player.PostAsJsonAsync("/api/me/phase/next", new { });
|
||||||
await admin.PostAsJsonAsync("/api/me/phase/next", new { });
|
await admin.AdvanceToVoteAsync("Admin unlink seed");
|
||||||
await admin.PostAsJsonAsync("/api/admin/link-suggestions", new
|
await admin.PostAsJsonAsync("/api/admin/link-suggestions", new
|
||||||
{
|
{
|
||||||
SourceSuggestionId = a,
|
SourceSuggestionId = a,
|
||||||
@@ -269,7 +269,7 @@ public class AdminTests
|
|||||||
await using var factory = new TestWebApplicationFactory();
|
await using var factory = new TestWebApplicationFactory();
|
||||||
var admin = factory.CreateClientWithCookies();
|
var admin = factory.CreateClientWithCookies();
|
||||||
await admin.RegisterAsync("admin", admin: true);
|
await admin.RegisterAsync("admin", admin: true);
|
||||||
await admin.PostAsJsonAsync("/api/me/phase/next", new { });
|
await admin.AdvanceToVoteAsync("Admin vote status seed");
|
||||||
|
|
||||||
var p1 = factory.CreateClientWithCookies();
|
var p1 = factory.CreateClientWithCookies();
|
||||||
await p1.RegisterAsync("alice");
|
await p1.RegisterAsync("alice");
|
||||||
@@ -277,7 +277,7 @@ public class AdminTests
|
|||||||
await p2.RegisterAsync("bob");
|
await p2.RegisterAsync("bob");
|
||||||
var s = await p1.CreateSuggestionAsync("Game");
|
var s = await p1.CreateSuggestionAsync("Game");
|
||||||
await p1.PostAsJsonAsync("/api/me/phase/next", new { });
|
await p1.PostAsJsonAsync("/api/me/phase/next", new { });
|
||||||
await p2.PostAsJsonAsync("/api/me/phase/next", new { });
|
await p2.AdvanceToVoteAsync("Bob vote seed");
|
||||||
await p1.PostAsJsonAsync("/api/votes", new
|
await p1.PostAsJsonAsync("/api/votes", new
|
||||||
{
|
{
|
||||||
SuggestionId = s,
|
SuggestionId = s,
|
||||||
@@ -300,7 +300,7 @@ public class AdminTests
|
|||||||
|
|
||||||
var p = factory.CreateClientWithCookies();
|
var p = factory.CreateClientWithCookies();
|
||||||
await p.RegisterAsync("player");
|
await p.RegisterAsync("player");
|
||||||
await p.PostAsJsonAsync("/api/me/phase/next", new { });
|
await p.AdvanceToVoteAsync("Player joker seed");
|
||||||
await p.PostAsJsonAsync("/api/votes/finalize", new { Final = true });
|
await p.PostAsJsonAsync("/api/votes/finalize", new { Final = true });
|
||||||
|
|
||||||
var give = await admin.PostAsJsonAsync("/api/admin/joker", new { playerId = (await p.GetProfileIdAsync()) });
|
var give = await admin.PostAsJsonAsync("/api/admin/joker", new { playerId = (await p.GetProfileIdAsync()) });
|
||||||
@@ -333,7 +333,7 @@ public class AdminTests
|
|||||||
});
|
});
|
||||||
Assert.Equal(HttpStatusCode.BadRequest, beforeVotePhase.StatusCode);
|
Assert.Equal(HttpStatusCode.BadRequest, beforeVotePhase.StatusCode);
|
||||||
|
|
||||||
await admin.PostAsJsonAsync("/api/me/phase/next", new { });
|
await admin.AdvanceToVoteAsync("Admin link-phase seed");
|
||||||
await player.PostAsJsonAsync("/api/me/phase/next", new { });
|
await player.PostAsJsonAsync("/api/me/phase/next", new { });
|
||||||
|
|
||||||
await player.PostAsJsonAsync("/api/votes", new
|
await player.PostAsJsonAsync("/api/votes", new
|
||||||
@@ -373,9 +373,9 @@ public class AdminTests
|
|||||||
var a = await p1.CreateSuggestionAsync("A");
|
var a = await p1.CreateSuggestionAsync("A");
|
||||||
var b = await p1.CreateSuggestionAsync("B");
|
var b = await p1.CreateSuggestionAsync("B");
|
||||||
|
|
||||||
await admin.PostAsJsonAsync("/api/me/phase/next", new { });
|
await admin.AdvanceToVoteAsync("Admin unfinalize seed");
|
||||||
await p1.PostAsJsonAsync("/api/me/phase/next", new { });
|
await p1.PostAsJsonAsync("/api/me/phase/next", new { });
|
||||||
await p2.PostAsJsonAsync("/api/me/phase/next", new { });
|
await p2.AdvanceToVoteAsync("P2 unfinalize seed");
|
||||||
|
|
||||||
await p1.PostAsJsonAsync("/api/votes/finalize", new { Final = true });
|
await p1.PostAsJsonAsync("/api/votes/finalize", new { Final = true });
|
||||||
await p2.PostAsJsonAsync("/api/votes/finalize", new { Final = true });
|
await p2.PostAsJsonAsync("/api/votes/finalize", new { Final = true });
|
||||||
@@ -400,7 +400,7 @@ public class AdminTests
|
|||||||
await using var factory = new TestWebApplicationFactory();
|
await using var factory = new TestWebApplicationFactory();
|
||||||
var admin = factory.CreateClientWithCookies();
|
var admin = factory.CreateClientWithCookies();
|
||||||
await admin.RegisterAsync("admin", admin: true);
|
await admin.RegisterAsync("admin", admin: true);
|
||||||
await admin.PostAsJsonAsync("/api/me/phase/next", new { });
|
await admin.AdvanceToVoteAsync("Admin unlink not-found seed");
|
||||||
|
|
||||||
var resp = await admin.PostAsJsonAsync("/api/admin/unlink-suggestions", new { suggestionId = 9999 });
|
var resp = await admin.PostAsJsonAsync("/api/admin/unlink-suggestions", new { suggestionId = 9999 });
|
||||||
resp.EnsureSuccessStatusCode();
|
resp.EnsureSuccessStatusCode();
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ public class ResultsTests
|
|||||||
await using var factory = new TestWebApplicationFactory();
|
await using var factory = new TestWebApplicationFactory();
|
||||||
var client = factory.CreateClientWithCookies();
|
var client = factory.CreateClientWithCookies();
|
||||||
await client.RegisterAsync("user");
|
await client.RegisterAsync("user");
|
||||||
await client.PostAsJsonAsync("/api/me/phase/next", new { });
|
await client.AdvanceToVoteAsync("Results locked seed");
|
||||||
var resp = await client.GetAsync("/api/results");
|
var resp = await client.GetAsync("/api/results");
|
||||||
Assert.Equal(System.Net.HttpStatusCode.BadRequest, resp.StatusCode);
|
Assert.Equal(System.Net.HttpStatusCode.BadRequest, resp.StatusCode);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,6 +91,7 @@ public class StateTests
|
|||||||
await using var factory = new TestWebApplicationFactory();
|
await using var factory = new TestWebApplicationFactory();
|
||||||
var client = factory.CreateClientWithCookies();
|
var client = factory.CreateClientWithCookies();
|
||||||
await client.RegisterAsync("advance");
|
await client.RegisterAsync("advance");
|
||||||
|
await client.CreateSuggestionAsync("Advance game");
|
||||||
|
|
||||||
await factory.WithDbContextAsync(async db =>
|
await factory.WithDbContextAsync(async db =>
|
||||||
{
|
{
|
||||||
@@ -121,6 +122,7 @@ public class StateTests
|
|||||||
await using var factory = new TestWebApplicationFactory();
|
await using var factory = new TestWebApplicationFactory();
|
||||||
var admin = factory.CreateClientWithCookies();
|
var admin = factory.CreateClientWithCookies();
|
||||||
await admin.RegisterAsync("admin", admin: true);
|
await admin.RegisterAsync("admin", admin: true);
|
||||||
|
await admin.CreateSuggestionAsync("Admin game");
|
||||||
|
|
||||||
await admin.PostAsJsonAsync("/api/me/phase/next", new { }); // Vote
|
await admin.PostAsJsonAsync("/api/me/phase/next", new { }); // Vote
|
||||||
await factory.WithDbContextAsync(async db =>
|
await factory.WithDbContextAsync(async db =>
|
||||||
@@ -143,6 +145,7 @@ public class StateTests
|
|||||||
await using var factory = new TestWebApplicationFactory();
|
await using var factory = new TestWebApplicationFactory();
|
||||||
var client = factory.CreateClientWithCookies();
|
var client = factory.CreateClientWithCookies();
|
||||||
await client.RegisterAsync("player");
|
await client.RegisterAsync("player");
|
||||||
|
await client.CreateSuggestionAsync("Player game");
|
||||||
|
|
||||||
var toVote = await client.PostAsync("/api/me/phase/next", JsonContent.Create(new { }));
|
var toVote = await client.PostAsync("/api/me/phase/next", JsonContent.Create(new { }));
|
||||||
toVote.EnsureSuccessStatusCode();
|
toVote.EnsureSuccessStatusCode();
|
||||||
@@ -152,6 +155,20 @@ public class StateTests
|
|||||||
Assert.Equal(HttpStatusCode.BadRequest, toResults.StatusCode);
|
Assert.Equal(HttpStatusCode.BadRequest, toResults.StatusCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Phase_next_from_suggest_requires_at_least_one_suggestion()
|
||||||
|
{
|
||||||
|
await using var factory = new TestWebApplicationFactory();
|
||||||
|
var client = factory.CreateClientWithCookies();
|
||||||
|
await client.RegisterAsync("nosuggest");
|
||||||
|
|
||||||
|
var response = await client.PostAsJsonAsync("/api/me/phase/next", new { });
|
||||||
|
|
||||||
|
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
||||||
|
var me = await client.GetFromJsonAsync<JsonElement>("/api/me");
|
||||||
|
Assert.Equal(nameof(Phase.Suggest), me.GetProperty("currentPhase").GetString());
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Admin_opening_results_moves_players_to_results_phase()
|
public async Task Admin_opening_results_moves_players_to_results_phase()
|
||||||
{
|
{
|
||||||
@@ -199,6 +216,7 @@ public class StateTests
|
|||||||
|
|
||||||
var admin = factory.CreateClientWithCookies();
|
var admin = factory.CreateClientWithCookies();
|
||||||
await admin.RegisterAsync("admin", admin: true);
|
await admin.RegisterAsync("admin", admin: true);
|
||||||
|
await admin.CreateSuggestionAsync("Admin phase game");
|
||||||
await admin.PostAsJsonAsync("/api/me/phase/next", new { }); // to Vote
|
await admin.PostAsJsonAsync("/api/me/phase/next", new { }); // to Vote
|
||||||
var back = await admin.PostAsJsonAsync("/api/me/phase/prev", new { });
|
var back = await admin.PostAsJsonAsync("/api/me/phase/prev", new { });
|
||||||
back.EnsureSuccessStatusCode();
|
back.EnsureSuccessStatusCode();
|
||||||
|
|||||||
@@ -602,6 +602,7 @@ public class SuggestionTests
|
|||||||
});
|
});
|
||||||
|
|
||||||
await owner.PostAsJsonAsync("/api/me/phase/next", new { }); // Vote
|
await owner.PostAsJsonAsync("/api/me/phase/next", new { }); // Vote
|
||||||
|
await other.CreateSuggestionAsync("Other vote seed");
|
||||||
await other.PostAsJsonAsync("/api/me/phase/next", new { });
|
await other.PostAsJsonAsync("/api/me/phase/next", new { });
|
||||||
await other.PostAsJsonAsync("/api/votes", new
|
await other.PostAsJsonAsync("/api/votes", new
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -49,4 +49,11 @@ internal static class TestClientExtensions
|
|||||||
var me = await client.GetFromJsonAsync<JsonElement>("/api/me");
|
var me = await client.GetFromJsonAsync<JsonElement>("/api/me");
|
||||||
return Guid.Parse(me.GetProperty("id").GetString()!);
|
return Guid.Parse(me.GetProperty("id").GetString()!);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static async Task AdvanceToVoteAsync(this HttpClient client, string suggestionName = "Seed game")
|
||||||
|
{
|
||||||
|
await client.CreateSuggestionAsync(suggestionName);
|
||||||
|
var response = await client.PostAsJsonAsync("/api/me/phase/next", new { });
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ public class VoteTests
|
|||||||
await using var factory = new TestWebApplicationFactory();
|
await using var factory = new TestWebApplicationFactory();
|
||||||
var client = factory.CreateClientWithCookies();
|
var client = factory.CreateClientWithCookies();
|
||||||
await client.RegisterAsync("invalid");
|
await client.RegisterAsync("invalid");
|
||||||
await client.PostAsJsonAsync("/api/me/phase/next", new { });
|
await client.AdvanceToVoteAsync("Invalid seed");
|
||||||
|
|
||||||
var resp = await client.PostAsJsonAsync("/api/votes", new
|
var resp = await client.PostAsJsonAsync("/api/votes", new
|
||||||
{
|
{
|
||||||
@@ -152,7 +152,7 @@ public class VoteTests
|
|||||||
await using var factory = new TestWebApplicationFactory();
|
await using var factory = new TestWebApplicationFactory();
|
||||||
var admin = factory.CreateClientWithCookies();
|
var admin = factory.CreateClientWithCookies();
|
||||||
await admin.RegisterAsync("admin", admin: true);
|
await admin.RegisterAsync("admin", admin: true);
|
||||||
await admin.PostAsJsonAsync("/api/me/phase/next", new { });
|
await admin.AdvanceToVoteAsync("Admin link seed");
|
||||||
|
|
||||||
var player = factory.CreateClientWithCookies();
|
var player = factory.CreateClientWithCookies();
|
||||||
await player.RegisterAsync("linker");
|
await player.RegisterAsync("linker");
|
||||||
@@ -189,7 +189,7 @@ public class VoteTests
|
|||||||
await using var factory = new TestWebApplicationFactory();
|
await using var factory = new TestWebApplicationFactory();
|
||||||
var admin = factory.CreateClientWithCookies();
|
var admin = factory.CreateClientWithCookies();
|
||||||
await admin.RegisterAsync("admin", admin: true);
|
await admin.RegisterAsync("admin", admin: true);
|
||||||
await admin.PostAsJsonAsync("/api/me/phase/next", new { });
|
await admin.AdvanceToVoteAsync("Admin chain seed");
|
||||||
|
|
||||||
var player = factory.CreateClientWithCookies();
|
var player = factory.CreateClientWithCookies();
|
||||||
await player.RegisterAsync("chain");
|
await player.RegisterAsync("chain");
|
||||||
|
|||||||
3
SPEC.md
3
SPEC.md
@@ -10,12 +10,13 @@ Help a small Discord group (4–8 players) pick a co-op game via phased flow:
|
|||||||
- Single shared instance
|
- Single shared instance
|
||||||
- Username/password login (cookie auth)
|
- Username/password login (cookie auth)
|
||||||
- Admins flagged via admin key at registration
|
- Admins flagged via admin key at registration
|
||||||
- Per-user phase tracking; admins can move themselves backward, everyone can move forward (subject to admin “results open” toggle)
|
- Per-user phase tracking; admins can move themselves backward, everyone can move forward (subject to admin “results open” toggle and Suggest→Vote requiring at least one own suggestion)
|
||||||
|
|
||||||
## Suggest Phase
|
## Suggest Phase
|
||||||
- Up to **5 suggestions** per player
|
- Up to **5 suggestions** per player
|
||||||
- Name required; optional genre, description, screenshot URL, YouTube URL, external game link, min/max players
|
- Name required; optional genre, description, screenshot URL, YouTube URL, external game link, min/max players
|
||||||
- Players see only their own suggestions until voting
|
- Players see only their own suggestions until voting
|
||||||
|
- A player can enter Vote only after submitting at least one own suggestion
|
||||||
- Screenshots validated as reachable images
|
- Screenshots validated as reachable images
|
||||||
|
|
||||||
## Vote Phase
|
## Vote Phase
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ Jeder Spieler durchläuft die Phasen unabhängig voneinander:
|
|||||||
**Vorschlagen → Abstimmen → Ergebnisse**
|
**Vorschlagen → Abstimmen → Ergebnisse**
|
||||||
|
|
||||||
Klicke auf **„Weiter"**, um fortzufahren. Admins können sich bei Bedarf auch wieder zurücksetzen.
|
Klicke auf **„Weiter"**, um fortzufahren. Admins können sich bei Bedarf auch wieder zurücksetzen.
|
||||||
|
In der **Vorschlagsphase** bleibt **„Weiter"** deaktiviert, bis dein Konto mindestens einen eigenen Spielvorschlag hat.
|
||||||
|
|
||||||
## Spiele vorschlagen
|
## Spiele vorschlagen
|
||||||
|
|
||||||
@@ -175,6 +176,10 @@ Stelle sicher:
|
|||||||
|
|
||||||
Warte auf die Abstimmungsphase und bitte bei Bedarf um einen Joker.
|
Warte auf die Abstimmungsphase und bitte bei Bedarf um einen Joker.
|
||||||
|
|
||||||
|
### „Füge mindestens einen Vorschlag hinzu, bevor du in die Abstimmungsphase wechselst."
|
||||||
|
|
||||||
|
Füge mit deinem aktuellen Konto mindestens einen Spielvorschlag hinzu. Erst dann kannst du von der Vorschlagsphase in die Abstimmungsphase wechseln.
|
||||||
|
|
||||||
### „Ungültiger Admin-Schlüssel."
|
### „Ungültiger Admin-Schlüssel."
|
||||||
|
|
||||||
Registriere dich erneut mit dem korrekten Schlüssel vom Host ‒ oder lasse das Feld leer, um ein normales Konto zu erstellen.
|
Registriere dich erneut mit dem korrekten Schlüssel vom Host ‒ oder lasse das Feld leer, um ein normales Konto zu erstellen.
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ Each player progresses independently through the phases:
|
|||||||
**Suggest → Vote → Results**
|
**Suggest → Vote → Results**
|
||||||
|
|
||||||
Click **"Next"** to move forward. Admins can move themselves backward if needed.
|
Click **"Next"** to move forward. Admins can move themselves backward if needed.
|
||||||
|
In the **Suggest** phase, **Next** stays disabled until your account has at least one own game suggestion.
|
||||||
|
|
||||||
## Suggesting Games
|
## Suggesting Games
|
||||||
|
|
||||||
@@ -179,6 +180,10 @@ Make sure:
|
|||||||
|
|
||||||
Wait for the Vote phase and request a joker if needed.
|
Wait for the Vote phase and request a joker if needed.
|
||||||
|
|
||||||
|
### "Add at least one suggestion before entering the Vote phase."
|
||||||
|
|
||||||
|
Add at least one game suggestion with your current account. Only then can you move from Suggest to Vote.
|
||||||
|
|
||||||
### "Invalid admin key."
|
### "Invalid admin key."
|
||||||
|
|
||||||
Register again using the correct key from the host ‒ or leave it blank to create a regular account.
|
Register again using the correct key from the host ‒ or leave it blank to create a regular account.
|
||||||
|
|||||||
@@ -26,6 +26,7 @@
|
|||||||
"counts.format": "Players: {players} • Suggestions: {suggestions} • Votes: {votes}",
|
"counts.format": "Players: {players} • Suggestions: {suggestions} • Votes: {votes}",
|
||||||
"nav.prev": "Back",
|
"nav.prev": "Back",
|
||||||
"nav.next": "Next",
|
"nav.next": "Next",
|
||||||
|
"nav.addSuggestionFirst": "Add a game first",
|
||||||
"nav.waitingForResults": "Waiting…",
|
"nav.waitingForResults": "Waiting…",
|
||||||
"nav.freezeTitle": "Ready to reveal?",
|
"nav.freezeTitle": "Ready to reveal?",
|
||||||
"nav.freezeHint": "Moving forward will freeze your suggestions. Game names become locked; only extra details stay editable.",
|
"nav.freezeHint": "Moving forward will freeze your suggestions. Game names become locked; only extra details stay editable.",
|
||||||
@@ -185,6 +186,7 @@
|
|||||||
"counts.format": "Spieler: {players} • Vorschläge: {suggestions} • Stimmen: {votes}",
|
"counts.format": "Spieler: {players} • Vorschläge: {suggestions} • Stimmen: {votes}",
|
||||||
"nav.prev": "Zurück",
|
"nav.prev": "Zurück",
|
||||||
"nav.next": "Weiter",
|
"nav.next": "Weiter",
|
||||||
|
"nav.addSuggestionFirst": "Zuerst ein Spiel vorschlagen",
|
||||||
"nav.waitingForResults": "Warten…",
|
"nav.waitingForResults": "Warten…",
|
||||||
"nav.freezeTitle": "Bereit zum Aufdecken?",
|
"nav.freezeTitle": "Bereit zum Aufdecken?",
|
||||||
"nav.freezeHint": "Beim Weitergehen werden deine Vorschläge eingefroren. Spielnamen werden gesperrt; nur Zusatzinfos bleiben bearbeitbar.",
|
"nav.freezeHint": "Beim Weitergehen werden deine Vorschläge eingefroren. Spielnamen werden gesperrt; nur Zusatzinfos bleiben bearbeitbar.",
|
||||||
|
|||||||
@@ -248,6 +248,16 @@ export function updatePhaseNav() {
|
|||||||
if (btn) btn.classList.toggle("hidden", !isAdmin);
|
if (btn) btn.classList.toggle("hidden", !isAdmin);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const suggestNext = $("nav-suggest-next");
|
||||||
|
if (suggestNext) {
|
||||||
|
const hasSuggestions = (state.mySuggestions?.length ?? 0) > 0;
|
||||||
|
const canAdvance = phase !== "Suggest" || hasSuggestions;
|
||||||
|
suggestNext.disabled = !canAdvance;
|
||||||
|
suggestNext.textContent = canAdvance
|
||||||
|
? t("nav.next")
|
||||||
|
: t("nav.addSuggestionFirst");
|
||||||
|
}
|
||||||
|
|
||||||
const voteNext = $("nav-vote-next");
|
const voteNext = $("nav-vote-next");
|
||||||
if (voteNext) {
|
if (voteNext) {
|
||||||
const locked = !state.resultsOpen && !isAdmin;
|
const locked = !state.resultsOpen && !isAdmin;
|
||||||
|
|||||||
Reference in New Issue
Block a user