From 255095e6bd12b3ad43aeeafa690d07888378c1f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20M=C3=B6rling?= Date: Fri, 22 May 2026 12:59:49 +0200 Subject: [PATCH 1/6] 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 --- REQUIREMENTS.md | 2 +- docs/production-email-checklist.md | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/REQUIREMENTS.md b/REQUIREMENTS.md index 22ff751..14da9f6 100644 --- a/REQUIREMENTS.md +++ b/REQUIREMENTS.md @@ -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 diff --git a/docs/production-email-checklist.md b/docs/production-email-checklist.md index 36dddb3..aed77fa 100644 --- a/docs/production-email-checklist.md +++ b/docs/production-email-checklist.md @@ -54,3 +54,24 @@ 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. Contact email (`kontakt@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. + +**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 a test mail to `kontakt@bilhej.se` and confirm it appears 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 | +|---------|---------|-----------------| +| `kontakt@bilhej.se` | General questions (site, orders, support) | Resend dashboard | +| `jcamorling@gmail.com` | Complaints (shown on `/kontakt` only) | Gmail directly | +| `noreply@bilhej.se` | Outbound only (password reset) | Not an inbox | From c0c32b718b2ff54d27964d35cfd157223d37e09d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20M=C3=B6rling?= Date: Fri, 22 May 2026 13:51:11 +0200 Subject: [PATCH 2/6] Merge about page prose into hero and drop redundant section heading. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- frontend/src/pages/AboutPage.vue | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/frontend/src/pages/AboutPage.vue b/frontend/src/pages/AboutPage.vue index 034999c..600d764 100644 --- a/frontend/src/pages/AboutPage.vue +++ b/frontend/src/pages/AboutPage.vue @@ -29,10 +29,6 @@ const highlights = [ Bilhej gör det enkelt att nå en bilägare med ett fysiskt brev. Du skriver meddelandet, vi sköter utskick och post.

- - -
-

Vad vi gör

Många situationer i trafiken eller på 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); } From bce24472383e5f6ab5faccd3eebfd7c0f29c2bcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20M=C3=B6rling?= Date: Fri, 22 May 2026 13:51:20 +0200 Subject: [PATCH 3/6] 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 --- docs/production-email-checklist.md | 16 ++++-- frontend/src/__tests__/ContactPage.spec.ts | 21 +++++--- frontend/src/pages/ContactPage.vue | 60 +++++++++++++--------- 3 files changed, 62 insertions(+), 35 deletions(-) diff --git a/docs/production-email-checklist.md b/docs/production-email-checklist.md index aed77fa..fa4580f 100644 --- a/docs/production-email-checklist.md +++ b/docs/production-email-checklist.md @@ -55,23 +55,29 @@ 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. Contact email (`kontakt@bilhej.se`) +## 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. +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 a test mail to `kontakt@bilhej.se` and confirm it appears under **Emails → Receiving** +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 | |---------|---------|-----------------| -| `kontakt@bilhej.se` | General questions (site, orders, support) | Resend dashboard | -| `jcamorling@gmail.com` | Complaints (shown on `/kontakt` only) | Gmail directly | +| `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. diff --git a/frontend/src/__tests__/ContactPage.spec.ts b/frontend/src/__tests__/ContactPage.spec.ts index 86f39f2..30e922b 100644 --- a/frontend/src/__tests__/ContactPage.spec.ts +++ b/frontend/src/__tests__/ContactPage.spec.ts @@ -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') }) }) diff --git a/frontend/src/pages/ContactPage.vue b/frontend/src/pages/ContactPage.vue index e3ba60e..462fed1 100644 --- a/frontend/src/pages/ContactPage.vue +++ b/frontend/src/pages/ContactPage.vue @@ -1,19 +1,27 @@ + + + + From ec62ba7673aa449d6364734752fad5eb8e44c85d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20M=C3=B6rling?= Date: Fri, 22 May 2026 13:51:20 +0200 Subject: [PATCH 5/6] =?UTF-8?q?Add=20anv=C3=A4ndarvillkor=20page=20for=20B?= =?UTF-8?q?ilhej=20service=20terms.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- .../src/__tests__/TermsOfServicePage.spec.ts | 58 ++++ frontend/src/pages/TermsOfServicePage.vue | 258 ++++++++++++++++++ 2 files changed, 316 insertions(+) create mode 100644 frontend/src/__tests__/TermsOfServicePage.spec.ts create mode 100644 frontend/src/pages/TermsOfServicePage.vue diff --git a/frontend/src/__tests__/TermsOfServicePage.spec.ts b/frontend/src/__tests__/TermsOfServicePage.spec.ts new file mode 100644 index 0000000..c21e0b2 --- /dev/null +++ b/frontend/src/__tests__/TermsOfServicePage.spec.ts @@ -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: '

Integritet
' }, + }, + { + path: '/kontakt', + name: 'contact', + component: { template: '
Kontakt
' }, + }, + ], + }) +} + +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, + ) + }) +}) diff --git a/frontend/src/pages/TermsOfServicePage.vue b/frontend/src/pages/TermsOfServicePage.vue new file mode 100644 index 0000000..505eeb6 --- /dev/null +++ b/frontend/src/pages/TermsOfServicePage.vue @@ -0,0 +1,258 @@ + + + + + From a12e07ec1c210c049bd5321f7ca4be7821c365cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20M=C3=B6rling?= Date: Fri, 22 May 2026 13:51:20 +0200 Subject: [PATCH 6/6] Register routes for integritetspolicy and villkor legal pages. - Add /integritetspolicy and /villkor to Vue Router - Add Router tests confirming both public legal routes resolve Co-authored-by: Cursor --- frontend/src/__tests__/Router.spec.ts | 12 ++++++++++++ frontend/src/router/index.ts | 12 ++++++++++++ 2 files changed, 24 insertions(+) diff --git a/frontend/src/__tests__/Router.spec.ts b/frontend/src/__tests__/Router.spec.ts index 257431a..3a5f1de 100644 --- a/frontend/src/__tests__/Router.spec.ts +++ b/frontend/src/__tests__/Router.spec.ts @@ -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() diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index 86fa1d2..3ec2a98 100644 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -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, + }, ], })