refactor: add design system with CSS tokens, utilities, and app shell
- Add design tokens (colors, spacing, radius, shadows, typography, transitions) - Add global reset, body/link/focus/typography base styles - Add utility classes (container, surface-card, btn variants, field, badge, message, divider) - Replace header ✉ symbol with inline SVG envelope icon - Update favicon to license-plate shaped mark with blue gradient and bold B - Rename brand from BilHälsning to Bilhej in header, footer, and HTML title - Rewrite footer tagline: focus on service, not privacy - Add theme-color meta tag for browser chrome
This commit is contained in:
parent
8cd7991603
commit
00327674ed
9 changed files with 653 additions and 68 deletions
|
|
@ -2,9 +2,14 @@
|
|||
<html lang="sv">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg?v=4" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>BilHälsning</title>
|
||||
<meta name="description" content="Skicka ett brev till en fordonsägare. Ange registreringsnummer, skriv ditt meddelande, så postar vi det." />
|
||||
<meta name="theme-color" content="#1d4ed8" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
|
||||
<title>Bilhej — Skicka brev till fordonsägare</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 9.3 KiB After Width: | Height: | Size: 990 B |
|
|
@ -14,6 +14,6 @@ import AppFooter from '@/components/AppFooter.vue'
|
|||
|
||||
<style>
|
||||
.app__main {
|
||||
min-height: calc(100vh - 12rem);
|
||||
min-height: calc(100vh - 10rem);
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -29,6 +29,6 @@ describe('App', () => {
|
|||
plugins: [router, createPinia()],
|
||||
},
|
||||
})
|
||||
expect(wrapper.text()).toContain('Skicka ett brev till en fordonsägare')
|
||||
expect(wrapper.text()).toContain('Skicka ett brev')
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -25,6 +25,11 @@ function createTestRouter() {
|
|||
name: 'orders',
|
||||
component: { template: '<div>Orders</div>' },
|
||||
},
|
||||
{
|
||||
path: '/admin',
|
||||
name: 'admin',
|
||||
component: { template: '<div>Admin</div>' },
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
|
|
@ -47,7 +52,7 @@ describe('AppHeader', () => {
|
|||
const wrapper = mount(AppHeader, {
|
||||
global: { plugins: [router, createPinia()] },
|
||||
})
|
||||
expect(wrapper.text()).toContain('BilHälsning')
|
||||
expect(wrapper.text()).toContain('Bilhej')
|
||||
})
|
||||
|
||||
it('has a link to home', () => {
|
||||
|
|
@ -113,38 +118,39 @@ describe('AppHeader', () => {
|
|||
})
|
||||
|
||||
describe('when authenticated', () => {
|
||||
function mountAuthenticated() {
|
||||
const jwt = makeJwt({ sub: 'test@bilhalsning.se', role: 'user' })
|
||||
function mountAuthenticated(role = 'user') {
|
||||
const jwt = makeJwt({ sub: 'test@bilhalsning.se', role })
|
||||
localStorage.setItem('auth_token', jwt)
|
||||
const pinia = createPinia()
|
||||
setActivePinia(pinia)
|
||||
const router = createTestRouter()
|
||||
return mount(AppHeader, {
|
||||
const wrapper = mount(AppHeader, {
|
||||
global: { plugins: [router, pinia] },
|
||||
})
|
||||
return { wrapper, router }
|
||||
}
|
||||
|
||||
it('shows user email', () => {
|
||||
const wrapper = mountAuthenticated()
|
||||
const { wrapper } = mountAuthenticated()
|
||||
expect(wrapper.text()).toContain('test@bilhalsning.se')
|
||||
})
|
||||
|
||||
it('shows logout button', () => {
|
||||
const wrapper = mountAuthenticated()
|
||||
const { wrapper } = mountAuthenticated()
|
||||
const logoutButton = wrapper.find('button')
|
||||
expect(logoutButton.exists()).toBe(true)
|
||||
expect(logoutButton.text()).toBe('Logga ut')
|
||||
})
|
||||
|
||||
it('does not show login link', () => {
|
||||
const wrapper = mountAuthenticated()
|
||||
const { wrapper } = mountAuthenticated()
|
||||
const links = wrapper.findAll('a')
|
||||
const loginLink = links.find((a) => a.attributes('href') === '/logga-in')
|
||||
expect(loginLink).toBeUndefined()
|
||||
})
|
||||
|
||||
it('does not show register link', () => {
|
||||
const wrapper = mountAuthenticated()
|
||||
const { wrapper } = mountAuthenticated()
|
||||
const links = wrapper.findAll('a')
|
||||
const registerLink = links.find(
|
||||
(a) => a.attributes('href') === '/registrera',
|
||||
|
|
@ -153,21 +159,47 @@ describe('AppHeader', () => {
|
|||
})
|
||||
|
||||
it('shows orders link', () => {
|
||||
const wrapper = mountAuthenticated()
|
||||
const { wrapper } = mountAuthenticated()
|
||||
const links = wrapper.findAll('a')
|
||||
const ordersLink = links.find((a) => a.attributes('href') === '/orders')
|
||||
expect(ordersLink).toBeTruthy()
|
||||
expect(ordersLink?.text()).toBe('Mina beställningar')
|
||||
})
|
||||
|
||||
it('calls logout when clicking logout button', async () => {
|
||||
const wrapper = mountAuthenticated()
|
||||
it('does not show admin link for regular user', () => {
|
||||
const { wrapper } = mountAuthenticated('user')
|
||||
const links = wrapper.findAll('a')
|
||||
const adminLink = links.find((a) => a.attributes('href') === '/admin')
|
||||
expect(adminLink).toBeUndefined()
|
||||
})
|
||||
|
||||
it('shows admin link for admin user', () => {
|
||||
const { wrapper } = mountAuthenticated('admin')
|
||||
const links = wrapper.findAll('a')
|
||||
const adminLink = links.find((a) => a.attributes('href') === '/admin')
|
||||
expect(adminLink).toBeTruthy()
|
||||
expect(adminLink?.text()).toBe('Admin')
|
||||
})
|
||||
|
||||
it('calls logout and redirects to home when clicking logout button', async () => {
|
||||
const { wrapper, router } = mountAuthenticated()
|
||||
const auth = useAuthStore()
|
||||
expect(auth.isAuthenticated).toBe(true)
|
||||
|
||||
await router.push('/orders')
|
||||
await router.isReady()
|
||||
|
||||
const navigationDone = new Promise<void>((resolve) => {
|
||||
const remove = router.afterEach(() => {
|
||||
remove()
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
await wrapper.find('button').trigger('click')
|
||||
await navigationDone
|
||||
|
||||
expect(auth.isAuthenticated).toBe(false)
|
||||
expect(router.currentRoute.value.path).toBe('/')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
409
frontend/src/assets/styles/base.css
Normal file
409
frontend/src/assets/styles/base.css
Normal file
|
|
@ -0,0 +1,409 @@
|
|||
/* ── Reset ────────────────────────────────────────────────────────────── */
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
img,
|
||||
svg {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
input,
|
||||
button,
|
||||
textarea,
|
||||
select {
|
||||
font: inherit;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* ── Design Tokens ───────────────────────────────────────────────────── */
|
||||
:root {
|
||||
/* ink / text */
|
||||
--color-ink: #111827;
|
||||
--color-muted: #667085;
|
||||
--color-soft: #9ca3af;
|
||||
|
||||
/* surfaces */
|
||||
--color-paper: #fdfaf5;
|
||||
--color-surface: #ffffff;
|
||||
--color-surface-tint: #f5f0ff;
|
||||
|
||||
/* brand */
|
||||
--color-primary: #1d4ed8;
|
||||
--color-primary-dark: #1e3a8a;
|
||||
--color-primary-soft: #dbeafe;
|
||||
--color-primary-ring: rgba(29, 78, 216, 0.22);
|
||||
|
||||
/* accent */
|
||||
--color-accent: #0f766e;
|
||||
--color-accent-soft: #ccfbf1;
|
||||
|
||||
/* semantic */
|
||||
--color-success: #15803d;
|
||||
--color-success-soft: #f0fdf4;
|
||||
--color-warning: #b45309;
|
||||
--color-warning-soft: #fffbeb;
|
||||
--color-danger: #b91c1c;
|
||||
--color-danger-soft: #fef2f2;
|
||||
|
||||
/* borders & dividers */
|
||||
--color-border: #e5e7eb;
|
||||
--color-border-light: #f3f4f6;
|
||||
|
||||
/* spacing */
|
||||
--space-xs: 0.25rem;
|
||||
--space-sm: 0.5rem;
|
||||
--space-md: 1rem;
|
||||
--space-lg: 1.5rem;
|
||||
--space-xl: 2rem;
|
||||
--space-2xl: 3rem;
|
||||
--space-3xl: 4rem;
|
||||
|
||||
/* radius */
|
||||
--radius-sm: 0.375rem;
|
||||
--radius-md: 0.5rem;
|
||||
--radius-lg: 0.75rem;
|
||||
--radius-xl: 1rem;
|
||||
--radius-full: 9999px;
|
||||
|
||||
/* shadows */
|
||||
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.07),
|
||||
0 2px 4px -2px rgba(0, 0, 0, 0.05);
|
||||
--shadow-lg: 0 10px 25px -5px rgba(0, 0, 0, 0.08),
|
||||
0 8px 10px -6px rgba(0, 0, 0, 0.05);
|
||||
--shadow-xl: 0 20px 60px rgba(0, 0, 0, 0.12);
|
||||
--shadow-card: 0 1px 3px rgba(0, 0, 0, 0.06),
|
||||
0 1px 2px rgba(0, 0, 0, 0.04);
|
||||
|
||||
/* typography */
|
||||
--font-sans: 'Inter', system-ui, -apple-system, BlinkMacSystemFont,
|
||||
'Segoe UI', Roboto, sans-serif;
|
||||
--font-serif: Georgia, 'Times New Roman', serif;
|
||||
--font-mono: 'JetBrains Mono', 'Fira Code', ui-monospace, monospace;
|
||||
|
||||
/* transitions */
|
||||
--transition-fast: 150ms ease;
|
||||
--transition-base: 200ms ease;
|
||||
}
|
||||
|
||||
/* ── Body ────────────────────────────────────────────────────────────── */
|
||||
body {
|
||||
font-family: var(--font-sans);
|
||||
font-size: 1rem;
|
||||
line-height: 1.6;
|
||||
color: var(--color-ink);
|
||||
background: var(--color-paper);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
/* ── Typography ──────────────────────────────────────────────────────── */
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4 {
|
||||
line-height: 1.25;
|
||||
font-weight: 700;
|
||||
color: var(--color-ink);
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 1.75rem;
|
||||
}
|
||||
h2 {
|
||||
font-size: 1.375rem;
|
||||
}
|
||||
h3 {
|
||||
font-size: 1.125rem;
|
||||
}
|
||||
h4 {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
p {
|
||||
color: var(--color-muted);
|
||||
}
|
||||
|
||||
/* ── Links ───────────────────────────────────────────────────────────── */
|
||||
a[href] {
|
||||
color: var(--color-primary);
|
||||
transition: color var(--transition-fast);
|
||||
}
|
||||
a[href]:hover {
|
||||
color: var(--color-primary-dark);
|
||||
}
|
||||
|
||||
/* ── Buttons as links ────────────────────────────────────────────────── */
|
||||
.btn[href],
|
||||
.btn[href]:hover {
|
||||
color: inherit;
|
||||
}
|
||||
.btn--primary[href],
|
||||
.btn--primary[href]:hover {
|
||||
color: #fff;
|
||||
}
|
||||
.btn--success[href],
|
||||
.btn--success[href]:hover {
|
||||
color: #fff;
|
||||
}
|
||||
.btn--accent[href],
|
||||
.btn--accent[href]:hover {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* ── Focus ───────────────────────────────────────────────────────────── */
|
||||
:focus-visible {
|
||||
outline: 2px solid var(--color-primary);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
/* ── Container ───────────────────────────────────────────────────────── */
|
||||
.container {
|
||||
width: 100%;
|
||||
max-width: 72rem;
|
||||
margin-inline: auto;
|
||||
padding-inline: var(--space-lg);
|
||||
}
|
||||
|
||||
.container--narrow {
|
||||
max-width: 36rem;
|
||||
}
|
||||
|
||||
.container--wide {
|
||||
max-width: 80rem;
|
||||
}
|
||||
|
||||
/* ── Surface card ────────────────────────────────────────────────────── */
|
||||
.surface-card {
|
||||
background: var(--color-surface);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: var(--space-lg);
|
||||
box-shadow: var(--shadow-card);
|
||||
}
|
||||
|
||||
/* ── buttons ─────────────────────────────────────────────────────────── */
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: var(--space-sm);
|
||||
padding: 0.75rem 1.5rem;
|
||||
font-size: 0.9375rem;
|
||||
font-weight: 600;
|
||||
border: none;
|
||||
border-radius: var(--radius-md);
|
||||
cursor: pointer;
|
||||
transition: background var(--transition-fast),
|
||||
transform var(--transition-fast), box-shadow var(--transition-fast);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.btn:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.btn:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.btn--primary {
|
||||
background: var(--color-primary);
|
||||
color: #fff;
|
||||
}
|
||||
.btn--primary:hover:not(:disabled) {
|
||||
background: var(--color-primary-dark);
|
||||
}
|
||||
|
||||
.btn--success {
|
||||
background: var(--color-success);
|
||||
color: #fff;
|
||||
}
|
||||
.btn--success:hover:not(:disabled) {
|
||||
background: #166534;
|
||||
}
|
||||
|
||||
.btn--accent {
|
||||
background: var(--color-accent);
|
||||
color: #fff;
|
||||
}
|
||||
.btn--accent:hover:not(:disabled) {
|
||||
background: #0284c7;
|
||||
}
|
||||
|
||||
.btn--ghost {
|
||||
background: transparent;
|
||||
color: var(--color-ink);
|
||||
border: 1px solid var(--color-border);
|
||||
}
|
||||
.btn--ghost:hover:not(:disabled) {
|
||||
background: var(--color-border-light);
|
||||
}
|
||||
|
||||
.btn--sm {
|
||||
padding: 0.5rem 1rem;
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
|
||||
.btn--lg {
|
||||
padding: 1rem 2rem;
|
||||
font-size: 1.0625rem;
|
||||
}
|
||||
|
||||
/* ── Form fields ─────────────────────────────────────────────────────── */
|
||||
.field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-xs);
|
||||
}
|
||||
|
||||
.field__label {
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
color: var(--color-ink);
|
||||
}
|
||||
|
||||
.field__input {
|
||||
width: 100%;
|
||||
padding: 0.75rem 1rem;
|
||||
font-size: 1rem;
|
||||
background: var(--color-surface);
|
||||
border: 2px solid var(--color-border);
|
||||
border-radius: var(--radius-md);
|
||||
outline: none;
|
||||
transition: border-color var(--transition-fast),
|
||||
box-shadow var(--transition-fast);
|
||||
}
|
||||
|
||||
.field__input:focus {
|
||||
border-color: var(--color-primary);
|
||||
box-shadow: 0 0 0 3px var(--color-primary-ring);
|
||||
}
|
||||
|
||||
.field__input--error {
|
||||
border-color: var(--color-danger);
|
||||
}
|
||||
.field__input--error:focus {
|
||||
box-shadow: 0 0 0 3px rgba(185, 28, 28, 0.2);
|
||||
}
|
||||
|
||||
.field__error {
|
||||
font-size: 0.8125rem;
|
||||
color: var(--color-danger);
|
||||
}
|
||||
|
||||
.field__hint {
|
||||
font-size: 0.8125rem;
|
||||
color: var(--color-soft);
|
||||
}
|
||||
|
||||
/* ── Badge ───────────────────────────────────────────────────────────── */
|
||||
.badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 0.2rem 0.75rem;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
border-radius: var(--radius-full);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.badge--muted {
|
||||
background: var(--color-border-light);
|
||||
color: var(--color-muted);
|
||||
}
|
||||
.badge--primary {
|
||||
background: var(--color-primary-soft);
|
||||
color: var(--color-primary-dark);
|
||||
}
|
||||
.badge--success {
|
||||
background: var(--color-success-soft);
|
||||
color: var(--color-success);
|
||||
}
|
||||
.badge--warning {
|
||||
background: var(--color-warning-soft);
|
||||
color: var(--color-warning);
|
||||
}
|
||||
.badge--danger {
|
||||
background: var(--color-danger-soft);
|
||||
color: var(--color-danger);
|
||||
}
|
||||
|
||||
/* ── Message boxes ───────────────────────────────────────────────────── */
|
||||
.message {
|
||||
padding: var(--space-md) var(--space-lg);
|
||||
border-radius: var(--radius-md);
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.message--error {
|
||||
background: var(--color-danger-soft);
|
||||
border: 1px solid #fecaca;
|
||||
color: var(--color-danger);
|
||||
}
|
||||
|
||||
.message--info {
|
||||
background: var(--color-primary-soft);
|
||||
border: 1px solid #ddd6fe;
|
||||
color: var(--color-primary-dark);
|
||||
}
|
||||
|
||||
.message--success {
|
||||
background: var(--color-success-soft);
|
||||
border: 1px solid #bbf7d0;
|
||||
color: var(--color-success);
|
||||
}
|
||||
|
||||
/* ── Divider ─────────────────────────────────────────────────────────── */
|
||||
.divider {
|
||||
border: none;
|
||||
border-top: 1px solid var(--color-border);
|
||||
margin: var(--space-lg) 0;
|
||||
}
|
||||
|
||||
/* ── Eyebrow ─────────────────────────────────────────────────────────── */
|
||||
.eyebrow {
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
/* ── Utility ─────────────────────────────────────────────────────────── */
|
||||
.visually-hidden {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
.text-muted {
|
||||
color: var(--color-muted);
|
||||
}
|
||||
.text-sm {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
.text-xs {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
|
@ -4,6 +4,11 @@ import { RouterLink } from 'vue-router'
|
|||
|
||||
<template>
|
||||
<footer class="app-footer">
|
||||
<div class="app-footer__inner">
|
||||
<p class="app-footer__tagline">
|
||||
Bilhej hjälper dig att skicka brev till bilägare via
|
||||
registreringsnummer.
|
||||
</p>
|
||||
<nav class="app-footer__links">
|
||||
<RouterLink to="/om" class="app-footer__link">Om oss</RouterLink>
|
||||
<RouterLink to="/kontakt" class="app-footer__link">Kontakt</RouterLink>
|
||||
|
|
@ -12,31 +17,54 @@ import { RouterLink } from 'vue-router'
|
|||
>
|
||||
<RouterLink to="/villkor" class="app-footer__link">Villkor</RouterLink>
|
||||
</nav>
|
||||
<p class="app-footer__copy">
|
||||
© {{ new Date().getFullYear() }} Bilhej
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.app-footer {
|
||||
background: #f7fafc;
|
||||
border-top: 1px solid #e2e8f0;
|
||||
padding: 1.5rem;
|
||||
background: var(--color-surface);
|
||||
border-top: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.app-footer__inner {
|
||||
max-width: 72rem;
|
||||
margin: 0 auto;
|
||||
padding: var(--space-xl) var(--space-lg);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.app-footer__tagline {
|
||||
color: var(--color-muted);
|
||||
font-size: 0.875rem;
|
||||
margin: 0 0 var(--space-lg) 0;
|
||||
}
|
||||
|
||||
.app-footer__links {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 2rem;
|
||||
gap: var(--space-xl);
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: var(--space-lg);
|
||||
}
|
||||
|
||||
.app-footer__link {
|
||||
color: #718096;
|
||||
color: var(--color-soft);
|
||||
text-decoration: none;
|
||||
font-size: 0.8125rem;
|
||||
transition: color var(--transition-fast);
|
||||
}
|
||||
|
||||
.app-footer__link:hover {
|
||||
color: #1a202c;
|
||||
color: var(--color-ink);
|
||||
}
|
||||
|
||||
.app-footer__copy {
|
||||
color: var(--color-soft);
|
||||
font-size: 0.75rem;
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,13 +1,46 @@
|
|||
<script setup lang="ts">
|
||||
import { RouterLink } from 'vue-router'
|
||||
import { RouterLink, useRouter } from 'vue-router'
|
||||
import { useAuthStore } from '@/stores/authStore'
|
||||
|
||||
const router = useRouter()
|
||||
const auth = useAuthStore()
|
||||
|
||||
function handleLogout() {
|
||||
auth.logout()
|
||||
router.push('/')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<header class="app-header">
|
||||
<RouterLink to="/" class="app-header__logo">BilHälsning</RouterLink>
|
||||
<div class="app-header__inner">
|
||||
<RouterLink to="/" class="app-header__logo">
|
||||
<svg
|
||||
class="app-header__logo-icon"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<rect
|
||||
x="2"
|
||||
y="5"
|
||||
width="20"
|
||||
height="14"
|
||||
rx="2"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
/>
|
||||
<path
|
||||
d="M2 7l10 6 10-6"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
Bilhej
|
||||
</RouterLink>
|
||||
<nav class="app-header__nav">
|
||||
<RouterLink to="/" class="app-header__link">Hem</RouterLink>
|
||||
<template v-if="!auth.isAuthenticated">
|
||||
|
|
@ -19,65 +52,119 @@ const auth = useAuthStore()
|
|||
>
|
||||
</template>
|
||||
<template v-else>
|
||||
<RouterLink
|
||||
v-if="auth.isAdmin"
|
||||
to="/admin"
|
||||
class="app-header__link app-header__link--admin"
|
||||
>Admin</RouterLink
|
||||
>
|
||||
<RouterLink to="/orders" class="app-header__link"
|
||||
>Mina beställningar</RouterLink
|
||||
>
|
||||
<span class="app-header__email">{{ auth.email }}</span>
|
||||
<button class="app-header__logout" @click="auth.logout()">
|
||||
<button class="app-header__logout" @click="handleLogout">
|
||||
Logga ut
|
||||
</button>
|
||||
</template>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.app-header {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
background: rgba(253, 250, 245, 0.85);
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.app-header__inner {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 1rem 1.5rem;
|
||||
border-bottom: 1px solid #e2e8f0;
|
||||
background: #fff;
|
||||
max-width: 72rem;
|
||||
margin: 0 auto;
|
||||
padding: 0.875rem var(--space-lg);
|
||||
}
|
||||
|
||||
.app-header__logo {
|
||||
font-size: 1.25rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-sm);
|
||||
font-size: 1.125rem;
|
||||
font-weight: 700;
|
||||
color: #1a202c;
|
||||
color: var(--color-ink);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.app-header__logo-icon {
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
}
|
||||
|
||||
.app-header__nav {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
align-items: center;
|
||||
gap: var(--space-sm);
|
||||
}
|
||||
|
||||
.app-header__link {
|
||||
color: #4a5568;
|
||||
text-decoration: none;
|
||||
padding: 0.4rem 0.875rem;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
color: var(--color-muted);
|
||||
text-decoration: none;
|
||||
border-radius: var(--radius-full);
|
||||
transition:
|
||||
color var(--transition-fast),
|
||||
background var(--transition-fast);
|
||||
}
|
||||
|
||||
.app-header__link:hover {
|
||||
color: #1a202c;
|
||||
.app-header__link:hover,
|
||||
.app-header__link.router-link-active {
|
||||
color: var(--color-primary-dark);
|
||||
background: var(--color-primary-soft);
|
||||
}
|
||||
|
||||
.app-header__link--admin {
|
||||
background: var(--color-primary-soft);
|
||||
color: var(--color-primary);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.app-header__link--admin:hover {
|
||||
background: #e9d5ff;
|
||||
color: var(--color-primary-dark);
|
||||
}
|
||||
|
||||
.app-header__email {
|
||||
color: #4a5568;
|
||||
font-size: 0.875rem;
|
||||
color: var(--color-muted);
|
||||
font-size: 0.8125rem;
|
||||
padding: 0 0.5rem;
|
||||
}
|
||||
|
||||
.app-header__logout {
|
||||
background: none;
|
||||
border: none;
|
||||
color: #4a5568;
|
||||
font-size: 0.875rem;
|
||||
border: 1px solid var(--color-border);
|
||||
color: var(--color-muted);
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
padding: 0.35rem 0.875rem;
|
||||
border-radius: var(--radius-full);
|
||||
transition:
|
||||
color var(--transition-fast),
|
||||
border-color var(--transition-fast),
|
||||
background var(--transition-fast);
|
||||
}
|
||||
|
||||
.app-header__logout:hover {
|
||||
color: #1a202c;
|
||||
color: var(--color-danger);
|
||||
border-color: var(--color-danger);
|
||||
background: var(--color-danger-soft);
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { createApp } from 'vue'
|
|||
import { createPinia } from 'pinia'
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
import './assets/styles/base.css'
|
||||
|
||||
const app = createApp(App)
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue