7.4 KiB
7.4 KiB
Auth + React Frontend — Design Spec
Context
Actorle is a Symfony 8.0 word game (Wordle-like for actors) running on FrankenPHP with PostgreSQL. The app currently has no authentication and uses Asset Mapper with Stimulus for a minimal frontend. This spec covers adding user authentication and migrating to a React-based interactive frontend via SymfonyUX.
Approach
Hybrid Twig + React (SymfonyUX React option 2):
- Symfony stays in control of routing, sessions, and page rendering via Twig
- Interactive parts (the game grid) are React components mounted in Twig via
{{ react_component() }} - Auth pages (login, register) remain pure Twig — no benefit from React here
1. Frontend Migration: Asset Mapper to Vite
Remove Asset Mapper
- Remove
symfony/asset-mapperfrom composer - Delete
importmap.php - Remove
asset_mapper.yamlconfig
Install Vite
- Install
pentatrion/vite-bundle(Symfony Vite integration) - Create
vite.config.jsat project root with@vitejs/plugin-react - Update
base.html.twigto use Vite's{{ vite_entry_link_tags('app') }}and{{ vite_entry_script_tags('app') }}instead of{{ importmap() }}
Install React
- Install
symfony/ux-react - npm dependencies:
react,react-dom,@vitejs/plugin-react - The UX React bundle auto-registers a Stimulus controller that mounts React components
Assets Structure
assets/
├── app.js (entry point: imports styles + Stimulus bootstrap)
├── bootstrap.js (Stimulus app initialization)
├── styles/
│ └── app.css
├── controllers/ (Stimulus controllers)
│ └── csrf_protection_controller.js
└── react/
└── controllers/ (React components mountable via {{ react_component() }})
├── GameGrid.jsx
├── GameRow.jsx
├── LetterInput.jsx
└── ActorPopover.jsx
2. Docker Setup
New service: docker/node/Dockerfile
- Base image:
node:22-alpine - Working directory:
/app - Runs
npm installandnpx vitedev server on port 5173
docker-compose.override.yaml (dev)
Add node service:
node:
build:
context: .
dockerfile: docker/node/Dockerfile
volumes:
- .:/app
- node_modules:/app/node_modules
ports:
- "5173:5173"
Production build
- The
docker/node/Dockerfilehas abuildstage that runsnpm run build - The
docker/app/Dockerfileprod stage copies built assets from the node build stage viaCOPY --from= - No Node.js runtime in production — only the compiled static assets
3. Authentication
User Entity
- Fields:
id(int, auto),email(string, unique),password(hashed),roles(json) - Implements
UserInterfaceandPasswordAuthenticatedUserInterface - Doctrine repository:
UserRepository - Database migration for the
usertable
Security Configuration (security.yaml)
security:
password_hashers:
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
providers:
app_user_provider:
entity:
class: App\Entity\User
property: email
firewalls:
main:
lazy: true
provider: app_user_provider
form_login:
login_path: app_login
check_path: app_login
default_target_path: /
logout:
path: app_logout
access_control:
- { path: ^/login, roles: PUBLIC_ACCESS }
- { path: ^/register, roles: PUBLIC_ACCESS }
- { path: ^/, roles: ROLE_USER }
Controllers & Routes
SecurityController:
GET /login— renders login form (templates/security/login.html.twig)POST /login— handled by Symfony'sform_loginauthenticatorGET /logout— handled by Symfony's logout handler
RegistrationController:
GET /register— renders registration form (templates/security/register.html.twig)POST /register— validates form, hashes password, persists User, redirects to/login
Templates
templates/security/login.html.twig— email + password fields, CSRF token, error display, link to registertemplates/security/register.html.twig— email + password + confirm password fields, CSRF token, validation errors, link to login- Both extend
base.html.twig - Pure Twig, no React
4. Game Grid React Components
Data Flow
HomepageController::index()prepares grid data (actors, letters, target) as it does today- Data is passed as JSON props to the React component in Twig:
{{ react_component('GameGrid', { grid: gridData, targetActorId: targetId }) }} - React hydrates client-side and manages all interactivity
Components
GameGrid.jsx — Root component
- Receives
grid(array of rows) andtargetActorIdas props - Renders a list of
GameRowcomponents - Manages global game state (which letters have been guessed)
GameRow.jsx — One row = one actor
- Receives row data (actor name, highlighted letter index)
- Renders a sequence of
LetterInputcomponents - Contains a button that triggers
ActorPopover
LetterInput.jsx — Single character input
<input maxLength={1}>styled to look like a game tile- On
keyup: if a character was typed, move focus to the nextLetterInput - On
Backspace: clear and move focus to previous input - Highlighted letter has distinct styling (red background, as current CSS)
ActorPopover.jsx — Info popover
- Uses
@floating-ui/reactfor positioning - Triggered by a button click on the row
- Displays actor name, movie info, or hints
- Dismissible by clicking outside or pressing Escape
Popover Library
@floating-ui/react— modern, lightweight, React-native successor to Popper.js
5. What Does NOT Change
HomepageController— same game logic, adapted only to pass data as props to React instead of Twig variables (listed in Modified Files below)- Entities:
Actor,Movie,MovieRole— untouched - PostgreSQL database and existing migrations — untouched (new migration only adds
usertable) - FrankenPHP server — untouched
- Makefile — extended with node/npm targets, existing targets unchanged
6. File Changes Summary
New Files
docker/node/Dockerfilevite.config.jspackage.json(replaces importmap-based setup)src/Entity/User.phpsrc/Repository/UserRepository.phpsrc/Controller/SecurityController.phpsrc/Controller/RegistrationController.phpsrc/Form/RegistrationType.phptemplates/security/login.html.twigtemplates/security/register.html.twigassets/react/controllers/GameGrid.jsxassets/react/controllers/GameRow.jsxassets/react/controllers/LetterInput.jsxassets/react/controllers/ActorPopover.jsx- Migration file for
usertable
Modified Files
composer.json— remove asset-mapper, add vite-bundle + ux-reactconfig/packages/security.yaml— full auth configtemplates/base.html.twig— Vite tags instead of importmaptemplates/homepage/index.html.twig— replace HTML grid with{{ react_component() }}docker-compose.override.yaml— add node servicedocker-compose.yaml— add node build step for proddocker/app/Dockerfile— prod stage copies built JS assetsassets/app.js— entry point adjustments for Vitesrc/Controller/HomepageController.php— pass grid data as JSON props to React componentMakefile— add npm/node targets