# 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-mapper` from composer - Delete `importmap.php` - Remove `asset_mapper.yaml` config ### Install Vite - Install `pentatrion/vite-bundle` (Symfony Vite integration) - Create `vite.config.js` at project root with `@vitejs/plugin-react` - Update `base.html.twig` to 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 install` and `npx vite` dev server on port 5173 ### docker-compose.override.yaml (dev) Add `node` service: ```yaml node: build: context: . dockerfile: docker/node/Dockerfile volumes: - .:/app - node_modules:/app/node_modules ports: - "5173:5173" ``` ### Production build - The `docker/node/Dockerfile` has a `build` stage that runs `npm run build` - The `docker/app/Dockerfile` prod stage copies built assets from the node build stage via `COPY --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 `UserInterface` and `PasswordAuthenticatedUserInterface` - Doctrine repository: `UserRepository` - Database migration for the `user` table ### Security Configuration (`security.yaml`) ```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's `form_login` authenticator - `GET /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 register - `templates/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 1. `HomepageController::index()` prepares grid data (actors, letters, target) as it does today 2. Data is passed as JSON props to the React component in Twig: ```twig {{ react_component('GameGrid', { grid: gridData, targetActorId: targetId }) }} ``` 3. React hydrates client-side and manages all interactivity ### Components **`GameGrid.jsx`** — Root component - Receives `grid` (array of rows) and `targetActorId` as props - Renders a list of `GameRow` components - 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 `LetterInput` components - Contains a button that triggers `ActorPopover` **`LetterInput.jsx`** — Single character input - `` styled to look like a game tile - On `keyup`: if a character was typed, move focus to the next `LetterInput` - 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/react` for 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 `user` table) - FrankenPHP server — untouched - Makefile — extended with node/npm targets, existing targets unchanged ## 6. File Changes Summary ### New Files - `docker/node/Dockerfile` - `vite.config.js` - `package.json` (replaces importmap-based setup) - `src/Entity/User.php` - `src/Repository/UserRepository.php` - `src/Controller/SecurityController.php` - `src/Controller/RegistrationController.php` - `src/Form/RegistrationType.php` - `templates/security/login.html.twig` - `templates/security/register.html.twig` - `assets/react/controllers/GameGrid.jsx` - `assets/react/controllers/GameRow.jsx` - `assets/react/controllers/LetterInput.jsx` - `assets/react/controllers/ActorPopover.jsx` - Migration file for `user` table ### Modified Files - `composer.json` — remove asset-mapper, add vite-bundle + ux-react - `config/packages/security.yaml` — full auth config - `templates/base.html.twig` — Vite tags instead of importmap - `templates/homepage/index.html.twig` — replace HTML grid with `{{ react_component() }}` - `docker-compose.override.yaml` — add node service - `docker-compose.yaml` — add node build step for prod - `docker/app/Dockerfile` — prod stage copies built JS assets - `assets/app.js` — entry point adjustments for Vite - `src/Controller/HomepageController.php` — pass grid data as JSON props to React component - `Makefile` — add npm/node targets