Compare commits

...

7 commits

Author SHA1 Message Date
3f20656f04 Merge pull request 'feature/cancel-edit-pending-orders' (#4) from feature/cancel-edit-pending-orders into master
All checks were successful
CI / Lint, type check, unit tests, coverage (push) Successful in 2m21s
CI / E2E browser tests (push) Successful in 56s
Reviewed-on: https://srvr.nu/git/git/jocke/bilhej/pulls/4
2026-05-22 11:54:15 +00:00
a12e07ec1c Register routes for integritetspolicy and villkor legal pages.
All checks were successful
CI / Lint, type check, unit tests, coverage (pull_request) Successful in 2m4s
CI / E2E browser tests (pull_request) Successful in 57s
- Add /integritetspolicy and /villkor to Vue Router
- Add Router tests confirming both public legal routes resolve

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-22 13:51:20 +02:00
ec62ba7673 Add användarvillkor page for Bilhej service terms.
- Create TermsOfServicePage covering Swish payment, letter content rules, and liability
- Describe edit/cancel before payment and refund expectations after posting
- Link to integritetspolicy and support contact in footer CTA
- Add TermsOfServicePage unit tests

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-22 13:51:20 +02:00
258f6f5a17 Add integritetspolicy page with Phase 0 privacy copy.
- Create PrivacyPolicyPage with sections on data, rights, and letter recipients
- Use plain Swedish without technical jargon or operational detail
- Link to kontakt page and mailto for privacy questions
- Add PrivacyPolicyPage unit tests

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-22 13:51:20 +02:00
bce2447238 Rework contact page emails and simplify mailto actions.
- Add support@bilhej.se for orders and technical issues
- Move complaints to klagomal@bilhej.se instead of personal Gmail
- Show one mailto chip per card instead of duplicate link and button
- Update ContactPage tests and production email checklist for all @bilhej.se addresses

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-22 13:51:20 +02:00
c0c32b718b Merge about page prose into hero and drop redundant section heading.
- Move the three explanatory paragraphs into the hero card under the lead
- Remove the separate "Vad vi gör" section that repeated the same framing
- Add a light divider between lead and body text for readability

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-22 13:51:11 +02:00
255095e6bd Document kontakt@bilhej.se receiving and fix stale contact address in requirements.
- Add production checklist section for Resend inbound on bilhej.se
- Note that mail is read in the Resend dashboard unless a webhook is added later
- Update GDPR letter footer example in REQUIREMENTS.md to kontakt@bilhej.se

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-22 12:59:49 +02:00
11 changed files with 718 additions and 36 deletions

View file

@ -446,7 +446,7 @@ Gross margin: 14 SEK
| Is a license plate personal data? | Yes (it directly identifies a vehicle owner). |
| Is an address personal data? | Yes. |
| What if we only process address transiently? | Data minimization is a GDPR principle (Art. 5(1)(c)). Transient processing with immediate deletion is a strong compliance posture. |
| Do we need to inform the recipient? | Yes, GDPR Art. 14 requires informing the data subject. The letter itself can serve this purpose — include a footer like: _"Detta brev skickades via BilHej.se. Din adress hämtades från Transportstyrelsens fordonsregister och har raderats efter utskick. För frågor: hej@bilhalsning.se"_ |
| Do we need to inform the recipient? | Yes, GDPR Art. 14 requires informing the data subject. The letter itself can serve this purpose — include a footer like: _"Detta brev skickades via BilHej.se. Din adress hämtades från Transportstyrelsens fordonsregister och har raderats efter utskick. För frågor: kontakt@bilhej.se"_ |
### 11.2 Transportstyrelsen Access

View file

@ -54,3 +54,30 @@ Fallback: reset links still log when `MAIL_HOST` is empty.
Keep using Mailpit (`docker compose up`, http://localhost:8025). Do not point local Docker at
Resend unless you intend to send real mail.
## 5. Inbound email on bilhej.se
Inbound mail uses **Resend Receiving** on the root domain `bilhej.se`. No mailbox is created in
Strato; the MX record routes all `@bilhej.se` addresses to Resend. You do not create each address
separately in Resend.
**Setup (done once):**
1. Resend → **Domains**`bilhej.se` → enable **Receiving**
2. Strato → **DNS** → add the receiving MX record (e.g. `inbound-smtp.eu-west-1.amazonaws.com`)
3. Wait until Resend shows receiving as **Verified**
4. Send test mail to `support@bilhej.se` and `kontakt@bilhej.se`; confirm both appear under **Emails → Receiving**
**Reading mail:** open the [Resend Receiving inbox](https://resend.com/emails/receiving). There is
no automatic forward to Gmail unless you add a webhook handler later.
| Address | Purpose | Where mail goes |
|---------|---------|-----------------|
| `support@bilhej.se` | Orders, Swish, status, technical issues | Resend dashboard |
| `kontakt@bilhej.se` | General contact, printed letter footer | Resend dashboard |
| `klagomal@bilhej.se` | Complaints (shown on `/kontakt`) | Resend dashboard |
| `noreply@bilhej.se` | Outbound only (password reset) | Not an inbox |
**Optional later (same Resend inbox, no extra DNS):** `abuse@bilhej.se` if you want a published
address for misuse reports; `privacy@bilhej.se` if integritetspolicy asks for a dedicated
data-protection contact.

View file

@ -9,17 +9,26 @@ describe('ContactPage', () => {
expect(wrapper.text()).toContain('klagomål')
})
it('renders general support email', () => {
it('renders support email', () => {
const wrapper = mount(ContactPage)
const link = wrapper.find('a[href="mailto:kontakt@bilhej.se"]')
expect(link.exists()).toBe(true)
expect(link.text()).toBe('kontakt@bilhej.se')
expect(wrapper.text()).toContain('support@bilhej.se')
})
it('renders general contact email', () => {
const wrapper = mount(ContactPage)
expect(wrapper.text()).toContain('kontakt@bilhej.se')
})
it('renders complaints email', () => {
const wrapper = mount(ContactPage)
const link = wrapper.find('a[href="mailto:jcamorling@gmail.com"]')
expect(wrapper.text()).toContain('klagomal@bilhej.se')
})
it('links support to mailto', () => {
const wrapper = mount(ContactPage)
const link = wrapper.find('a[href="mailto:support@bilhej.se"]')
expect(link.exists()).toBe(true)
expect(wrapper.text()).toContain('jcamorling@gmail.com')
expect(link.text()).toBe('support@bilhej.se')
expect(link.attributes('aria-label')).toBe('Skicka till support: support@bilhej.se')
})
})

View file

@ -0,0 +1,54 @@
import { describe, it, expect } from 'vitest'
import { mount } from '@vue/test-utils'
import { createRouter, createMemoryHistory } from 'vue-router'
import PrivacyPolicyPage from '@/pages/PrivacyPolicyPage.vue'
function createTestRouter() {
return createRouter({
history: createMemoryHistory(),
routes: [
{
path: '/integritetspolicy',
name: 'privacy',
component: PrivacyPolicyPage,
},
{
path: '/kontakt',
name: 'contact',
component: { template: '<div>Kontakt</div>' },
},
],
})
}
describe('PrivacyPolicyPage', () => {
it('renders title and lead', () => {
const router = createTestRouter()
const wrapper = mount(PrivacyPolicyPage, {
global: { plugins: [router] },
})
expect(wrapper.text()).toContain('Integritetspolicy')
expect(wrapper.text()).toContain('personuppgifter')
})
it('describes sender and recipient data handling', () => {
const router = createTestRouter()
const wrapper = mount(PrivacyPolicyPage, {
global: { plugins: [router] },
})
expect(wrapper.text()).toContain('Mottagarens postadress')
expect(wrapper.text()).toContain('sparas inte efter utskick')
expect(wrapper.text()).toContain('varken vi eller obehöriga')
})
it('links to contact email and contact page', () => {
const router = createTestRouter()
const wrapper = mount(PrivacyPolicyPage, {
global: { plugins: [router] },
})
expect(wrapper.find('a[href="mailto:kontakt@bilhej.se"]').exists()).toBe(
true,
)
expect(wrapper.find('a.policy__link').attributes('href')).toBe('/kontakt')
})
})

View file

@ -32,6 +32,18 @@ describe('Router', () => {
expect(router.currentRoute.value.name).toBe('forgot-password')
})
it('resolves /integritetspolicy to PrivacyPolicyPage', async () => {
await router.push('/integritetspolicy')
await router.isReady()
expect(router.currentRoute.value.name).toBe('privacy')
})
it('resolves /villkor to TermsOfServicePage', async () => {
await router.push('/villkor')
await router.isReady()
expect(router.currentRoute.value.name).toBe('terms')
})
it('resolves /aterstall-losenord to ResetPasswordPage', async () => {
await router.push('/aterstall-losenord?token=abc')
await router.isReady()

View file

@ -0,0 +1,58 @@
import { describe, it, expect } from 'vitest'
import { mount } from '@vue/test-utils'
import { createRouter, createMemoryHistory } from 'vue-router'
import TermsOfServicePage from '@/pages/TermsOfServicePage.vue'
function createTestRouter() {
return createRouter({
history: createMemoryHistory(),
routes: [
{
path: '/villkor',
name: 'terms',
component: TermsOfServicePage,
},
{
path: '/integritetspolicy',
name: 'privacy',
component: { template: '<div>Integritet</div>' },
},
{
path: '/kontakt',
name: 'contact',
component: { template: '<div>Kontakt</div>' },
},
],
})
}
describe('TermsOfServicePage', () => {
it('renders title and lead', () => {
const router = createTestRouter()
const wrapper = mount(TermsOfServicePage, {
global: { plugins: [router] },
})
expect(wrapper.text()).toContain('Användarvillkor')
expect(wrapper.text()).toContain('49 kr')
})
it('describes payment and order rules', () => {
const router = createTestRouter()
const wrapper = mount(TermsOfServicePage, {
global: { plugins: [router] },
})
expect(wrapper.text()).toContain('Swish')
expect(wrapper.text()).toContain('Obetalda beställningar kan redigeras')
})
it('links to privacy policy and support email', () => {
const router = createTestRouter()
const wrapper = mount(TermsOfServicePage, {
global: { plugins: [router] },
})
expect(wrapper.find('a[href="/integritetspolicy"]').exists()).toBe(true)
expect(wrapper.find('a[href="mailto:support@bilhej.se"]').exists()).toBe(
true,
)
})
})

View file

@ -29,10 +29,6 @@ const highlights = [
Bilhej gör det enkelt att en bilägare med ett fysiskt brev. Du
skriver meddelandet, vi sköter utskick och post.
</p>
</section>
<section class="about__section">
<h2 class="about__section-title">Vad vi gör</h2>
<div class="about__prose">
<p>
Många situationer i trafiken eller parkeringen är enklare att lösa
@ -126,12 +122,17 @@ const highlights = [
}
.about__lead {
margin: 0;
margin: 0 0 var(--space-lg) 0;
font-size: 1.0625rem;
line-height: 1.75;
color: var(--color-muted);
}
.about__prose {
padding-top: var(--space-lg);
border-top: 1px solid var(--color-border);
}
.about__section {
margin-bottom: var(--space-2xl);
}

View file

@ -1,19 +1,27 @@
<script setup lang="ts">
const contactChannels = [
{
variant: 'general',
title: 'Frågor om tjänsten',
variant: 'support',
title: 'Support',
description:
'Beställningar, betalning, tekniska problem eller allmänna frågor om hur Bilhej fungerar.',
'Beställningar, betalning via Swish, tekniska problem eller frågor om status och spårning.',
email: 'support@bilhej.se',
label: 'Skicka till support',
},
{
variant: 'general',
title: 'Allmän kontakt',
description:
'Övriga frågor om tjänsten, synpunkter som inte är klagomål, eller om du är osäker på vilken adress du ska använda.',
email: 'kontakt@bilhej.se',
label: 'Skicka e-post',
},
{
variant: 'complaints',
title: 'Klagomål och synpunkter',
title: 'Klagomål',
description:
'Om något gått fel eller du vill lämna ett klagomål direkt till oss som driver tjänsten.',
email: 'jcamorling@gmail.com',
email: 'klagomal@bilhej.se',
label: 'Skicka klagomål',
},
]
@ -25,8 +33,8 @@ const contactChannels = [
<p class="contact__eyebrow">Kontakt</p>
<h1 class="contact__title">Kontakta oss</h1>
<p class="contact__lead">
Vi svarar snart vi kan. Välj rätt adress beroende om det gäller en
vanlig fråga eller ett klagomål.
Vi svarar snart vi kan. Använd support för beställningar och tekniska
frågor, kontakt för övrigt, eller klagomål-adressen om något gått fel.
</p>
</section>
@ -40,14 +48,12 @@ const contactChannels = [
>
<h2>{{ channel.title }}</h2>
<p>{{ channel.description }}</p>
<a class="contact__email" :href="`mailto:${channel.email}`">
{{ channel.email }}
</a>
<a
class="btn btn--ghost contact__btn"
class="contact__mailto"
:href="`mailto:${channel.email}`"
:aria-label="`${channel.label}: ${channel.email}`"
>
{{ channel.label }}
{{ channel.email }}
</a>
</article>
</div>
@ -155,6 +161,10 @@ const contactChannels = [
background: var(--contact-accent);
}
.contact__card--support {
--contact-accent: linear-gradient(90deg, #0f766e, #2dd4bf);
}
.contact__card--general {
--contact-accent: linear-gradient(90deg, #1d4ed8, #60a5fa);
}
@ -176,22 +186,24 @@ const contactChannels = [
color: var(--color-muted);
}
.contact__email {
display: inline-block;
margin-bottom: var(--space-md);
.contact__mailto {
display: inline-flex;
align-items: center;
padding: 0.65rem 1rem;
font-family: var(--font-sans);
font-size: 1rem;
font-weight: 600;
color: var(--color-primary);
word-break: break-all;
color: var(--color-primary-dark);
background: var(--color-primary-soft);
border: 1px solid #bfdbfe;
border-radius: var(--radius-md);
transition: background var(--transition-fast), border-color var(--transition-fast);
}
.contact__email:hover {
text-decoration: underline;
text-underline-offset: 2px;
}
.contact__btn {
display: inline-flex;
.contact__mailto:hover {
background: #dbeafe;
border-color: #93c5fd;
color: var(--color-primary-dark);
}
.contact__tips {

View file

@ -0,0 +1,239 @@
<script setup lang="ts">
import { RouterLink } from 'vue-router'
const sections = [
{
id: 'ansvarig',
title: 'Personuppgiftsansvarig',
paragraphs: [
'Bilhej (bilhej.se) är personuppgiftsansvarig för den behandling som sker när du använder tjänsten som avsändare.',
'Frågor om integritet: kontakt@bilhej.se. Klagomål som rör tjänsten: klagomal@bilhej.se.',
],
},
{
id: 'vilka-uppgifter',
title: 'Vilka uppgifter behandlar vi?',
paragraphs: [
'Kontouppgifter: e-postadress och lösenord. Lösenordet lagras så att varken vi eller obehöriga kan läsa det.',
'Beställningar: registreringsnummer, brevtext, status, betalningsbelopp och eventuellt spårningsnummer kopplat till utskick.',
'Fordonsuppgifter: märke och modell för att du ska kunna verifiera nummerplåten. Vi lagrar inte mottagarens namn eller adress i tjänsten.',
'Mottagarens postadress används enbart för att posta brevet och sparas inte efter utskick.',
],
},
{
id: 'varfor',
title: 'Varför behandlar vi uppgifterna?',
paragraphs: [
'För att tillhandahålla tjänsten: konto, beställning, betalning och utskick av brev.',
'För att uppfylla rättsliga skyldigheter, till exempel bokföring av betalningar där så krävs.',
'För att skicka nödvändiga meddelanden till dig, till exempel återställning av lösenord.',
'Mottagaren informeras i brevets fot om att brevet skickats via Bilhej och hur hen kan kontakta oss.',
],
},
{
id: 'delning',
title: 'Vem delar vi uppgifter med?',
paragraphs: [
'Leverantörer som hjälper oss att skicka e-post, ta emot supportmail och driva webbplatsen.',
'Behörig myndighet och postoperatör i den utsträckning som krävs för att posta brevet till rätt mottagare.',
'Offentliga fordonsregister när du verifierar registreringsnummer (endast fordonsdata, inte ägaruppgifter).',
'Vi säljer inte personuppgifter och visar inte mottagarens identitet eller adress för dig som avsändare.',
],
},
{
id: 'lagring',
title: 'Hur länge sparar vi uppgifterna?',
paragraphs: [
'Kontouppgifter sparas tills du ber oss radera kontot.',
'Beställningshistorik (nummerplåt, brevtext, status) sparas så att du kan se Mina beställningar och så att vi kan hantera support.',
'Mottagarens adress sparas inte efter utskick.',
'Lösenordsåterställning gäller under begränsad tid och upphör efter användning.',
],
},
{
id: 'rattigheter',
title: 'Dina rättigheter',
paragraphs: [
'Du kan begära tillgång till, rättelse eller radering av dina uppgifter, begränsa behandling eller invända mot viss behandling.',
'Du kan begära radering av ditt konto genom att kontakta oss.',
'Du har rätt att lämna klagomål till Integritetsskyddsmyndigheten (IMY), imy.se.',
'Kontakta kontakt@bilhej.se för att utöva dina rättigheter. Vi svarar inom en månad om inte något annat anges.',
],
},
{
id: 'mottagare',
title: 'Om du fått ett brev via Bilhej',
paragraphs: [
'Din adress användes en gång för att posta brevet och ska ha raderats hos oss efter utskick.',
'För frågor eller invändning mot att få brev via tjänsten: kontakt@bilhej.se. Ange registreringsnummer om du kan.',
],
},
{
id: 'andringar',
title: 'Ändringar',
paragraphs: [
'Vi kan uppdatera denna policy när tjänsten utvecklas. Datum för senaste version anges nedan.',
],
},
]
</script>
<template>
<div class="policy">
<section class="policy__hero">
<p class="policy__eyebrow">Integritet</p>
<h1 class="policy__title">Integritetspolicy</h1>
<p class="policy__lead">
Här beskriver vi hur Bilhej behandlar personuppgifter när du skickar brev
via tjänsten, och vilka rättigheter du har.
</p>
<p class="policy__updated">Senast uppdaterad: 22 maj 2026</p>
</section>
<section
v-for="section in sections"
:key="section.id"
:id="section.id"
class="policy__section"
>
<h2 class="policy__section-title">{{ section.title }}</h2>
<div class="policy__prose">
<p v-for="(paragraph, index) in section.paragraphs" :key="index">
{{ paragraph }}
</p>
</div>
</section>
<section class="policy__section policy__section--cta">
<div class="policy__cta-box">
<h2 class="policy__cta-title">Frågor om integritet?</h2>
<p class="policy__cta-text">
Hör av dig via
<a class="policy__mailto" href="mailto:kontakt@bilhej.se"
>kontakt@bilhej.se</a
>
eller vår
<RouterLink to="/kontakt" class="policy__link">kontaktsida</RouterLink>.
</p>
</div>
</section>
</div>
</template>
<style scoped>
.policy {
max-width: 48rem;
margin: 0 auto;
padding: var(--space-3xl) var(--space-lg) var(--space-3xl);
}
.policy__hero {
margin-bottom: var(--space-2xl);
padding: var(--space-2xl);
background: linear-gradient(
145deg,
var(--color-surface) 0%,
#f8faff 55%,
var(--color-paper) 100%
);
border: 1px solid var(--color-border);
border-radius: var(--radius-xl);
box-shadow: var(--shadow-lg);
}
.policy__eyebrow {
display: inline-block;
margin: 0 0 var(--space-md) 0;
font-size: 0.8125rem;
font-weight: 600;
letter-spacing: 0.06em;
text-transform: uppercase;
color: var(--color-primary-dark);
background: var(--color-primary-soft);
border: 1px solid #bfdbfe;
padding: 0.35rem 0.75rem;
border-radius: var(--radius-full);
}
.policy__title {
margin: 0 0 var(--space-md) 0;
font-size: clamp(1.75rem, 4vw, 2.25rem);
font-weight: 800;
letter-spacing: -0.02em;
color: var(--color-ink);
}
.policy__lead {
margin: 0 0 var(--space-md) 0;
font-size: 1.0625rem;
line-height: 1.75;
color: var(--color-muted);
}
.policy__updated {
margin: 0;
font-size: 0.8125rem;
color: var(--color-soft);
}
.policy__section {
margin-bottom: var(--space-2xl);
scroll-margin-top: var(--space-lg);
}
.policy__section-title {
margin: 0 0 var(--space-md) 0;
font-size: 1.25rem;
font-weight: 700;
color: var(--color-ink);
}
.policy__prose p {
margin: 0 0 var(--space-md) 0;
font-size: 0.9375rem;
line-height: 1.75;
color: var(--color-muted);
}
.policy__prose p:last-child {
margin-bottom: 0;
}
.policy__cta-box {
padding: var(--space-xl);
text-align: center;
background: linear-gradient(
135deg,
var(--color-primary-soft) 0%,
#eef2ff 100%
);
border: 1px solid #bfdbfe;
border-radius: var(--radius-xl);
}
.policy__cta-title {
margin: 0 0 var(--space-sm) 0;
font-size: 1.25rem;
color: var(--color-primary-dark);
}
.policy__cta-text {
margin: 0;
font-size: 0.9375rem;
line-height: 1.65;
color: var(--color-primary-dark);
}
.policy__mailto,
.policy__link {
font-weight: 600;
color: var(--color-primary);
text-decoration: underline;
text-underline-offset: 2px;
}
.policy__mailto:hover,
.policy__link:hover {
color: var(--color-primary-dark);
}
</style>

View file

@ -0,0 +1,258 @@
<script setup lang="ts">
import { RouterLink } from 'vue-router'
const sections = [
{
id: 'tjansten',
title: 'Tjänsten',
paragraphs: [
'Bilhej (bilhej.se) är en tjänst där du som avsändare kan beställa utskick av ett fysiskt brev till en fordonsägare via registreringsnummer. Vi är mellanhand för att posta brevet. Vi är inte part i innehållet mellan dig och mottagaren.',
'Genom att skapa konto, beställa eller betala accepterar du dessa villkor.',
],
},
{
id: 'konto',
title: 'Konto',
paragraphs: [
'Du ansvarar för att uppgifterna i ditt konto stämmer och att ditt lösenord hålls hemligt.',
'Du måste vara minst 18 år, eller ha målsmans tillstånd, för att använda tjänsten.',
'Vi får stänga av eller avsluta konton som bryter mot villkoren eller missbrukar tjänsten.',
],
},
{
id: 'bestallning',
title: 'Beställning och betalning',
paragraphs: [
'Priset för ett brev framgår i tjänsten (för närvarande 49 kr). Betalning sker via Swish enligt instruktionerna i beställningsflödet.',
'Du ser hela brevet innan du betalar. Kontrollera registreringsnummer och text noga.',
'Obetalda beställningar kan redigeras eller avbrytas i Mina beställningar. Efter betalning behandlas beställningen för utskick.',
'Vi kan avvisa eller stoppa beställningar med olagligt, kränkande, hotfullt eller uppenbart missbrukande innehåll. Betald avgift återbetalas i så fall om utskick inte hunnit ske.',
],
},
{
id: 'innehåll',
title: 'Innehåll i brevet',
paragraphs: [
'Du ansvarar själv för texten du skickar. Brevet ska vara respektfullt och följa svensk lag.',
'Du får inte använda tjänsten för trakasserier, hot, olaglig reklam, spridning av personuppgifter om tredje man, eller annat otillbörligt innehåll.',
'Om du anger kontaktuppgifter i brevet kan mottagaren svara dig direkt. Annars är avsändaren anonym gentemot mottagaren.',
'Du garanterar att du har rätt att skicka innehållet och ger oss rätt att skriva ut och posta brevet en gång för att fullgöra beställningen.',
],
},
{
id: 'utskick',
title: 'Utskick och leverans',
paragraphs: [
'Efter betalning hanterar vi utskick av brevet. Det sker normalt inom några vardagar, men exakt tid kan variera.',
'Vi garanterar inte att mottagaren läser eller svarar på brevet.',
'När spårning finns tillgänglig visas den i Mina beställningar. Postens leveranstider ligger utanför vår kontroll.',
'Om registreringsnumret är felaktigt eller mottagaren inte kan nås kan utskick misslyckas. Kontakta support@bilhej.se så hjälper vi dig.',
],
},
{
id: 'reklamation',
title: 'Reklamation och återbetalning',
paragraphs: [
'Har något blivit fel? Kontakta support@bilhej.se så snart som möjligt och ange beställnings-ID.',
'Om brevet inte skickats kan vi i normalfallet återbetala eller korrigera beställningen.',
'Efter att brevet postats kan återbetalning normalt inte ske, eftersom tjänsten då är utförd.',
'Som konsument har du enligt lag rätt att reklamera fel i tjänsten inom tre år. Kontakta oss först så löser vi det i god tro.',
],
},
{
id: 'ansvar',
title: 'Ansvar',
paragraphs: [
'Tjänsten tillhandahålls i befintligt skick. Vi ansvarar inte för indirekta skador, utebliven vinst eller följder av innehållet i brev du skrivit.',
'Vårt ansvar gentemot dig som konsument begränsas inte i strid med tvingande lag.',
'Du håller Bilhej skadeslöst från krav från tredje part som beror på ditt innehåll eller ditt brott mot dessa villkor, i den utsträckning lagen tillåter.',
],
},
{
id: 'integritet',
title: 'Personuppgifter',
paragraphs: [],
},
{
id: 'andringar',
title: 'Ändringar av villkoren',
paragraphs: [
'Vi kan uppdatera villkoren när tjänsten ändras. Fortsatt användning efter att ändringar publicerats innebär att du accepterar de uppdaterade villkoren.',
],
},
{
id: 'tvist',
title: 'Tillämplig lag och kontakt',
paragraphs: [
'Svensk lag gäller för dessa villkor. Tvister ska i första hand lösas i samförstånd. Konsument kan vända sig till Allmänna reklamationsnämnden (arn.se).',
'Frågor om tjänsten: support@bilhej.se. Allmän kontakt: kontakt@bilhej.se. Klagomål: klagomal@bilhej.se.',
],
},
]
</script>
<template>
<div class="terms">
<section class="terms__hero">
<p class="terms__eyebrow">Villkor</p>
<h1 class="terms__title">Användarvillkor</h1>
<p class="terms__lead">
Villkor för att använda Bilhej när du beställer utskick av brev till
fordonsägare.
</p>
<p class="terms__updated">Senast uppdaterad: 22 maj 2026</p>
</section>
<section
v-for="section in sections"
:key="section.id"
:id="section.id"
class="terms__section"
>
<h2 class="terms__section-title">{{ section.title }}</h2>
<div class="terms__prose">
<p v-if="section.id === 'integritet'">
Vi behandlar personuppgifter enligt vår
<RouterLink to="/integritetspolicy" class="terms__link"
>integritetspolicy</RouterLink
>.
</p>
<p v-for="(paragraph, index) in section.paragraphs" :key="index">
{{ paragraph }}
</p>
</div>
</section>
<section class="terms__section terms__section--cta">
<div class="terms__cta-box">
<h2 class="terms__cta-title">Frågor om villkoren?</h2>
<p class="terms__cta-text">
Hör av dig via
<a class="terms__mailto" href="mailto:support@bilhej.se"
>support@bilhej.se</a
>
eller vår
<RouterLink to="/kontakt" class="terms__link">kontaktsida</RouterLink>.
</p>
</div>
</section>
</div>
</template>
<style scoped>
.terms {
max-width: 48rem;
margin: 0 auto;
padding: var(--space-3xl) var(--space-lg) var(--space-3xl);
}
.terms__hero {
margin-bottom: var(--space-2xl);
padding: var(--space-2xl);
background: linear-gradient(
145deg,
var(--color-surface) 0%,
#f8faff 55%,
var(--color-paper) 100%
);
border: 1px solid var(--color-border);
border-radius: var(--radius-xl);
box-shadow: var(--shadow-lg);
}
.terms__eyebrow {
display: inline-block;
margin: 0 0 var(--space-md) 0;
font-size: 0.8125rem;
font-weight: 600;
letter-spacing: 0.06em;
text-transform: uppercase;
color: var(--color-primary-dark);
background: var(--color-primary-soft);
border: 1px solid #bfdbfe;
padding: 0.35rem 0.75rem;
border-radius: var(--radius-full);
}
.terms__title {
margin: 0 0 var(--space-md) 0;
font-size: clamp(1.75rem, 4vw, 2.25rem);
font-weight: 800;
letter-spacing: -0.02em;
color: var(--color-ink);
}
.terms__lead {
margin: 0 0 var(--space-md) 0;
font-size: 1.0625rem;
line-height: 1.75;
color: var(--color-muted);
}
.terms__updated {
margin: 0;
font-size: 0.8125rem;
color: var(--color-soft);
}
.terms__section {
margin-bottom: var(--space-2xl);
scroll-margin-top: var(--space-lg);
}
.terms__section-title {
margin: 0 0 var(--space-md) 0;
font-size: 1.25rem;
font-weight: 700;
color: var(--color-ink);
}
.terms__prose p {
margin: 0 0 var(--space-md) 0;
font-size: 0.9375rem;
line-height: 1.75;
color: var(--color-muted);
}
.terms__prose p:last-child {
margin-bottom: 0;
}
.terms__cta-box {
padding: var(--space-xl);
text-align: center;
background: linear-gradient(
135deg,
var(--color-primary-soft) 0%,
#eef2ff 100%
);
border: 1px solid #bfdbfe;
border-radius: var(--radius-xl);
}
.terms__cta-title {
margin: 0 0 var(--space-sm) 0;
font-size: 1.25rem;
color: var(--color-primary-dark);
}
.terms__cta-text {
margin: 0;
font-size: 0.9375rem;
line-height: 1.65;
color: var(--color-primary-dark);
}
.terms__mailto,
.terms__link {
font-weight: 600;
color: var(--color-primary);
text-decoration: underline;
text-underline-offset: 2px;
}
.terms__mailto:hover,
.terms__link:hover {
color: var(--color-primary-dark);
}
</style>

View file

@ -3,6 +3,8 @@ import HomePage from '@/pages/HomePage.vue'
import ComposePage from '@/pages/ComposePage.vue'
import AboutPage from '@/pages/AboutPage.vue'
import ContactPage from '@/pages/ContactPage.vue'
import PrivacyPolicyPage from '@/pages/PrivacyPolicyPage.vue'
import TermsOfServicePage from '@/pages/TermsOfServicePage.vue'
import RegisterPage from '@/pages/RegisterPage.vue'
import LoginPage from '@/pages/LoginPage.vue'
import ForgotPasswordPage from '@/pages/ForgotPasswordPage.vue'
@ -97,6 +99,16 @@ const router = createRouter({
name: 'contact',
component: ContactPage,
},
{
path: '/integritetspolicy',
name: 'privacy',
component: PrivacyPolicyPage,
},
{
path: '/villkor',
name: 'terms',
component: TermsOfServicePage,
},
],
})