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()
|
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 }) => {
|
test('shows tracking links for orders with tracking ID', async ({ page }) => {
|
||||||
await page.goto('/logga-in')
|
await page.goto('/logga-in')
|
||||||
await page.getByLabel('E-postadress').fill('test@bilhalsning.se')
|
await page.getByLabel('E-postadress').fill('test@bilhalsning.se')
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,11 @@ function createTestRouter() {
|
||||||
history: createMemoryHistory(),
|
history: createMemoryHistory(),
|
||||||
routes: [
|
routes: [
|
||||||
{ path: '/orders', name: 'orders', component: OrdersPage },
|
{ path: '/orders', name: 'orders', component: OrdersPage },
|
||||||
|
{
|
||||||
|
path: '/betalning/:orderId',
|
||||||
|
name: 'payment',
|
||||||
|
component: { template: '<div>Payment</div>' },
|
||||||
|
},
|
||||||
{ path: '/', name: 'home', component: { template: '<div>Home</div>' } },
|
{ path: '/', name: 'home', component: { template: '<div>Home</div>' } },
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|
@ -38,6 +43,7 @@ const mockOrders = [
|
||||||
{
|
{
|
||||||
id: 'c1eebc99-9c0b-4ef8-bb6d-6bb9bd380a11',
|
id: 'c1eebc99-9c0b-4ef8-bb6d-6bb9bd380a11',
|
||||||
plate: 'ABC123',
|
plate: 'ABC123',
|
||||||
|
letterText: 'Hej fin bil!',
|
||||||
status: 'sent',
|
status: 'sent',
|
||||||
trackingId: 'PN123456789',
|
trackingId: 'PN123456789',
|
||||||
createdAt: '2026-05-11T12:00:00Z',
|
createdAt: '2026-05-11T12:00:00Z',
|
||||||
|
|
@ -45,6 +51,7 @@ const mockOrders = [
|
||||||
{
|
{
|
||||||
id: 'c2eebc99-9c0b-4ef8-bb6d-6bb9bd380a12',
|
id: 'c2eebc99-9c0b-4ef8-bb6d-6bb9bd380a12',
|
||||||
plate: 'DEF456',
|
plate: 'DEF456',
|
||||||
|
letterText: 'Vill köpa din bil.',
|
||||||
status: 'pending_payment',
|
status: 'pending_payment',
|
||||||
trackingId: null,
|
trackingId: null,
|
||||||
createdAt: '2026-05-14T13:00:00Z',
|
createdAt: '2026-05-14T13:00:00Z',
|
||||||
|
|
@ -112,6 +119,7 @@ describe('OrdersPage', () => {
|
||||||
{
|
{
|
||||||
id: 'c2eebc99-9c0b-4ef8-bb6d-6bb9bd380a12',
|
id: 'c2eebc99-9c0b-4ef8-bb6d-6bb9bd380a12',
|
||||||
plate: 'DEF456',
|
plate: 'DEF456',
|
||||||
|
letterText: 'Test',
|
||||||
status: 'pending_payment',
|
status: 'pending_payment',
|
||||||
trackingId: null,
|
trackingId: null,
|
||||||
createdAt: '2026-05-14T13:00:00Z',
|
createdAt: '2026-05-14T13:00:00Z',
|
||||||
|
|
@ -126,6 +134,16 @@ describe('OrdersPage', () => {
|
||||||
expect(link.exists()).toBe(false)
|
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 () => {
|
it('renders formatted date', async () => {
|
||||||
const { wrapper } = mountPage()
|
const { wrapper } = mountPage()
|
||||||
await new Promise((r) => setTimeout(r, 50))
|
await new Promise((r) => setTimeout(r, 50))
|
||||||
|
|
@ -156,11 +174,35 @@ describe('OrdersPage', () => {
|
||||||
expect(badges[1].classes()).toContain('badge--muted')
|
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 () => {
|
it('renders processing status correctly', async () => {
|
||||||
const ordersWithProcessing = [
|
const ordersWithProcessing = [
|
||||||
{
|
{
|
||||||
id: 'c4eebc99-9c0b-4ef8-bb6d-6bb9bd380a14',
|
id: 'c4eebc99-9c0b-4ef8-bb6d-6bb9bd380a14',
|
||||||
plate: 'XYZ123',
|
plate: 'XYZ123',
|
||||||
|
letterText: 'Processing message',
|
||||||
status: 'processing',
|
status: 'processing',
|
||||||
trackingId: null,
|
trackingId: null,
|
||||||
createdAt: '2026-05-15T10:00:00Z',
|
createdAt: '2026-05-15T10:00:00Z',
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import { request } from './client'
|
||||||
export interface Order {
|
export interface Order {
|
||||||
id: string
|
id: string
|
||||||
plate: string
|
plate: string
|
||||||
|
letterText: string
|
||||||
status: string
|
status: string
|
||||||
trackingId: string | null
|
trackingId: string | null
|
||||||
amountPaid: number | null
|
amountPaid: number | null
|
||||||
|
|
|
||||||
|
|
@ -86,6 +86,14 @@ onMounted(async () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="orders__card-meta">
|
<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-label">Datum</span>
|
||||||
<span class="orders__meta-value">{{
|
<span class="orders__meta-value">{{
|
||||||
formatDate(order.createdAt)
|
formatDate(order.createdAt)
|
||||||
|
|
@ -103,6 +111,22 @@ onMounted(async () => {
|
||||||
</a>
|
</a>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -177,6 +201,17 @@ onMounted(async () => {
|
||||||
color: var(--color-ink);
|
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 {
|
.orders__tracking {
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
color: var(--color-primary);
|
color: var(--color-primary);
|
||||||
|
|
@ -188,6 +223,17 @@ onMounted(async () => {
|
||||||
text-decoration: underline;
|
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 {
|
.orders__empty {
|
||||||
padding: var(--space-2xl) 0;
|
padding: var(--space-2xl) 0;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue