Operators can fix prod admin passwords without email via Byt lösenord;
end users can use forgot-password when SMTP is configured. Local and CI
use Mailpit to capture outbound mail and verify reset links end-to-end.
- Backend: V8 password_reset_tokens, PasswordResetService, EmailService,
POST /api/auth/forgot-password, reset-password, change-password
- Optional testToken in forgot-password response (docker profile only, for E2E)
- Frontend: ForgotPasswordPage, ResetPasswordPage, ChangePasswordPage,
routes, login link, header Byt lösenord
- Mailpit (ghcr.io/axllent/mailpit:v1.28) in docker-compose + e2e stack
- E2E: password-reset.spec.ts + Mailpit API helper tests SMTP delivery
- Separate dev/e2e Docker image names to avoid overwriting bilhej-frontend
- Docs: README email section, production-email-checklist, .env.example
- Unit/integration tests for reset, change password, and Vitest page specs
Co-authored-by: Cursor <cursoragent@cursor.com>
The admin search label and parallel compose tests made strict-mode
Playwright locators ambiguous after the dashboard rework.
- Assert table columns via columnheader roles instead of getByText
- Target seeded order by ID when opening the message modal
Co-authored-by: Cursor <cursoragent@cursor.com>
Manual Swish flow means users often pay later; admins match payments via
order ID or regnr under Att göra.
- User flow: create order, leave payment, pay from orders page
- Admin: find order via partial ID, full ID, and plate search
- Assert unpaid orders appear under Väntar, not Att göra
- Use unique plates per run to avoid collisions with seed data
Admins need to find orders quickly and read full letter text without a
cramped table column.
- Make stat cards clickable filters (Totalt, Att göra, Betalda, Väntar)
- Add search by partial order ID or registration number
- Show shortened order ID in table with full ID on hover
- Replace message column with "Visa meddelande" opening a modal
- Keep expanded row for tracking only; remove duplicate brevtext block
- Update AdminDashboard unit tests and admin-dashboard e2e specs
Users who leave the payment step can return later and still see what
they ordered. Unpaid orders get a clear path back to Swish checkout.
- Add letterText to frontend Order type
- Show beställnings-ID, message, and formatted date on each order card
- Add "Betala nu" link to payment route for pending_payment orders
- Extend OrdersPage unit tests and order-history e2e for pay-later flow
fix: add preview.allowedHosts and preview.host to vite.config.ts
Vite preview server blocks requests from non-localhost hosts by default.
In the E2E Docker Compose stack, Playwright accesses the frontend via
http://frontend (container hostname). Without allowedHosts, Vite returns
"Blocked request. This host is not allowed." and the SPA never mounts,
causing all 59 E2E tests to fail with blank pages and missing elements.
- Add preview.host: true (bind to 0.0.0.0)
- Add preview.allowedHosts: ['frontend', 'localhost']
test: update payment-redirect E2E tests to match current UI
The payment page was redesigned to a two-step confirmation flow:
"Jag har betalat" → confirmation → "Ja, jag har betalat". The E2E
tests still referenced the old single-step "Genomför testbetalning"
button and a removed .payment__note CSS class.
- Update 'payment button marks order as paid' to click through both steps
- Rename 'shows mock payment note' to 'shows Swish payment instructions'
and assert on actual UI elements (Swish label + payment button)
Result: E2E suite now passes 59/59 tests in the Docker Compose CI stack.
- enters plate and sees vehicle info with CTA button:
types HDO732, verifies Peugeot 107 1.0, 2011, Gul, Bensin appear,
verifies Fortsatt till brevet link has correct href
- shows not found for unknown plate (ZZZ999)
- CTA navigates to compose when authenticated:
logs in as test@bilhalsning.se, performs lookup, clicks CTA,
verifies redirect to /compose?plate=HDO732
Vitest:
- PaymentRedirect.spec.ts (8 tests): renders heading and 49 kr,
shows plate from query, Betalt button exists, calls payOrder on
click, navigates to /orders on success, shows error on failure,
disables button while paying, shows mock note
- ComposePage.spec.ts: update navigation test to expect /betalning
route with orderId param instead of /orders; add payment route
to test router; add PaymentRedirect import
Playwright E2E:
- payment-redirect.spec.ts (4 tests): compose→payment navigation,
Betalt→orders flow, auth guard redirects to login, mock note
visible
- compose.spec.ts: rename test and update assertion from /orders
to /betalning/ URL pattern; use getByRole('heading',
{ name: 'Betalning' }) to avoid strict mode violation with
mock-note paragraph containing the word 'Betalning'
- AdminDashboard.spec.ts (+6 tests):
- tracking input and save button visible in expanded row
- PostNord link visible when trackingId is set
- PostNord link hidden when trackingId is null
- save button fires PATCH to correct URL
- tracking error shown on failed save
- admin-dashboard.spec.ts (+4 tests):
- tracking input and save button visible after row expand
- PostNord link with postnord href visible for orders with tracking
- PostNord link hidden for orders without tracking
- fix row selector to use .last() for deterministic tracking check
(compose test creates extra ABC123 order that shifts row order)
- compose.spec.ts: fix strict mode violation — getByText('ABC123')
resolved to 2 elements (strong + preview paragraph) after admin
test expanded an ABC123 row; use .first()
- order-history.spec.ts: fix strict mode violations — ABC123 and
Levererat resolve to 2 elements due to compose test creating
an extra ABC123 order with status changed to delivered; use
.first() on affected assertions
Vitest (14 tests) — AdminDashboard.spec.ts:
- renders heading, subtitle, table columns, order data in rows
- shows loading, empty, and error states
- fetches GET /api/admin/orders on mount
- expands row on click to reveal letter content (Brevtext label)
- collapses row on second click
- only one row expanded at a time (clicking row 2 closes row 1)
- status dropdown change fires PATCH /api/admin/orders/{id}/status
with correct URL, method, and JSON body
- shows error message on failed status update
Playwright E2E (8 tests) — admin-dashboard.spec.ts:
- admin login (admin@bilhalsning.se / test1234) before each test
- admin can navigate to /admin and see heading
- non-admin user (test@bilhalsning.se) is redirected away from /admin
- table renders Datum/E-post/Regnr/Status column headers
- seeded order plates visible (ABC123, DEF456, GHI789)
- click row expands letter content
- click again collapses letter content
- status dropdown change persists (selectOption delivered)
- unauthenticated access redirects to login with ?redirect=/admin
Templates serve as a brand shield (showing the platform facilitates all
kinds of messaging), not as a compose-flow form control. Remove them from
the data model and compose page. Templates will live as branding elements
on the landing page in a future commit.
Backend:
- Remove template field from Order entity (getter/setter removed)
- Remove template from CreateOrderRequest DTO
- Remove template from OrderResponse DTO
- Remove template param from OrderService.createOrder()
- Remove template passthrough in OrderController
- Remove /api/templates permitAll from SecurityConfig
- Edit V5 migration: remove template column from orders table
- Edit V6 migration: remove template from seed data
- Update OrderControllerTest (remove template from assertions/requests)
- Update OrderServiceTest (remove template from createOrder calls)
Frontend:
- Remove template from Order interface in api/orders.ts
- Remove template param from createOrder() function
- Remove template display from OrdersPage.vue cards
- Rewrite ComposePage.vue: remove template selector, keep textarea + preview + submit
- Update ComposePage.spec.ts (remove template tests, add preview/GDPR tests)
- Update OrdersPage.spec.ts (remove template from mock data and display test)
- Update compose.spec.ts E2E (remove template selector interactions)
- Update order-history.spec.ts E2E (remove template names test)
- Fix unused import in Router.spec.ts
- Also includes minor Prettier formatting in AppHeader.spec.ts, AdminPage.vue, authStore.ts
- Add createOrder(plate, template, letterText) to frontend api/orders.ts
- Create data/templates.ts with 6 Swedish letter templates (Komplimang,
Jag vill köpa din bil, Tips / servicebehov, Synpunkter på körbeteende,
Tuta / frustration, Fritt meddelande) with pre-filled body text
- Rewrite ComposePage.vue with full compose flow:
- Template selector dropdown (Fritt meddelande selected by default)
- Textarea with 1000-char limit and live character counter
- Inline A4 letter preview with plate, body, and GDPR Art. 14 footer
- 'Skicka brev (49 kr)' submit button, disabled when empty
- On success: redirects to /orders; on error: shows error message
- Shows error with back link if no plate in route query
- Add 12 Vitest tests for ComposePage (template fill, char counter, submit
validation, createOrder call, navigation, null template for Fritt meddelande)
- Add 8 Playwright E2E tests (auth guard, no-plate error, template selection,
textarea edit, submit button state, order creation, preview content)
- Add 'Mina beställningar' RouterLink to AppHeader in authenticated template
- Add Vitest tests: link visible when authenticated, hidden when not
- Add Playwright E2E test: shows orders link when authenticated
- Add Playwright E2E test: can navigate from home to orders via header link
Update AppHeader to reflect authentication state. When not authenticated,
show Logga in and Registrera links. When authenticated, show the user's
email address and a Logga ut button. Uses v-if/v-else with template blocks
for clean conditional rendering without wrapper elements.
Changes:
- authStore: add email computed that extracts sub claim from JWT payload
- AppHeader: conditional nav with v-if/v-else (guest vs authenticated)
- AppHeader: add email display and logout button with styles
- App.spec.ts: add Pinia to test setup (required by AppHeader now)
- AppHeader.spec.ts: rewrite with tests for both auth states
- authStore.spec.ts: add tests for email extraction and clearing
- header-auth.spec.ts: 5 Playwright E2E tests for header auth state
Implement client-side route protection with role-based access control. The auth
store now extracts the role claim from JWT tokens and exposes isAdmin. Router
guards enforce three levels of access: guestOnly (redirect authenticated users),
requiresAuth (redirect unauthenticated to login with redirect param), and
requiresAdmin (redirect non-admin users to home).
Changes:
- utils/jwt.ts: JWT payload parser using base64url decode (new file)
- authStore: add role ref, isAdmin computed, extractRole from JWT payload
- router: add route metadata (requiresAuth, requiresAdmin, guestOnly) and
beforeEach guard with getActivePinia() safety for test environments
- OrdersPage.vue, AdminPage.vue: placeholder pages (new files)
- LoginPage.vue, RegisterPage.vue: use route.query.redirect after auth
- Router.spec.ts: 14 tests covering all guard scenarios
- authStore.spec.ts: tests for role extraction, isAdmin, role persistence
- LoginPage.spec.ts: test for redirect query param after login
- auth-guards.spec.ts: 7 Playwright E2E tests for guard behavior
- login.spec.ts: fix seed user credentials (test@bilhalsning.se)
Add the frontend login page (LoginPage.vue) with email and password
fields, Swedish UI strings, and integration with the backend login
endpoint. Also sets up Playwright as the E2E testing framework with
browser tests for both login and registration flows.
Frontend login implementation:
- Add LoginPage.vue with form validation, error handling, and link to
registration page
- Add login() API function in auth.ts
- Add loginUser() method to authStore that stores JWT token
- Add /logga-in route to Vue Router
- Add 'Logga in' nav link to AppHeader alongside existing 'Registrera'
- Add 10 unit tests for LoginPage component
- Add 4 unit tests for loginUser auth store method
- Add 1 route resolution test and 1 AppHeader link test
Playwright E2E setup and tests:
- Install @playwright/test and configure playwright.config.ts
- Add npm scripts: test:e2e (local) and test:e2e:ci (Docker CI)
- Exclude e2e/ directory from Vitest to prevent test runner conflicts
- Add .gitignore entries for test-results/ and playwright-report/
- Add 5 E2E tests for login (navigation, invalid credentials, success
redirect, navigation to register, input types)
- Add 6 E2E tests for register (navigation, success redirect, validation
errors for invalid email/short password/mismatched passwords,
navigation to login)