- Rewrite homepage: practical headline, use-case cards, calm trust note - Switch from purple to blue brand tokens across all pages - Replace all CTA buttons with brand-primary, reserve green for success - Remove emoji from template picker and compose page - Replace unicode chevrons with SVG expand buttons in admin - Redesign template picker modal with accessibility semantics - Add aria-invalid, aria-describedby to form validation - Add role=status/alert to loading, error, and result messages - Remove inline styles, replace with scoped utility classes - Update compose submit text, payment button, order empty state copy - Remove icon field from letter templates
225 lines
5 KiB
Vue
225 lines
5 KiB
Vue
<script setup lang="ts">
|
|
import { ref, onMounted } from 'vue'
|
|
import { fetchOrders, type Order } from '@/api/orders'
|
|
import { RouterLink } from 'vue-router'
|
|
|
|
const orders = ref<Order[]>([])
|
|
const loading = ref(true)
|
|
const error = ref('')
|
|
|
|
const statusLabels: Record<string, string> = {
|
|
pending_payment: 'Väntar på betalning',
|
|
paid: 'Betalad',
|
|
lookup_started: 'Hanteras',
|
|
sent: 'Skickat',
|
|
delivered: 'Levererat',
|
|
failed: 'Misslyckad',
|
|
}
|
|
|
|
const statusBadge: Record<string, string> = {
|
|
pending_payment: 'badge--muted',
|
|
paid: 'badge--primary',
|
|
lookup_started: 'badge--primary',
|
|
sent: 'badge--success',
|
|
delivered: 'badge--success',
|
|
failed: 'badge--danger',
|
|
}
|
|
|
|
function formatDate(iso: string): string {
|
|
return new Date(iso).toLocaleDateString('sv-SE', {
|
|
year: 'numeric',
|
|
month: 'short',
|
|
day: 'numeric',
|
|
})
|
|
}
|
|
|
|
onMounted(async () => {
|
|
try {
|
|
orders.value = await fetchOrders()
|
|
} catch {
|
|
error.value = 'Kunde inte hämta beställningar. Försök igen senare.'
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<div class="page">
|
|
<h1 class="page__title">Mina beställningar</h1>
|
|
<p class="page__subtitle">Här kan du se dina tidigare beställningar.</p>
|
|
|
|
<p
|
|
v-if="loading"
|
|
class="text-muted text-center orders__loading"
|
|
role="status"
|
|
>
|
|
Laddar beställningar...
|
|
</p>
|
|
|
|
<div v-else-if="error" class="message message--error" role="alert">
|
|
{{ error }}
|
|
</div>
|
|
|
|
<div v-else-if="orders.length === 0" class="orders__empty">
|
|
<div class="orders__empty-card">
|
|
<p class="orders__empty-title">Inga beställningar ännu</p>
|
|
<p class="orders__empty-text">
|
|
Följ dina brev och se tidigare skickade hälsningar.
|
|
</p>
|
|
<RouterLink to="/" class="btn btn--primary orders__empty-cta">
|
|
Skicka första brevet
|
|
</RouterLink>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-else 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>
|
|
</div>
|
|
|
|
<div class="orders__card-meta">
|
|
<span class="orders__meta-label">Datum</span>
|
|
<span class="orders__meta-value">{{
|
|
formatDate(order.createdAt)
|
|
}}</span>
|
|
|
|
<template v-if="order.trackingId">
|
|
<span class="orders__meta-label">Spårning</span>
|
|
<a
|
|
class="orders__tracking"
|
|
:href="`https://www.postnord.se/verktyg/spara/?id=${order.trackingId}`"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
>
|
|
{{ order.trackingId }}
|
|
</a>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.page {
|
|
max-width: 48rem;
|
|
margin: var(--space-3xl) auto 0;
|
|
padding: 0 var(--space-lg);
|
|
}
|
|
|
|
.page__title {
|
|
margin: 0 0 var(--space-sm) 0;
|
|
font-size: 1.5rem;
|
|
color: var(--color-ink);
|
|
}
|
|
|
|
.page__subtitle {
|
|
margin: 0 0 var(--space-xl) 0;
|
|
font-size: 0.875rem;
|
|
color: var(--color-muted);
|
|
}
|
|
|
|
.orders__list {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: var(--space-md);
|
|
}
|
|
|
|
.orders__card {
|
|
background: var(--color-surface);
|
|
border: 1px solid var(--color-border);
|
|
border-radius: var(--radius-lg);
|
|
overflow: hidden;
|
|
box-shadow: var(--shadow-card);
|
|
}
|
|
|
|
.orders__card-top {
|
|
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);
|
|
}
|
|
|
|
.orders__plate {
|
|
font-size: 1.125rem;
|
|
font-weight: 600;
|
|
color: var(--color-ink);
|
|
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__tracking {
|
|
font-size: 0.875rem;
|
|
color: var(--color-primary);
|
|
text-decoration: none;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.orders__tracking:hover {
|
|
text-decoration: underline;
|
|
}
|
|
|
|
.orders__empty {
|
|
padding: var(--space-2xl) 0;
|
|
text-align: center;
|
|
}
|
|
|
|
.orders__empty-card {
|
|
background: var(--color-surface);
|
|
border: 1px solid var(--color-border);
|
|
border-radius: var(--radius-xl);
|
|
padding: var(--space-2xl);
|
|
box-shadow: var(--shadow-card);
|
|
}
|
|
|
|
.orders__empty-title {
|
|
font-size: 1.125rem;
|
|
font-weight: 600;
|
|
color: var(--color-ink);
|
|
margin: 0 0 var(--space-sm) 0;
|
|
}
|
|
|
|
.orders__empty-text {
|
|
font-size: 0.875rem;
|
|
color: var(--color-muted);
|
|
margin: 0;
|
|
}
|
|
|
|
.orders__empty-cta {
|
|
margin-top: var(--space-md);
|
|
display: inline-flex;
|
|
}
|
|
|
|
.orders__loading {
|
|
padding: var(--space-2xl) 0;
|
|
}
|
|
</style>
|