Commit Graph
41 Commits
Author SHA1 Message Date
Frank 98cf48b29d Make admin panel usable on mobile
The sidebar was a fixed 220px flex column that left barely any room
for content on a phone, and tables had no min-width so columns just
squeezed into unreadable slivers instead of scrolling.

Moves the sidebar's layout rules out of inline styles (which media
queries can't override) into real CSS classes, and collapses it into
a horizontally-scrollable pill bar below 768px so the content area
gets the full screen width. Tables now get a sensible min-width so
they scroll horizontally on narrow screens instead of squishing, and
the per-player tab bar on the session log view scrolls too.
2026-08-10 14:58:12 +02:00
Frank f6df9f7ba6 Show friendly session status labels on the player dashboard
Players saw the raw enum value (e.g. "created") in the sessions
table, which doesn't mean anything to them. Adds SessionStatus::label()
mapping each status to a player-facing description like "Waiting for
players".
2026-08-10 14:58:01 +02:00
Frank 1be07440e3 Bring back 1-minute ready-status expiry with live per-player broadcasts
A player's own browser now starts a 60s timer the moment they check
"ready" (with a visible countdown) and proactively tells the server
when it's up via a new expire_ready action - idempotent, so it only
actually clears the status if the deadline has genuinely passed.
Everyone else finds out live through a Mercure player_ready broadcast
that now carries which player and their new ready/not-ready state
(previously the broadcast payload's `ready` flag was always false due
to a stale-variable bug, though nothing consumed it yet).

checkAllPlayersReady() still does the same expiry check server-side
and broadcasts on anyone else's request in the meantime, so a stalled
frontend timer doesn't leave a stale "ready" badge showing forever.

This only affects the pre-PLAYING ready phase: checkAllPlayersReady()
still flips the session to PLAYING and stops touching ready state the
instant everyone is simultaneously ready, so the earlier fix (a reload
should never send an already-started game back to the waiting room)
is unaffected.

Also fixed the ready checkbox itself: unchecking it submitted a POST
without `toggle_ready` in the body (unchecked checkboxes aren't sent),
so un-readying silently did nothing server-side. Added the standard
hidden-fallback-input pattern to fix it.
2026-07-11 23:45:41 +02:00
Frank c28abef5b7 Block unverified users from marking themselves ready
GameDashboardService::toggleReady() now rejects the toggle if the
user's email isn't verified (mirrors the same gate UserChecker already
applies at login). The waiting-room checkbox is disabled client-side
with an explanatory alert and a link to resend the verification email,
and the controller adds a flash error as a server-side fallback if it
somehow gets submitted anyway.
2026-07-11 23:25:50 +02:00
Frank 886208c5c0 Add profile page for changing password and email
New /profile route (nav link between Admin and Logout) with two
independent forms, both requiring the current password before
applying a change:
- Change password: standard current/new/repeat flow, same password
  strength rules as registration.
- Change email: re-checks the new address isn't already taken (avoids
  a 500 from the unique constraint), then marks the account
  unverified and re-sends the confirmation email via the existing
  EmailVerifier/verify-email flow, same as registration. The current
  session stays logged in, but the user needs to verify the new
  address before their next login (UserChecker already blocks
  unverified accounts).
2026-07-11 23:23:38 +02:00
Frank 3c58e153dc Soft-delete users instead of hard-deleting, add last login tracking
Fixes the 500 on admin user deletion: deleting a user with an
email_log row (i.e. basically anyone who received any email) hit an
unhandled ForeignKeyConstraintViolationException, since email_log,
reset_password_request and player all have non-nullable FKs to user
with no cascade at the DB level.

Instead of cascading the delete (which would be fine for email_log
and reset_password_request but risky for player - removing a player
row could corrupt other real players' session state), admin delete
now sets a deletedAt timestamp instead of removing the row:
- UserChecker blocks login for deleted users (checkPreAuth).
- EmailLoggerListener rejects the message before send for deleted
  recipients (checked at actual send time, not at queue time).
- Added a last_login_at column + a LoginSuccessEvent listener to
  populate it, and surfaced both last login and status in the admin
  users list.

Added `app:users:purge-deleted`, a command intended to run on a
schedule that permanently removes users who were soft-deleted more
than 3 months ago and never played a game (join to Player via a
NOT EXISTS subquery). For that eventual hard-delete to actually
succeed, email_log and reset_password_request now cascade-delete at
the DB level (migration drops+recreates both FK constraints with ON
DELETE CASCADE) - player intentionally still isn't cascaded, so a
user with game history can never be purged this way even by mistake.
2026-07-11 23:09:52 +02:00
Frank cb2e945419 Remove 60-second ready-status expiry so the group can always start
Ready status previously expired 60 seconds after a player checked the
box, evaluated per-player against their own timestamp. Unless every
player happened to click ready within the same 60-second window, the
earliest player's readiness would silently expire before the last one
joined, so the session could get stuck on "Waiting for all players to
be ready" indefinitely, especially after a reload re-triggered the
timeout check.

Ready state is now durable: once checked, it stays until the player
unchecks it or the whole group is simultaneously ready, at which point
the session always transitions to PLAYING regardless of how long that
took. session.timer is still only ever set once during that one-way
READY -> PLAYING transition, so a reload never restarts or desyncs the
countdown between players.
2026-07-11 21:48:32 +02:00
Frank 3984a33282 Add win/lose game-ending flow
Removing all 3 locked files while the timer is still running now marks
the session WON immediately (checked right after every successful rm)
and broadcasts a "game_finished" signal over Mercure so every
connected player gets redirected together, not just the one who
removed the last file. A new /won/{session} route + won.html.twig
mirrors the existing lost flow (victory narrative + the same feedback
form).

The existing timer-expiry path already set LOST but always redirected
to lostUrl regardless of actual status; it now picks won/lost based on
the status the server reports.

Also fixes a pre-existing bug on the lost page (and would-be bug on
the new won page): PlayerService::GetCurrentlyActiveAsPlayer() only
matches players in READY/PLAYING sessions, so by the time a session
has ended it always returned null there, silently breaking the
feedback form. Both pages now look the player up directly via
PlayerRepository instead.

Added a navigatingAway flag so the page's "confirm before leaving"
prompt doesn't block our own win/lose redirects.
2026-07-11 21:41:16 +02:00
Frank eee6c3a369 Auto-trigger locked-files restoration exactly at the 60s deadline
Previously the restore check only ran lazily on a player's next
message, so files could stay wrongly-removed for an arbitrary amount
of time after the window expired. The frontend now schedules a
setTimeout (using the server-provided deadline, mirroring the terminal
lock's reveal timer) that pings the backend right at the deadline so
the check runs promptly regardless of player activity. Restored via
data-files-removal-deadline on page load too, so a refresh mid-window
doesn't lose the timer.
2026-07-11 17:45:08 +02:00
Frank 6db9d42852 Wire up decode puzzle chain and lock terminal on file removal
Connects the previously-disconnected /decode command to per-player
messages: player 1 unlocks sudo for everyone, player 2 unlocks a scan
command that reveals which files the AI virus has locked, player 3
learns those files should be removed.

Also adds a retaliation mechanic: successfully removing a file locks
the player's terminal. The AI virus locks it out in red immediately;
the mainframe reveals a 12-character recovery code in green after 30
seconds, which can be submitted via /unlock to restore access early,
otherwise the terminal auto-recovers after 45 seconds.
2026-07-11 16:48:15 +02:00
Frank 1e644eb13b Add "Looking for Help" page for the open visual artist request
Adds a /looking-for-help page listing the open volunteer request and
links it from the site footer on every page.
2026-07-04 23:23:04 +02:00
Frank 8554f04735 Add "In Development" watermark badge over AI virus artwork
Overlay a rotated CSS badge on the homepage hero and briefing page
image instead of baking it into the PNG, so it's easy to remove once
the game ships.
2026-07-04 23:02:46 +02:00
Frank 839113c356 Add AI Virus Deflection story to homepage and new agent briefing page
Give the public homepage a themed breach-alert hero and add a /briefing
page with the full mission narrative, both linking into the existing
game dashboard flow.
2026-07-04 23:00:31 +02:00
FrankandClaude Sonnet 5 7dbb738da8 Show marketing opt-in status in admin users overview
Adds a Marketing column to the admin users table and a total opt-in
count in the header, so admins can see who signed up for updates on
future projects.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-04 20:48:48 +02:00
FrankandClaude Sonnet 5 ec34a1ddb9 Add Terms and Conditions and marketing opt-in checkboxes to registration
Registration now requires agreeing to the Terms and Conditions and lets
users opt in to hear about future projects. The opt-in is persisted on
the user via a new marketing_opt_in column (migration included).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-04 20:28:52 +02:00
FrankandClaude Sonnet 5 703d2af3d8 Add admin nav link and reuse site header/footer on admin pages
Admins now see an Admin link in the main navigation, and the admin
section inherits the branded header/footer from layout/site.html.twig
instead of the bare base layout.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-04 19:15:24 +02:00
Frank 5b03bd1d1c Complete layout overhaul 2026-07-04 19:08:31 +02:00
FrankandClaude Sonnet 5 e76d0b0f07 Fix Mercure reload spam on waiting page in Chrome
When the last player clicked ready, toggleReady() published a
redundant 'player_ready' event on top of checkAllPlayersReady()'s
'all_ready', and the client reloaded on every message with no guard
and without closing the EventSource. Chrome kept the old page's
script (and its EventSource) alive across the overlapping reload
calls, causing repeated reconnects to the Mercure hub; Firefox
apparently tore the page down fast enough to mask it. Skip the
redundant publish server-side, and make the client reload idempotent
by tracking whether it already fired and closing the EventSource
before reloading.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-04 16:26:47 +02:00
FrankandClaude Sonnet 5 c4e5139ec4 Increase email header logo size from 40px to 140px
40px made the logo too small in the header banner.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-04 14:55:15 +02:00
FrankandClaude Sonnet 5 238705495e Add shared HTML layout for transactional emails
confirmation_email.html.twig and reset_password/email.html.twig were
plain unstyled HTML with no shared structure. Extract a table-based
layout (templates/emails/layout.html.twig) with header/logo, content
block, and footer, so future transactional emails can extend it
instead of starting from scratch.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-04 14:51:34 +02:00
Frank van den BergandClaude Sonnet 4.6 aa56617e50 hub location mercure adjustment
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-28 23:09:35 +02:00
Frank van den Berg ac57385a9d Admin panels 2026-06-27 15:53:43 +02:00
Frank 6b5c205a27 Request resend of verification mail 2026-01-14 13:09:32 +01:00
Frank 70f5aa785e captcha 2026-01-13 21:54:26 +01:00
Frank 2008a379a8 Lost page 2026-01-08 20:32:21 +01:00
Frank 928ab3cbfe Added mercure to update when everyone is ready 2026-01-08 20:11:14 +01:00
Frank 4d021e7cf1 Trying to add waiting pages 2026-01-08 19:32:13 +01:00
Frank 30aa73bf53 Look into session logfiles 2026-01-08 18:26:32 +01:00
Frank 9d9de0fd0d Logfiles for sessions 2026-01-08 18:14:56 +01:00
Frank cdb469456f Dynamic number of players 2026-01-08 15:49:47 +01:00
Frank b6c09c267f Message when everyone is verified 2026-01-08 15:34:43 +01:00
Frank cdd5bc3fd8 Verification done 2026-01-07 20:06:28 +01:00
Frank 225c14124a Hint for first part 2026-01-07 14:33:10 +01:00
Frank 01b0522bd1 Created a dashboard and created an invite code for game sessions. 2026-01-06 20:23:46 +01:00
Frank 25664bbc4f Layout done, probably need rework later on 2026-01-06 20:20:33 +01:00
Frank b486ca64d3 Message ipv echo voor route 2026-01-05 12:16:30 +01:00
Frank 55a46a42b2 Forgot password 2026-01-03 22:57:45 +01:00
Frank 654b4036b4 Quite some work done here. 2026-01-03 22:12:51 +01:00
Frank b58da74967 Some settings 2026-01-03 13:16:58 +01:00
Frank 18821e2463 Startup 2026-01-02 20:27:56 +01:00
Frank 3b071eec9b Setup 2025-09-06 16:50:16 +02:00