Add player name input and default value handling; enhance loading shimmer effect

This commit is contained in:
2026-04-03 00:28:14 -05:00
parent f012aa3dc4
commit 426adc3043
4 changed files with 222 additions and 5 deletions
+29 -5
View File
@@ -2295,6 +2295,7 @@
const myRunsSection = qs('run-my-runs-section');
const savedRunsListEl = qs('run-saved-runs-list');
const saveAccountBtn = qs('run-save-account');
const playerNameInput = qs('run-player-name');
function setRunFormStatus(message, isError, isSuccess){
if(!formStatusEl) return;
@@ -2309,6 +2310,23 @@
listStatusEl.classList.toggle('error-text', !!isError);
}
function showRunSubmissionsShimmer(){
if(!submissionsEl) return;
submissionsEl.textContent = '';
for(let i = 0; i < 4; i += 1){
const card = document.createElement('article');
card.className = 'submission-card submission-card--shimmer';
card.setAttribute('aria-hidden', 'true');
submissionsEl.appendChild(card);
}
}
function applyRunPlayerDefault(){
if(!playerNameInput || !fedlServerUsername) return;
if(String(playerNameInput.value || '').trim() !== '') return;
playerNameInput.value = fedlServerUsername;
}
function renderRunSubmissions(runs){
submissionsEl.innerHTML = '';
if(!runs.length){
@@ -2417,6 +2435,9 @@
levelOptionsEl.innerHTML = titles.map(title=>`<option value="${escapeAttr(title)}"></option>`).join('');
}).catch(err=>console.error(err));
showRunSubmissionsShimmer();
setRunListStatus('Loading recent submissions…');
loadRuns().then(runs=>{
const sortedRuns = runs.slice().sort((a,b)=>new Date(b.submittedAt) - new Date(a.submittedAt));
renderRunSubmissions(sortedRuns);
@@ -2450,6 +2471,7 @@
document.addEventListener('fedl-auth-updated', ()=>{
renderMySavedRuns();
applyRunPlayerDefault();
});
form.addEventListener('submit', function(event){
@@ -2483,6 +2505,7 @@
}
clearRunsCache();
form.reset();
applyRunPlayerDefault();
const okMsg = fedlServerUsername
? `Run submitted successfully. It is linked to your account (${fedlServerUsername}) for moderators.`
: 'Run submitted successfully. The admin panel can review it now.';
@@ -2506,6 +2529,7 @@
.finally(()=>{
renderMySavedRuns();
fedlUpdateAuthNav();
applyRunPlayerDefault();
});
loadRunPage();
@@ -2561,9 +2585,9 @@
setSignupStatus('Account created successfully. Loading your data…', 'success');
return fedlPullUserStateToLocal(data.userId);
}).then(()=>{
setSignupStatus('You are signed in. Redirecting to the roulette page…', 'success');
setSignupStatus('You are signed in. Redirecting to the home page…', 'success');
setTimeout(()=>{
window.location.href = 'roulette.html';
window.location.href = 'index.html';
}, FEDL_AUTH_REDIRECT_MS);
}).catch(err=>{
console.error(err);
@@ -2612,9 +2636,9 @@
setLoginStatus('Signed in successfully. Loading your data…', 'success');
return fedlPullUserStateToLocal(data.userId);
}).then(()=>{
setLoginStatus('Welcome back. Redirecting to the roulette page…', 'success');
setLoginStatus('Welcome back. Redirecting to the home page…', 'success');
setTimeout(()=>{
window.location.href = 'roulette.html';
window.location.href = 'index.html';
}, FEDL_AUTH_REDIRECT_MS);
}).catch(err=>{
console.error(err);
@@ -2628,7 +2652,7 @@
fedlRefreshAuthState().finally(()=>{
fedlUpdateAuthNav();
if ((page === 'signup' || page === 'login') && fedlServerUsername) {
window.location.replace('roulette.html');
window.location.replace('index.html');
}
});
+1
View File
@@ -38,6 +38,7 @@
<label class="control-block" for="run-player-name">
<span>Player Name</span>
<input id="run-player-name" name="playerName" type="text" placeholder="Your in-game name" required />
<span class="muted small">When you are logged in, your account username is filled here automatically (you can change it).</span>
</label>
<label class="control-block" for="run-level-title">
+181
View File
@@ -0,0 +1,181 @@
# FEDL HTTP server
Node.js static file + JSON API server for the FEDL site. It serves the repo root (`../`) as files, exposes REST-style JSON endpoints, and broadcasts **Server-Sent Events (SSE)** when list or run data changes.
## Running
From the **repository root**:
```bash
node server/server.js
```
Or from `server/`:
```bash
node server.js
```
### Environment variables
| Variable | Default | Purpose |
|----------|---------|---------|
| `PORT` | `8090` | Listen port |
| `HOST` | `127.0.0.1` | Bind address |
| `ADMIN_PASSWORD` | _(empty)_ | If set, **HTTP Basic** password for admin-only routes (username can be anything; password must match) |
| `AREDL_ACCESS_TOKEN` | _(empty)_ | Bearer token for AREDL import APIs |
| `AREDL_API_KEY` | _(empty)_ | API key header for AREDL import APIs |
Console output shows the effective `PORT`, `HOST`, data paths, and whether admin protection is enabled.
## Base path
All HTTP handling assumes a **URL prefix** of `/fedl`. Incoming paths are normalized: if the request path starts with `/fedl`, that prefix is stripped and the remainder is used for routing and static files.
**Examples** (with defaults, no reverse proxy):
- Site root (often `index.html`): `http://127.0.0.1:8090/fedl` or `http://127.0.0.1:8090/fedl/`
- API list: `http://127.0.0.1:8090/fedl/api/list`
- Events: `http://127.0.0.1:8090/fedl/events`
If you mount the app at a different path in production, change the `BASE` constant in `server.js` to match.
## CORS
Responses to API routes set permissive CORS headers (`Access-Control-Allow-Origin: *`, allowed methods **GET**, **POST**, **PUT**, **DELETE**, **HEAD**, **OPTIONS**, and `Authorization` + `Content-Type` allowed). Browsers may send a preflight **OPTIONS** request; the server answers **204**.
## Data files (under `server/`)
| File | Role |
|------|------|
| `data.txt` | Pipe-separated demon list source (`category\|position\|title\|url` lines) |
| `runs.json` | Run submission queue (moderation) |
| `users.json` | Registered FEDL accounts (hashed passwords) |
| `sessions.json` | Bearer session tokens + expiry |
| `userdata.json` | Per-user synced state (roulette, list %, saved runs, roulette slots) |
These files are created on demand where noted below. Use `.gitignore` for `users.json`, `sessions.json`, and `userdata.json` if they contain real data.
## Server-Sent Events
### `GET /events`
- **Response:** `text/event-stream`, `Cache-Control: no-cache`, `Connection: keep-alive`
- **Initial line:** `retry: 3000`
- **Events:** `list-update`, `runs-update` with JSON payloads (e.g. `{ updatedAt: "ISO-8601" }`)
Clients reconnect using the `retry` hint after drops.
## Public JSON API
Unless noted, bodies are **JSON** with `Content-Type: application/json`. Errors use `{ "error": "message" }` when applicable.
### Demon list
#### `GET /api/list`
- **200:** `{ "items": [ { "level", "position", "title", "url" } ], "text": "raw file text" }`
- **500:** could not read `data.txt`
#### `PUT /api/list`
- **Auth:** Admin Basic (if `ADMIN_PASSWORD` is set)
- **Body:** `{ "text": "full data.txt content" }`
- **200:** `{ "ok": true }` — writes `data.txt`, emits `list-update`
### Run queue
#### `GET /api/runs`
- **200:** `{ "items": [ run, ... ] }` — each run includes fields from `normalizeRun` (see below)
#### `POST /api/runs`
- **Optional:** `Authorization: Bearer <session-token>` — associates submission with account (`accountUserId`, `accountUsername` on stored run)
- **Body:** `{ "playerName", "levelTitle", "videoUrl", "percent", "rawFootageUrl?", "notes?" }` (required fields enforced server-side)
- **201:** `{ "ok": true, "item": run }`
- **400:** validation / invalid JSON
Run objects include (among others): `id`, `playerName`, `levelTitle`, `videoUrl`, `percent`, `rawFootageUrl`, `notes`, `status`, `reviewedBy`, `reviewNotes`, `submittedAt`, `updatedAt`, `accountUserId`, `accountUsername`.
#### `PUT /api/runs/:id` / `DELETE /api/runs/:id`
- **Auth:** Admin Basic
- **PUT body:** fields merged via `normalizeRun` with existing run
200 / 404 / 400 as appropriate; emits `runs-update` on success
#### `POST /api/runs/bulk-approve`
- **Auth:** Admin Basic
- **Body:** `{ "playerName": "exact match", "reviewNotes?": "..." }`
- **200:** `{ "ok": true, "approved": number, "playerName": "..." }` — approves all **pending** runs whose `playerName` matches (case-insensitive); may emit `runs-update`
### Auth (FEDL user accounts)
#### `POST /api/auth/signup`
- **Body:** `{ "username", "password" }` — username normalized to lowercase; pattern 324 chars `[a-z0-9_]`, password min 8 chars
- **201:** `{ "ok": true, "token", "userId", "username" }`
- **400 / 409:** validation or username taken
#### `POST /api/auth/login`
- **Body:** `{ "username", "password" }`
- **200:** `{ "ok": true, "token", "userId", "username" }`
- **401:** invalid credentials
#### `POST /api/auth/logout`
- **Optional:** `Authorization: Bearer <token>` — revokes that session
- **200:** `{ "ok": true }`
#### `GET /api/auth/me`
- **Header:** `Authorization: Bearer <token>`
- **200:** `{ "userId", "username" }`
- **401:** invalid or expired token
### User state (synced client storage)
#### `GET /api/user/state`
- **Header:** `Authorization: Bearer <token>`
- **200:** `{ "data": { "roulettePick", "levelPercents", "savedRuns", "rouletteSlots" } }` — shapes are normalized on read; missing keys default safely
- **401:** not signed in
#### `PUT /api/user/state`
- **Header:** `Authorization: Bearer <token>`
- **Body:** `{ "data": { ...same fields as above... } }` — server sanitizes `savedRuns` and `rouletteSlots` (size limits and field trimming)
- **200:** `{ "ok": true }`
- **401 / 400**
### Imports (admin + external APIs)
#### `POST /api/import/pointercrate`
- **Auth:** Admin Basic
- Fetches Pointercrate records, maps to run shape, appends to `runs.json`
- **200:** summary object with counts (see implementation)
#### `POST /api/import/aredl`
- **Auth:** Admin Basic
- Requires `AREDL_ACCESS_TOKEN` or `AREDL_API_KEY` configured
- **200:** summary / **500** on configuration or API errors
#### `POST /api/import/targeted`
- **Auth:** Admin Basic
- **Body:** `{ "source": "pointercrate" | "aredl", "filter": "player" | "level", "query": "string" }`
- Filters remote records and appends matching runs
## Static files
For **GET** / **HEAD** requests that do not match an API or `/events` handler, the server maps the path (after `BASE` strip) to files under the **repository root** (`../` from `server/`). Default document for `/` is `index.html`.
Unsupported methods receive **405**.
## Options preflight
`OPTIONS` on any path: **204** with CORS headers.
+11
View File
@@ -267,6 +267,17 @@ table.levels-table .btn{padding:9px 14px}
.run-my-runs-head{margin-bottom:14px}
.run-saved-card-actions{display:flex;flex-wrap:wrap;gap:10px;margin-top:12px}
.submission-card{padding:18px;border-radius:18px;background:rgba(255,255,255,0.04);border:1px solid rgba(255,255,255,0.08)}
@keyframes fedl-shimmer{
0%{background-position:100% 0}
100%{background-position:-100% 0}
}
.submission-card--shimmer{
min-height:118px;
background:linear-gradient(90deg,rgba(255,255,255,0.04) 0%,rgba(255,255,255,0.1) 45%,rgba(255,255,255,0.04) 90%);
background-size:200% 100%;
animation:fedl-shimmer 1.15s ease-in-out infinite;
pointer-events:none
}
.submission-card-top{display:flex;align-items:center;justify-content:space-between;gap:12px}
.submission-card strong{font-size:1.05rem}
.submission-card p{margin:10px 0 0 0;color:#dce8f5;line-height:1.55}