Files
pictureFrame-webApp/templates/security/login.html.twig
T
football2801 a55b3bd187 feat(story-1.2): Vue 3 SPA scaffold, base component library, User entity, SpaController
Frontend:
- Vue 3 + Vite + TypeScript strict in frontend/; builds to public/build/
- Vue Router (hash-history, requiresAuth guard → /login) and Pinia
- Global SCSS design tokens with 6 full themes (Warm Craft, Playful Pop, Sage & Cream, Dusty Mauve, Ocean Dusk, Honey & Slate)
- Base components: BaseButton (5 variants), BaseInput (floating label, error state),
  BaseBottomSheet (slide-up, focus trap, tap-outside dismiss), BaseCard, BaseChip,
  BaseToast (2.5s auto-dismiss, aria-live polite), BottomNav (4 tabs, hides at 960px)
- Type stubs for all API shapes: User, Device, Image, StickerLayer, RenderedAsset, Token

Backend:
- SpaController catch-all serves public/build/index.html; excludes api/setup/token/login/register
- User entity (email+password+roles+theme); UserRepository with PasswordUpgrader
- SecurityController with /login, /logout, /register stubs; Twig login form
- security.yaml: form_login firewall, remember_me, role_hierarchy, access_control
- Migration: create user table

Verified: npm run build succeeds, GET / → 302 /login (unauthenticated)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 23:21:29 -04:00

34 lines
1.7 KiB
Twig

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Sign in — pictureFrame</title>
<style>
body { font-family: sans-serif; display: flex; align-items: center; justify-content: center; min-height: 100vh; margin: 0; background: #fdf6ee; }
form { width: 100%; max-width: 360px; padding: 2rem; background: #fff; border-radius: 12px; border: 1px solid #e8d9c4; }
h1 { margin: 0 0 1.5rem; font-size: 1.4rem; }
label { display: block; margin-bottom: 0.25rem; font-size: 0.875rem; }
input { width: 100%; padding: 0.75rem; border: 1px solid #ccc; border-radius: 8px; font-size: 1rem; margin-bottom: 1rem; box-sizing: border-box; }
button { width: 100%; padding: 0.875rem; background: #c97c3a; color: #fff; border: none; border-radius: 9999px; font-size: 1rem; font-weight: 600; cursor: pointer; }
.error { color: #c0392b; font-size: 0.875rem; margin-bottom: 1rem; }
a { display: block; text-align: center; margin-top: 1rem; color: #c97c3a; }
</style>
</head>
<body>
<form method="post">
<h1>Sign in</h1>
{% if error %}
<p class="error">{{ error.messageKey|trans(error.messageData, 'security') }}</p>
{% endif %}
<label for="inputEmail">Email</label>
<input type="email" id="inputEmail" name="_username" value="{{ last_username }}" required autofocus>
<label for="inputPassword">Password</label>
<input type="password" id="inputPassword" name="_password" required>
<input type="hidden" name="_csrf_token" value="{{ csrf_token('authenticate') }}">
<button type="submit">Sign in</button>
<a href="/register">Create account</a>
</form>
</body>
</html>