Add player count fields with validation and labeled UX
This commit is contained in:
@@ -294,7 +294,7 @@ function setupHandlers() {
|
||||
$("suggest-form").addEventListener("submit", async (e) => {
|
||||
e.preventDefault();
|
||||
const form = e.target;
|
||||
const data = Object.fromEntries(new FormData(form).entries());
|
||||
const data = normalizeSuggestionForm(new FormData(form));
|
||||
if (!data.name) return toast("Name required", true);
|
||||
if (data.screenshotUrl && !isValidImageUrl(data.screenshotUrl)) {
|
||||
return toast("Screenshot URL must be http(s) and end with an image file.", true);
|
||||
@@ -406,6 +406,7 @@ function buildCard(s, { showAuthor = false, allowDelete = false, allowEdit = fal
|
||||
</div>
|
||||
${s.genre ? `<p class="muted">${s.genre}</p>` : ""}
|
||||
${s.description ? `<p>${s.description}</p>` : ""}
|
||||
${(s.minPlayers || s.maxPlayers) ? `<p class="muted">Players: ${s.minPlayers ?? "?"}–${s.maxPlayers ?? "?"}</p>` : ""}
|
||||
</div>
|
||||
`;
|
||||
if (hasImage) {
|
||||
@@ -470,7 +471,7 @@ function openEditModal(s) {
|
||||
const form = overlay.querySelector("#edit-form");
|
||||
form?.addEventListener("submit", async (e) => {
|
||||
e.preventDefault();
|
||||
const data = Object.fromEntries(new FormData(form).entries());
|
||||
const data = normalizeSuggestionForm(new FormData(form));
|
||||
if (data.screenshotUrl && !isValidImageUrl(data.screenshotUrl)) {
|
||||
return toast("Screenshot URL must be http(s) and end with an image file.", true);
|
||||
}
|
||||
@@ -535,3 +536,22 @@ function isValidImageUrl(url) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeSuggestionForm(formData) {
|
||||
const obj = Object.fromEntries(formData.entries());
|
||||
const parseNum = (v) => {
|
||||
if (v === undefined || v === null || v === "") return null;
|
||||
const n = Number(v);
|
||||
return Number.isFinite(n) ? n : null;
|
||||
};
|
||||
return {
|
||||
name: obj.name?.trim(),
|
||||
genre: obj.genre?.trim() || null,
|
||||
description: obj.description?.trim() || null,
|
||||
screenshotUrl: obj.screenshotUrl?.trim() || null,
|
||||
youtubeUrl: obj.youtubeUrl?.trim() || null,
|
||||
gameUrl: obj.gameUrl?.trim() || null,
|
||||
minPlayers: parseNum(obj.minPlayers),
|
||||
maxPlayers: parseNum(obj.maxPlayers),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -15,15 +15,33 @@
|
||||
<button class="ghost" data-auth-tab="register" type="button">Register</button>
|
||||
</div>
|
||||
<form id="login-form" class="stack auth-form" data-mode="login">
|
||||
<input id="login-username" name="username" maxlength="64" placeholder="Username" autocomplete="username" required />
|
||||
<input id="login-password" name="password" type="password" placeholder="Password" autocomplete="current-password" required />
|
||||
<label class="stack">
|
||||
<span class="label">Username</span>
|
||||
<input id="login-username" name="username" maxlength="64" autocomplete="username" required />
|
||||
</label>
|
||||
<label class="stack">
|
||||
<span class="label">Password</span>
|
||||
<input id="login-password" name="password" type="password" autocomplete="current-password" required />
|
||||
</label>
|
||||
<button type="submit">Log in</button>
|
||||
</form>
|
||||
<form id="register-form" class="stack auth-form hidden" data-mode="register">
|
||||
<input id="register-username" name="username" maxlength="64" placeholder="Username" autocomplete="username" required />
|
||||
<input id="register-password" name="password" type="password" placeholder="Password" autocomplete="new-password" required />
|
||||
<input id="register-displayName" name="displayName" maxlength="64" placeholder="Display name (shows to group)" required />
|
||||
<input id="register-adminkey" name="adminKey" type="password" maxlength="128" placeholder="Admin key (optional)" />
|
||||
<label class="stack">
|
||||
<span class="label">Username</span>
|
||||
<input id="register-username" name="username" maxlength="64" autocomplete="username" required />
|
||||
</label>
|
||||
<label class="stack">
|
||||
<span class="label">Password</span>
|
||||
<input id="register-password" name="password" type="password" autocomplete="new-password" required />
|
||||
</label>
|
||||
<label class="stack">
|
||||
<span class="label">Display name (shows to group)</span>
|
||||
<input id="register-displayName" name="displayName" maxlength="64" required />
|
||||
</label>
|
||||
<label class="stack">
|
||||
<span class="label">Admin key (optional)</span>
|
||||
<input id="register-adminkey" name="adminKey" type="password" maxlength="128" />
|
||||
</label>
|
||||
<button type="submit">Create account</button>
|
||||
</form>
|
||||
</section>
|
||||
@@ -47,17 +65,46 @@
|
||||
<h2>Suggest (up to 3)</h2>
|
||||
<p class="hint">Only you can see your suggestions until Reveal.</p>
|
||||
<form id="suggest-form" class="stack">
|
||||
<input name="name" required maxlength="100" placeholder="Game name *" />
|
||||
<input name="genre" maxlength="50" placeholder="Genre" />
|
||||
<textarea name="description" maxlength="500" placeholder="Short description"></textarea>
|
||||
<div class="stack horizontal">
|
||||
<input name="screenshotUrl" maxlength="2048" placeholder="Screenshot URL" />
|
||||
<input name="youtubeUrl" maxlength="2048" placeholder="YouTube URL" />
|
||||
<input name="gameUrl" maxlength="2048" placeholder="Game website URL" />
|
||||
</div>
|
||||
<button type="submit">Submit</button>
|
||||
</form>
|
||||
</div>
|
||||
<label class="stack">
|
||||
<span class="label">Game name *</span>
|
||||
<input name="name" required maxlength="100" />
|
||||
</label>
|
||||
<label class="stack">
|
||||
<span class="label">Genre</span>
|
||||
<input name="genre" maxlength="50" />
|
||||
</label>
|
||||
<label class="stack">
|
||||
<span class="label">Description</span>
|
||||
<textarea name="description" maxlength="500"></textarea>
|
||||
</label>
|
||||
<div class="stack">
|
||||
<span class="label">Players</span>
|
||||
<div class="stack horizontal">
|
||||
<label class="stack">
|
||||
<span class="label">Min</span>
|
||||
<input name="minPlayers" type="number" min="1" max="32" inputmode="numeric" />
|
||||
</label>
|
||||
<label class="stack">
|
||||
<span class="label">Max</span>
|
||||
<input name="maxPlayers" type="number" min="1" max="32" inputmode="numeric" />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<label class="stack">
|
||||
<span class="label">Screenshot URL</span>
|
||||
<input name="screenshotUrl" maxlength="2048" />
|
||||
</label>
|
||||
<label class="stack">
|
||||
<span class="label">YouTube URL</span>
|
||||
<input name="youtubeUrl" maxlength="2048" />
|
||||
</label>
|
||||
<label class="stack">
|
||||
<span class="label">Game website URL</span>
|
||||
<input name="gameUrl" maxlength="2048" />
|
||||
</label>
|
||||
<button type="submit">Submit</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="card subcard">
|
||||
<h3>Your suggestions</h3>
|
||||
<div id="my-suggestions" class="card-grid"></div>
|
||||
|
||||
Reference in New Issue
Block a user