Improve orders page with details and deferred payment.
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
This commit is contained in:
parent
e2bccb4029
commit
dfb3e0dedc
4 changed files with 109 additions and 0 deletions
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -17,6 +17,11 @@ function createTestRouter() {
|
|||
history: createMemoryHistory(),
|
||||
routes: [
|
||||
{ path: '/orders', name: 'orders', component: OrdersPage },
|
||||
{
|
||||
path: '/betalning/:orderId',
|
||||
name: 'payment',
|
||||
component: { template: '<div>Payment</div>' },
|
||||
},
|
||||
{ path: '/', name: 'home', component: { template: '<div>Home</div>' } },
|
||||
],
|
||||
})
|
||||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -86,6 +86,14 @@ onMounted(async () => {
|
|||
</div>
|
||||
|
||||
<div class="orders__card-meta">
|
||||
<span class="orders__meta-label">Beställnings-ID</span>
|
||||
<span class="orders__meta-value orders__order-id">{{ order.id }}</span>
|
||||
|
||||
<span class="orders__meta-label">Meddelande</span>
|
||||
<span class="orders__meta-value orders__message">{{
|
||||
order.letterText
|
||||
}}</span>
|
||||
|
||||
<span class="orders__meta-label">Datum</span>
|
||||
<span class="orders__meta-value">{{
|
||||
formatDate(order.createdAt)
|
||||
|
|
@ -103,6 +111,22 @@ onMounted(async () => {
|
|||
</a>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="order.status === 'pending_payment'"
|
||||
class="orders__card-actions"
|
||||
>
|
||||
<RouterLink
|
||||
:to="{
|
||||
name: 'payment',
|
||||
params: { orderId: order.id },
|
||||
query: { plate: order.plate },
|
||||
}"
|
||||
class="btn btn--primary orders__pay-btn"
|
||||
>
|
||||
Betala nu
|
||||
</RouterLink>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in a new issue