Polish orders page UI for pending and completed cards.
Redesigns the order list so unpaid and paid orders share a consistent card layout, with clearer payment context and labeled metadata users need before paying via Swish. - Split list into Obetalda/Tidigare sections with pending orders first - Pending cards: preview box, labeled Beställnings-ID, price row, Betala 49 kr - Completed cards: same header/preview layout, prominent Spåra brev button - Replace em-dash pay label and update unit/E2E selectors Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
3d0b7fe799
commit
ca5ce12812
4 changed files with 381 additions and 158 deletions
|
|
@ -59,15 +59,15 @@ test.describe('Deferred payment and admin lookup', () => {
|
|||
const orderCard = page.locator('.orders__card', { hasText: orderId })
|
||||
await expect(orderCard.getByText(plate)).toBeVisible()
|
||||
await expect(orderCard.locator('.badge')).toHaveText('Väntar på betalning')
|
||||
await expect(orderCard.getByRole('link', { name: 'Betala nu' })).toBeVisible()
|
||||
await expect(orderCard.getByRole('link', { name: 'Betala 49 kr' })).toBeVisible()
|
||||
|
||||
await orderCard.getByRole('link', { name: 'Betala nu' }).click()
|
||||
await orderCard.getByRole('link', { name: 'Betala 49 kr' }).click()
|
||||
await expect(page).toHaveURL(new RegExp(`/betalning/${orderId}`))
|
||||
await completeSwishPayment(page)
|
||||
|
||||
await expect(page).toHaveURL('/orders')
|
||||
await expect(orderCard.locator('.badge')).toHaveText('Hanteras')
|
||||
await expect(orderCard.getByRole('link', { name: 'Betala nu' })).not.toBeVisible()
|
||||
await expect(orderCard.getByRole('link', { name: 'Betala 49 kr' })).not.toBeVisible()
|
||||
})
|
||||
|
||||
test('admin finds paid order under Att göra when searching partial order id', async ({
|
||||
|
|
|
|||
|
|
@ -66,8 +66,8 @@ test.describe('Order history', () => {
|
|||
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(unpaidCard.getByRole('link', { name: 'Betala 49 kr' })).toBeVisible()
|
||||
await unpaidCard.getByRole('link', { name: 'Betala 49 kr' }).click()
|
||||
|
||||
await expect(page).toHaveURL(/\/betalning\/c2eebc99/)
|
||||
await expect(page.getByRole('heading', { name: 'Betalning' })).toBeVisible()
|
||||
|
|
|
|||
|
|
@ -63,13 +63,39 @@ const mockOrders = [
|
|||
},
|
||||
]
|
||||
|
||||
function mockOrdersFetch(orders: unknown) {
|
||||
vi.mocked(globalThis.fetch).mockImplementation((url, init) => {
|
||||
const urlStr = String(url)
|
||||
const method = init?.method ?? 'GET'
|
||||
|
||||
if (urlStr.includes('/payment/swish-info')) {
|
||||
return mockFetchResponse(200, { number: '1234567890', amount: 49 })
|
||||
}
|
||||
|
||||
if (urlStr.includes('/cancel') && method === 'POST') {
|
||||
return mockFetchResponse(200, {
|
||||
id: 'c2eebc99-9c0b-4ef8-bb6d-6bb9bd380a12',
|
||||
plate: 'DEF456',
|
||||
letterText: 'Vill köpa din bil.',
|
||||
status: 'cancelled',
|
||||
trackingId: null,
|
||||
createdAt: '2026-05-14T13:00:00Z',
|
||||
})
|
||||
}
|
||||
|
||||
if (urlStr.includes('/orders')) {
|
||||
return mockFetchResponse(200, orders)
|
||||
}
|
||||
|
||||
return mockFetchResponse(404, { message: 'Not found' })
|
||||
})
|
||||
}
|
||||
|
||||
describe('OrdersPage', () => {
|
||||
beforeEach(() => {
|
||||
localStorage.clear()
|
||||
globalThis.fetch = vi.fn()
|
||||
vi.mocked(globalThis.fetch).mockResolvedValue(
|
||||
mockFetchResponse(200, mockOrders),
|
||||
)
|
||||
mockOrdersFetch(mockOrders)
|
||||
})
|
||||
|
||||
it('renders heading and subtitle', async () => {
|
||||
|
|
@ -87,6 +113,13 @@ describe('OrdersPage', () => {
|
|||
expect(wrapper.text()).toContain('Laddar beställningar...')
|
||||
})
|
||||
|
||||
it('shows section headings when pending and completed orders exist', async () => {
|
||||
const { wrapper } = mountPage()
|
||||
await new Promise((r) => setTimeout(r, 50))
|
||||
expect(wrapper.text()).toContain('Obetalda beställningar')
|
||||
expect(wrapper.text()).toContain('Tidigare beställningar')
|
||||
})
|
||||
|
||||
it('fetches orders from API on mount', async () => {
|
||||
mountPage()
|
||||
await new Promise((r) => setTimeout(r, 50))
|
||||
|
|
@ -115,7 +148,9 @@ describe('OrdersPage', () => {
|
|||
await new Promise((r) => setTimeout(r, 50))
|
||||
const link = wrapper.find('a[href*="postnord"]')
|
||||
expect(link.exists()).toBe(true)
|
||||
expect(link.classes()).toContain('orders__tracking-btn')
|
||||
expect(link.text()).toContain('PN123456789')
|
||||
expect(link.text()).toContain('Spåra brev')
|
||||
expect(link.attributes('target')).toBe('_blank')
|
||||
})
|
||||
|
||||
|
|
@ -130,9 +165,7 @@ describe('OrdersPage', () => {
|
|||
createdAt: '2026-05-14T13:00:00Z',
|
||||
},
|
||||
]
|
||||
vi.mocked(globalThis.fetch).mockResolvedValue(
|
||||
mockFetchResponse(200, ordersWithoutTracking),
|
||||
)
|
||||
mockOrdersFetch(ordersWithoutTracking)
|
||||
const { wrapper } = mountPage()
|
||||
await new Promise((r) => setTimeout(r, 50))
|
||||
const link = wrapper.find('a[href*="postnord"]')
|
||||
|
|
@ -142,9 +175,8 @@ describe('OrdersPage', () => {
|
|||
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('Beställnings-ID')
|
||||
expect(wrapper.text()).toContain('Hej fin bil!')
|
||||
expect(wrapper.text()).toContain('Vill köpa din bil.')
|
||||
})
|
||||
|
|
@ -153,19 +185,24 @@ describe('OrdersPage', () => {
|
|||
const { wrapper } = mountPage()
|
||||
await new Promise((r) => setTimeout(r, 50))
|
||||
expect(wrapper.text()).toContain('2026')
|
||||
expect(wrapper.text()).toContain('Skapad')
|
||||
})
|
||||
|
||||
it('shows empty state when no orders', async () => {
|
||||
vi.mocked(globalThis.fetch).mockResolvedValue(mockFetchResponse(200, []))
|
||||
mockOrdersFetch([])
|
||||
const { wrapper } = mountPage()
|
||||
await new Promise((r) => setTimeout(r, 50))
|
||||
expect(wrapper.text()).toContain('Inga beställningar ännu')
|
||||
})
|
||||
|
||||
it('shows error state on API failure', async () => {
|
||||
vi.mocked(globalThis.fetch).mockResolvedValue(
|
||||
mockFetchResponse(500, { message: 'Internal server error' }),
|
||||
)
|
||||
vi.mocked(globalThis.fetch).mockImplementation((url) => {
|
||||
const urlStr = String(url)
|
||||
if (urlStr.includes('/payment/swish-info')) {
|
||||
return mockFetchResponse(200, { number: '1234567890', amount: 49 })
|
||||
}
|
||||
return mockFetchResponse(500, { message: 'Internal server error' })
|
||||
})
|
||||
const { wrapper } = mountPage()
|
||||
await new Promise((r) => setTimeout(r, 50))
|
||||
expect(wrapper.text()).toContain('Kunde inte hämta beställningar')
|
||||
|
|
@ -175,8 +212,21 @@ describe('OrdersPage', () => {
|
|||
const { wrapper } = mountPage()
|
||||
await new Promise((r) => setTimeout(r, 50))
|
||||
const badges = wrapper.findAll('.badge')
|
||||
expect(badges[0].classes()).toContain('badge--success')
|
||||
expect(badges[1].classes()).toContain('badge--muted')
|
||||
expect(badges[0].classes()).toContain('badge--warning')
|
||||
expect(badges[1].classes()).toContain('badge--success')
|
||||
})
|
||||
|
||||
it('shows order id on pending payment orders', async () => {
|
||||
const { wrapper } = mountPage()
|
||||
await new Promise((r) => setTimeout(r, 50))
|
||||
|
||||
const pendingCard = wrapper
|
||||
.findAll('.orders__card')
|
||||
.find((card) => card.text().includes('DEF456'))
|
||||
expect(pendingCard?.text()).toContain('Beställnings-ID')
|
||||
expect(pendingCard?.text()).toContain(
|
||||
'c2eebc99-9c0b-4ef8-bb6d-6bb9bd380a12',
|
||||
)
|
||||
})
|
||||
|
||||
it('shows pay button only for pending payment orders', async () => {
|
||||
|
|
@ -186,9 +236,9 @@ describe('OrdersPage', () => {
|
|||
const pendingCard = wrapper
|
||||
.findAll('.orders__card')
|
||||
.find((card) => card.text().includes('DEF456'))
|
||||
const payLink = pendingCard?.find('a.orders__action-btn')
|
||||
const payLink = pendingCard?.find('a.orders__pay-btn')
|
||||
expect(payLink?.exists()).toBe(true)
|
||||
expect(payLink?.text()).toBe('Betala nu')
|
||||
expect(payLink?.text()).toBe('Betala 49 kr')
|
||||
|
||||
const href = payLink?.attributes('href')
|
||||
expect(href).toContain('c2eebc99-9c0b-4ef8-bb6d-6bb9bd380a12')
|
||||
|
|
@ -202,7 +252,7 @@ describe('OrdersPage', () => {
|
|||
const sentCard = wrapper
|
||||
.findAll('.orders__card')
|
||||
.find((card) => card.text().includes('ABC123'))
|
||||
expect(sentCard?.find('a.orders__action-btn').exists()).toBe(false)
|
||||
expect(sentCard?.find('a.orders__pay-btn').exists()).toBe(false)
|
||||
})
|
||||
|
||||
it('shows edit link for pending payment orders', async () => {
|
||||
|
|
@ -212,9 +262,9 @@ describe('OrdersPage', () => {
|
|||
const pendingCard = wrapper
|
||||
.findAll('.orders__card')
|
||||
.find((card) => card.text().includes('DEF456'))
|
||||
const editLinks = pendingCard?.findAll('a.orders__action-btn') ?? []
|
||||
const editLink = editLinks.find((link) => link.text() === 'Redigera')
|
||||
const editLink = pendingCard?.find('a.orders__edit-btn')
|
||||
expect(editLink?.exists()).toBe(true)
|
||||
expect(editLink?.text()).toBe('Redigera brev')
|
||||
|
||||
const href = editLink?.attributes('href')
|
||||
expect(href).toContain('c2eebc99-9c0b-4ef8-bb6d-6bb9bd380a12')
|
||||
|
|
@ -239,19 +289,6 @@ describe('OrdersPage', () => {
|
|||
vi.fn(() => true),
|
||||
)
|
||||
|
||||
vi.mocked(globalThis.fetch)
|
||||
.mockResolvedValueOnce(mockFetchResponse(200, mockOrders))
|
||||
.mockResolvedValueOnce(
|
||||
mockFetchResponse(200, {
|
||||
id: 'c2eebc99-9c0b-4ef8-bb6d-6bb9bd380a12',
|
||||
plate: 'DEF456',
|
||||
letterText: 'Vill köpa din bil.',
|
||||
status: 'cancelled',
|
||||
trackingId: null,
|
||||
createdAt: '2026-05-14T13:00:00Z',
|
||||
}),
|
||||
)
|
||||
|
||||
const { wrapper } = mountPage()
|
||||
await new Promise((r) => setTimeout(r, 50))
|
||||
|
||||
|
|
@ -278,7 +315,7 @@ describe('OrdersPage', () => {
|
|||
.findAll('.orders__card')
|
||||
.find((card) => card.text().includes('ABC123'))
|
||||
expect(sentCard?.find('.orders__cancel-btn').exists()).toBe(false)
|
||||
expect(sentCard?.text()).not.toContain('Redigera')
|
||||
expect(sentCard?.text()).not.toContain('Redigera brev')
|
||||
expect(sentCard?.text()).not.toContain('Avbryt beställning')
|
||||
})
|
||||
|
||||
|
|
@ -293,9 +330,7 @@ describe('OrdersPage', () => {
|
|||
createdAt: '2026-05-15T10:00:00Z',
|
||||
},
|
||||
]
|
||||
vi.mocked(globalThis.fetch).mockResolvedValue(
|
||||
mockFetchResponse(200, ordersWithProcessing),
|
||||
)
|
||||
mockOrdersFetch(ordersWithProcessing)
|
||||
const { wrapper } = mountPage()
|
||||
await new Promise((r) => setTimeout(r, 50))
|
||||
expect(wrapper.text()).toContain('Hanteras')
|
||||
|
|
|
|||
|
|
@ -1,14 +1,26 @@
|
|||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { fetchOrders, cancelOrder, type Order } from '@/api/orders'
|
||||
import { fetchSwishInfo } from '@/api/payment'
|
||||
import { RouterLink } from 'vue-router'
|
||||
|
||||
const ORDER_AMOUNT_FALLBACK = 49
|
||||
|
||||
const orders = ref<Order[]>([])
|
||||
const orderAmount = ref(ORDER_AMOUNT_FALLBACK)
|
||||
const loading = ref(true)
|
||||
const error = ref('')
|
||||
const actionError = ref('')
|
||||
const cancellingId = ref<string | null>(null)
|
||||
|
||||
const pendingOrders = computed(() =>
|
||||
orders.value.filter((order) => order.status === 'pending_payment'),
|
||||
)
|
||||
|
||||
const completedOrders = computed(() =>
|
||||
orders.value.filter((order) => order.status !== 'pending_payment'),
|
||||
)
|
||||
|
||||
const statusLabels: Record<string, string> = {
|
||||
pending_payment: 'Väntar på betalning',
|
||||
paid: 'Betalad',
|
||||
|
|
@ -39,7 +51,15 @@ function formatDate(iso: string): string {
|
|||
|
||||
async function loadOrders() {
|
||||
try {
|
||||
orders.value = await fetchOrders()
|
||||
const [fetchedOrders, swishInfo] = await Promise.all([
|
||||
fetchOrders(),
|
||||
fetchSwishInfo().catch(() => ({
|
||||
number: '',
|
||||
amount: ORDER_AMOUNT_FALLBACK,
|
||||
})),
|
||||
])
|
||||
orders.value = fetchedOrders
|
||||
orderAmount.value = swishInfo.amount
|
||||
} catch {
|
||||
error.value = 'Kunde inte hämta beställningar. Försök igen senare.'
|
||||
} finally {
|
||||
|
|
@ -110,83 +130,134 @@ onMounted(loadOrders)
|
|||
{{ actionError }}
|
||||
</div>
|
||||
|
||||
<div class="orders__list">
|
||||
<div v-for="order in orders" :key="order.id" class="orders__card">
|
||||
<div class="orders__card-top">
|
||||
<span class="orders__plate">{{ order.plate }}</span>
|
||||
<span
|
||||
class="badge"
|
||||
:class="statusBadge[order.status] || 'badge--muted'"
|
||||
>
|
||||
{{ statusLabels[order.status] || order.status }}
|
||||
</span>
|
||||
<section v-if="pendingOrders.length" class="orders__section">
|
||||
<h2 class="orders__section-title">Obetalda beställningar</h2>
|
||||
<div class="orders__list">
|
||||
<div
|
||||
v-for="order in pendingOrders"
|
||||
:key="order.id"
|
||||
class="orders__card orders__card--pending"
|
||||
>
|
||||
<div class="orders__card-content">
|
||||
<div class="orders__card-head">
|
||||
<p class="orders__plate-badge">
|
||||
<span class="orders__plate-label">Regnr</span>
|
||||
<span class="orders__plate-value">{{ order.plate }}</span>
|
||||
</p>
|
||||
<span class="badge badge--warning">
|
||||
{{ statusLabels[order.status] }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="orders__preview-box">
|
||||
<p class="orders__preview">{{ order.letterText }}</p>
|
||||
</div>
|
||||
|
||||
<p class="orders__order-date">
|
||||
Skapad {{ formatDate(order.createdAt) }}
|
||||
</p>
|
||||
|
||||
<div class="orders__order-ref orders__order-ref--highlight">
|
||||
<p class="orders__order-ref-label">Beställnings-ID</p>
|
||||
<p class="orders__order-id">{{ order.id }}</p>
|
||||
</div>
|
||||
|
||||
<div class="orders__price-row">
|
||||
<span class="orders__price-label">Att betala</span>
|
||||
<span class="orders__price-amount">{{ orderAmount }} kr</span>
|
||||
</div>
|
||||
|
||||
<RouterLink
|
||||
:to="{
|
||||
name: 'payment',
|
||||
params: { orderId: order.id },
|
||||
query: { plate: order.plate },
|
||||
}"
|
||||
class="btn btn--primary orders__pay-btn"
|
||||
>
|
||||
Betala {{ orderAmount }} kr
|
||||
</RouterLink>
|
||||
|
||||
<div class="orders__links">
|
||||
<RouterLink
|
||||
:to="{
|
||||
name: 'edit-order',
|
||||
params: { orderId: order.id },
|
||||
}"
|
||||
class="orders__text-link orders__edit-btn"
|
||||
>
|
||||
Redigera brev
|
||||
</RouterLink>
|
||||
<span class="orders__link-sep" aria-hidden="true">·</span>
|
||||
<button
|
||||
type="button"
|
||||
class="orders__text-link orders__text-link--danger orders__cancel-btn"
|
||||
spellcheck="false"
|
||||
:disabled="cancellingId === order.id"
|
||||
@click="handleCancel(order)"
|
||||
>
|
||||
{{
|
||||
cancellingId === order.id
|
||||
? 'Avbryter...'
|
||||
: 'Avbryt beställning'
|
||||
}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<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>
|
||||
<section v-if="completedOrders.length" class="orders__section">
|
||||
<h2 v-if="pendingOrders.length" class="orders__section-title">
|
||||
Tidigare beställningar
|
||||
</h2>
|
||||
<div class="orders__list">
|
||||
<div
|
||||
v-for="order in completedOrders"
|
||||
:key="order.id"
|
||||
class="orders__card"
|
||||
>
|
||||
<div class="orders__card-content">
|
||||
<div class="orders__card-head">
|
||||
<p class="orders__plate-badge">
|
||||
<span class="orders__plate-label">Regnr</span>
|
||||
<span class="orders__plate-value">{{ order.plate }}</span>
|
||||
</p>
|
||||
<span
|
||||
class="badge"
|
||||
:class="statusBadge[order.status] || 'badge--muted'"
|
||||
>
|
||||
{{ statusLabels[order.status] || order.status }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<span class="orders__meta-label">Meddelande</span>
|
||||
<span class="orders__meta-value orders__message">{{
|
||||
order.letterText
|
||||
}}</span>
|
||||
<div class="orders__preview-box">
|
||||
<p class="orders__preview">{{ order.letterText }}</p>
|
||||
</div>
|
||||
|
||||
<span class="orders__meta-label">Datum</span>
|
||||
<span class="orders__meta-value">{{
|
||||
formatDate(order.createdAt)
|
||||
}}</span>
|
||||
<p class="orders__order-date">
|
||||
Skapad {{ formatDate(order.createdAt) }}
|
||||
</p>
|
||||
|
||||
<template v-if="order.trackingId">
|
||||
<span class="orders__meta-label">Spårning</span>
|
||||
<a
|
||||
class="orders__tracking"
|
||||
v-if="order.trackingId"
|
||||
class="btn btn--ghost orders__tracking-btn"
|
||||
:href="`https://www.postnord.se/verktyg/spara/?id=${order.trackingId}`"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{{ order.trackingId }}
|
||||
Spåra brev · {{ order.trackingId }}
|
||||
</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__action-btn"
|
||||
>
|
||||
Betala nu
|
||||
</RouterLink>
|
||||
<RouterLink
|
||||
:to="{
|
||||
name: 'edit-order',
|
||||
params: { orderId: order.id },
|
||||
}"
|
||||
class="btn btn--ghost orders__action-btn"
|
||||
>
|
||||
Redigera
|
||||
</RouterLink>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn--ghost orders__action-btn orders__cancel-btn"
|
||||
:disabled="cancellingId === order.id"
|
||||
@click="handleCancel(order)"
|
||||
>
|
||||
{{
|
||||
cancellingId === order.id ? 'Avbryter...' : 'Avbryt beställning'
|
||||
}}
|
||||
</button>
|
||||
<div class="orders__order-ref">
|
||||
<p class="orders__order-ref-label">Beställnings-ID</p>
|
||||
<p class="orders__order-id">{{ order.id }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -214,6 +285,19 @@ onMounted(loadOrders)
|
|||
margin-bottom: var(--space-md);
|
||||
}
|
||||
|
||||
.orders__section + .orders__section {
|
||||
margin-top: var(--space-xl);
|
||||
}
|
||||
|
||||
.orders__section-title {
|
||||
margin: 0 0 var(--space-md) 0;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
color: var(--color-muted);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.orders__list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
|
@ -225,87 +309,191 @@ onMounted(loadOrders)
|
|||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-lg);
|
||||
overflow: hidden;
|
||||
box-shadow: var(--shadow-card);
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
.orders__card-top {
|
||||
.orders__card-content {
|
||||
padding: var(--space-lg);
|
||||
}
|
||||
|
||||
.orders__card--pending .orders__order-date {
|
||||
margin-bottom: var(--space-md);
|
||||
}
|
||||
|
||||
.orders__card-head {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: var(--space-md) var(--space-lg);
|
||||
background: var(--color-border-light);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
gap: var(--space-md);
|
||||
margin-bottom: var(--space-md);
|
||||
}
|
||||
|
||||
.orders__plate {
|
||||
font-size: 1.125rem;
|
||||
.orders__plate-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--space-sm);
|
||||
background: var(--color-primary-soft);
|
||||
padding: var(--space-sm) var(--space-md);
|
||||
border-radius: var(--radius-full);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.orders__plate-label {
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
color: var(--color-muted);
|
||||
}
|
||||
|
||||
.orders__plate-value {
|
||||
font-size: 0.9375rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.08em;
|
||||
color: var(--color-primary-dark);
|
||||
}
|
||||
|
||||
.orders__preview-box {
|
||||
background: var(--color-border-light);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-md);
|
||||
padding: var(--space-md);
|
||||
margin-bottom: var(--space-sm);
|
||||
}
|
||||
|
||||
.orders__preview {
|
||||
margin: 0;
|
||||
font-family: var(--font-serif);
|
||||
font-size: 0.9375rem;
|
||||
line-height: 1.6;
|
||||
color: var(--color-ink);
|
||||
white-space: pre-wrap;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 3;
|
||||
line-clamp: 3;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.orders__order-date {
|
||||
margin: 0 0 var(--space-sm) 0;
|
||||
font-size: 0.8125rem;
|
||||
color: var(--color-muted);
|
||||
}
|
||||
|
||||
.orders__tracking-btn {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
margin: var(--space-md) 0 var(--space-sm);
|
||||
font-size: 0.9375rem;
|
||||
font-weight: 600;
|
||||
color: var(--color-primary-dark);
|
||||
border-color: #ddd6fe;
|
||||
background: var(--color-primary-soft);
|
||||
}
|
||||
|
||||
.orders__tracking-btn:hover {
|
||||
background: #dbeafe;
|
||||
border-color: #93c5fd;
|
||||
}
|
||||
|
||||
.orders__order-ref {
|
||||
margin: var(--space-md) 0 0;
|
||||
padding-top: var(--space-md);
|
||||
border-top: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.orders__order-ref--highlight {
|
||||
margin: 0 0 var(--space-md);
|
||||
padding: var(--space-md);
|
||||
background: var(--color-border-light);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-md);
|
||||
}
|
||||
|
||||
.orders__order-ref-label {
|
||||
margin: 0 0 var(--space-xs) 0;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
color: var(--color-muted);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.orders__card-meta {
|
||||
padding: var(--space-md) var(--space-lg);
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
gap: var(--space-sm) var(--space-lg);
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.orders__meta-label {
|
||||
font-size: 0.8125rem;
|
||||
color: var(--color-soft);
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.orders__meta-value {
|
||||
font-size: 0.875rem;
|
||||
color: var(--color-ink);
|
||||
}
|
||||
|
||||
.orders__order-id {
|
||||
margin: 0;
|
||||
font-family: ui-monospace, monospace;
|
||||
font-size: 0.8125rem;
|
||||
color: var(--color-ink);
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.orders__message {
|
||||
white-space: pre-wrap;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.orders__tracking {
|
||||
font-size: 0.875rem;
|
||||
color: var(--color-primary);
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.orders__tracking:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.orders__card-actions {
|
||||
.orders__price-row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-sm);
|
||||
padding: var(--space-md) var(--space-lg);
|
||||
border-top: 1px solid var(--color-border);
|
||||
background: var(--color-border-light);
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: var(--space-md);
|
||||
padding-bottom: var(--space-md);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.orders__action-btn {
|
||||
.orders__price-label {
|
||||
font-size: 0.875rem;
|
||||
color: var(--color-muted);
|
||||
}
|
||||
|
||||
.orders__price-amount {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 700;
|
||||
color: var(--color-ink);
|
||||
}
|
||||
|
||||
.orders__pay-btn {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
margin-bottom: var(--space-md);
|
||||
}
|
||||
|
||||
.orders__cancel-btn {
|
||||
.orders__links {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: var(--space-sm);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.orders__text-link {
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 500;
|
||||
color: var(--color-primary);
|
||||
text-decoration: none;
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.orders__text-link:hover:not(:disabled) {
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 2px;
|
||||
}
|
||||
|
||||
.orders__text-link--danger {
|
||||
color: var(--color-danger);
|
||||
}
|
||||
|
||||
.orders__cancel-btn:hover:not(:disabled) {
|
||||
background: #fef2f2;
|
||||
.orders__text-link--danger:hover:not(:disabled) {
|
||||
color: #991b1b;
|
||||
}
|
||||
|
||||
.orders__text-link:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.orders__link-sep {
|
||||
color: var(--color-soft);
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.orders__empty {
|
||||
|
|
|
|||
Loading…
Reference in a new issue