From dfb3e0dedc4fc45878409c97d884e25500cdc1f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20M=C3=B6rling?= Date: Thu, 21 May 2026 14:49:50 +0200 Subject: [PATCH] Improve orders page with details and deferred payment. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- frontend/e2e/order-history.spec.ts | 20 ++++++++++ frontend/src/__tests__/OrdersPage.spec.ts | 42 +++++++++++++++++++++ frontend/src/api/orders.ts | 1 + frontend/src/pages/OrdersPage.vue | 46 +++++++++++++++++++++++ 4 files changed, 109 insertions(+) diff --git a/frontend/e2e/order-history.spec.ts b/frontend/e2e/order-history.spec.ts index 82c3033..9726ff8 100644 --- a/frontend/e2e/order-history.spec.ts +++ b/frontend/e2e/order-history.spec.ts @@ -54,6 +54,26 @@ test.describe('Order history', () => { await expect(page.getByText('Levererat').first()).toBeVisible() }) + test('shows pay button for unpaid order and opens payment page', async ({ + page, + }) => { + await page.goto('/logga-in') + await page.getByLabel('E-postadress').fill('test@bilhalsning.se') + await page.getByLabel('Lösenord').fill('test1234') + await page.getByRole('button', { name: 'Logga in' }).click() + await page.waitForURL('/') + + await page.goto('/orders') + + const unpaidCard = page.locator('.orders__card', { hasText: 'DEF456' }) + await expect(unpaidCard.getByRole('link', { name: 'Betala nu' })).toBeVisible() + await unpaidCard.getByRole('link', { name: 'Betala nu' }).click() + + await expect(page).toHaveURL(/\/betalning\/c2eebc99/) + await expect(page.getByRole('heading', { name: 'Betalning' })).toBeVisible() + await expect(page.getByText('DEF456')).toBeVisible() + }) + test('shows tracking links for orders with tracking ID', async ({ page }) => { await page.goto('/logga-in') await page.getByLabel('E-postadress').fill('test@bilhalsning.se') diff --git a/frontend/src/__tests__/OrdersPage.spec.ts b/frontend/src/__tests__/OrdersPage.spec.ts index e789298..a57a73c 100644 --- a/frontend/src/__tests__/OrdersPage.spec.ts +++ b/frontend/src/__tests__/OrdersPage.spec.ts @@ -17,6 +17,11 @@ function createTestRouter() { history: createMemoryHistory(), routes: [ { path: '/orders', name: 'orders', component: OrdersPage }, + { + path: '/betalning/:orderId', + name: 'payment', + component: { template: '
Payment
' }, + }, { path: '/', name: 'home', component: { template: '
Home
' } }, ], }) @@ -38,6 +43,7 @@ const mockOrders = [ { id: 'c1eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', plate: 'ABC123', + letterText: 'Hej fin bil!', status: 'sent', trackingId: 'PN123456789', createdAt: '2026-05-11T12:00:00Z', @@ -45,6 +51,7 @@ const mockOrders = [ { id: 'c2eebc99-9c0b-4ef8-bb6d-6bb9bd380a12', plate: 'DEF456', + letterText: 'Vill köpa din bil.', status: 'pending_payment', trackingId: null, createdAt: '2026-05-14T13:00:00Z', @@ -112,6 +119,7 @@ describe('OrdersPage', () => { { id: 'c2eebc99-9c0b-4ef8-bb6d-6bb9bd380a12', plate: 'DEF456', + letterText: 'Test', status: 'pending_payment', trackingId: null, createdAt: '2026-05-14T13:00:00Z', @@ -126,6 +134,16 @@ describe('OrdersPage', () => { expect(link.exists()).toBe(false) }) + it('renders order id and message', async () => { + const { wrapper } = mountPage() + await new Promise((r) => setTimeout(r, 50)) + expect(wrapper.text()).toContain('Beställnings-ID') + expect(wrapper.text()).toContain('c1eebc99-9c0b-4ef8-bb6d-6bb9bd380a11') + expect(wrapper.text()).toContain('Meddelande') + expect(wrapper.text()).toContain('Hej fin bil!') + expect(wrapper.text()).toContain('Vill köpa din bil.') + }) + it('renders formatted date', async () => { const { wrapper } = mountPage() await new Promise((r) => setTimeout(r, 50)) @@ -156,11 +174,35 @@ describe('OrdersPage', () => { expect(badges[1].classes()).toContain('badge--muted') }) + it('shows pay button only for pending payment orders', async () => { + const { wrapper } = mountPage() + await new Promise((r) => setTimeout(r, 50)) + + const payLinks = wrapper.findAll('.orders__pay-btn') + expect(payLinks).toHaveLength(1) + expect(payLinks[0].text()).toBe('Betala nu') + + const href = payLinks[0].attributes('href') + expect(href).toContain('c2eebc99-9c0b-4ef8-bb6d-6bb9bd380a12') + expect(href).toContain('plate=DEF456') + }) + + it('does not show pay button for paid or sent orders', async () => { + const { wrapper } = mountPage() + await new Promise((r) => setTimeout(r, 50)) + + const sentCard = wrapper + .findAll('.orders__card') + .find((card) => card.text().includes('ABC123')) + expect(sentCard?.find('.orders__pay-btn').exists()).toBe(false) + }) + it('renders processing status correctly', async () => { const ordersWithProcessing = [ { id: 'c4eebc99-9c0b-4ef8-bb6d-6bb9bd380a14', plate: 'XYZ123', + letterText: 'Processing message', status: 'processing', trackingId: null, createdAt: '2026-05-15T10:00:00Z', diff --git a/frontend/src/api/orders.ts b/frontend/src/api/orders.ts index 41dac62..2dd34d8 100644 --- a/frontend/src/api/orders.ts +++ b/frontend/src/api/orders.ts @@ -3,6 +3,7 @@ import { request } from './client' export interface Order { id: string plate: string + letterText: string status: string trackingId: string | null amountPaid: number | null diff --git a/frontend/src/pages/OrdersPage.vue b/frontend/src/pages/OrdersPage.vue index 4b82d7a..43c0bc6 100644 --- a/frontend/src/pages/OrdersPage.vue +++ b/frontend/src/pages/OrdersPage.vue @@ -86,6 +86,14 @@ onMounted(async () => {
+ Beställnings-ID + {{ order.id }} + + Meddelande + {{ + order.letterText + }} + Datum {{ formatDate(order.createdAt) @@ -103,6 +111,22 @@ onMounted(async () => {
+ +
+ + Betala nu + +
@@ -177,6 +201,17 @@ onMounted(async () => { color: var(--color-ink); } +.orders__order-id { + font-family: ui-monospace, monospace; + font-size: 0.8125rem; + word-break: break-all; +} + +.orders__message { + white-space: pre-wrap; + line-height: 1.5; +} + .orders__tracking { font-size: 0.875rem; color: var(--color-primary); @@ -188,6 +223,17 @@ onMounted(async () => { text-decoration: underline; } +.orders__card-actions { + padding: var(--space-md) var(--space-lg); + border-top: 1px solid var(--color-border); + background: var(--color-border-light); +} + +.orders__pay-btn { + width: 100%; + justify-content: center; +} + .orders__empty { padding: var(--space-2xl) 0; text-align: center;