diff --git a/frontend/e2e/compose.spec.ts b/frontend/e2e/compose.spec.ts index d4a9e96..b789b84 100644 --- a/frontend/e2e/compose.spec.ts +++ b/frontend/e2e/compose.spec.ts @@ -84,4 +84,39 @@ test.describe('Compose flow', () => { ).toBeVisible() await expect(page.getByText('Transportstyrelsens fordonsregister')).toBeVisible() }) + + test('Visa mallar button opens template picker', async ({ page }) => { + await page.goto('/logga-in') + await page.getByLabel('E-postadress').fill('test@bilhalsning.se') + await page.getByLabel('Lösenord').fill('test1234') + await page.getByRole('button', { name: 'Logga in' }).click() + await page.waitForURL('/') + + await page.goto('/compose?plate=ABC123') + + await page.getByRole('button', { name: 'Visa mallar' }).click() + + await expect(page.getByRole('heading', { name: 'Välj en mall' })).toBeVisible() + await expect(page.getByText('Komplimang')).toBeVisible() + await expect(page.getByText('Köpförfrågan')).toBeVisible() + }) + + test('selecting template fills textarea and closes picker', async ({ + page, + }) => { + await page.goto('/logga-in') + await page.getByLabel('E-postadress').fill('test@bilhalsning.se') + await page.getByLabel('Lösenord').fill('test1234') + await page.getByRole('button', { name: 'Logga in' }).click() + await page.waitForURL('/') + + await page.goto('/compose?plate=ABC123') + + await page.getByRole('button', { name: 'Visa mallar' }).click() + await page.getByText('Komplimang').click() + + const textarea = page.getByLabel('Ditt meddelande') + await expect(textarea).toHaveValue(/jättefin/) + await expect(page.getByRole('heading', { name: 'Välj en mall' })).not.toBeVisible() + }) }) diff --git a/frontend/src/__tests__/ComposePage.spec.ts b/frontend/src/__tests__/ComposePage.spec.ts index bdd50bd..3c1fc76 100644 --- a/frontend/src/__tests__/ComposePage.spec.ts +++ b/frontend/src/__tests__/ComposePage.spec.ts @@ -168,4 +168,43 @@ describe('ComposePage', () => { const { wrapper } = await mountPage() expect(wrapper.text()).toContain('Detta brev skickades via BilHej.se') }) + + it('shows Visa mallar button', async () => { + const { wrapper } = await mountPage() + const btn = wrapper.find('.compose__templates-btn') + expect(btn.exists()).toBe(true) + expect(btn.text()).toContain('Visa mallar') + }) + + it('opens template picker when Visa mallar is clicked', async () => { + const { wrapper } = await mountPage() + const btn = wrapper.find('.compose__templates-btn') + await btn.trigger('click') + + expect(wrapper.text()).toContain('Välj en mall') + expect(wrapper.text()).toContain('Komplimang') + }) + + it('fills textarea when template is selected', async () => { + const { wrapper } = await mountPage() + const btn = wrapper.find('.compose__templates-btn') + await btn.trigger('click') + + const cards = wrapper.findAll('.modal__card') + await cards[0].trigger('click') + + const textarea = wrapper.find('textarea') + expect(textarea.element.value).toContain('jättefin') + }) + + it('closes picker after template is selected', async () => { + const { wrapper } = await mountPage() + const btn = wrapper.find('.compose__templates-btn') + await btn.trigger('click') + + const cards = wrapper.findAll('.modal__card') + await cards[0].trigger('click') + + expect(wrapper.find('.modal-overlay').exists()).toBe(false) + }) }) diff --git a/frontend/src/__tests__/TemplatePicker.spec.ts b/frontend/src/__tests__/TemplatePicker.spec.ts new file mode 100644 index 0000000..d0a66b6 --- /dev/null +++ b/frontend/src/__tests__/TemplatePicker.spec.ts @@ -0,0 +1,59 @@ +import { describe, it, expect } from 'vitest' +import { mount } from '@vue/test-utils' +import TemplatePicker from '@/components/TemplatePicker.vue' + +describe('TemplatePicker', () => { + it('renders all template cards', () => { + const wrapper = mount(TemplatePicker) + const cards = wrapper.findAll('.modal__card') + expect(cards).toHaveLength(7) + }) + + it('shows template names', () => { + const wrapper = mount(TemplatePicker) + expect(wrapper.text()).toContain('Komplimang') + expect(wrapper.text()).toContain('Köpförfrågan') + expect(wrapper.text()).toContain('Fritt meddelande') + }) + + it('emits select event with template data when card is clicked', async () => { + const wrapper = mount(TemplatePicker) + const cards = wrapper.findAll('.modal__card') + await cards[0].trigger('click') + + expect(wrapper.emitted('select')).toHaveLength(1) + expect(wrapper.emitted('select')![0][0]).toMatchObject({ + name: 'Komplimang', + icon: '🌟', + }) + }) + + it('emits close event when card is clicked', async () => { + const wrapper = mount(TemplatePicker) + const cards = wrapper.findAll('.modal__card') + await cards[0].trigger('click') + + expect(wrapper.emitted('close')).toHaveLength(1) + }) + + it('emits close event when close button is clicked', async () => { + const wrapper = mount(TemplatePicker) + const closeBtn = wrapper.find('.modal__close') + await closeBtn.trigger('click') + + expect(wrapper.emitted('close')).toHaveLength(1) + }) + + it('emits close event when overlay is clicked', async () => { + const wrapper = mount(TemplatePicker) + const overlay = wrapper.find('.modal-overlay') + await overlay.trigger('click') + + expect(wrapper.emitted('close')).toHaveLength(1) + }) + + it('includes new parking damage template', () => { + const wrapper = mount(TemplatePicker) + expect(wrapper.text()).toContain('Mindre parkeringsskada') + }) +}) diff --git a/frontend/src/components/TemplatePicker.vue b/frontend/src/components/TemplatePicker.vue new file mode 100644 index 0000000..5dabaf8 --- /dev/null +++ b/frontend/src/components/TemplatePicker.vue @@ -0,0 +1,151 @@ + + + + + + + diff --git a/frontend/src/data/templates.ts b/frontend/src/data/templates.ts index 7c4771d..725b330 100644 --- a/frontend/src/data/templates.ts +++ b/frontend/src/data/templates.ts @@ -1,11 +1,13 @@ export interface LetterTemplate { name: string + icon: string body: string } export const templates: LetterTemplate[] = [ { name: 'Komplimang', + icon: '🌟', body: `Hej! Jag ville bara säga att din bil är jättefin! Det syns att den är väl omhändertagen och jag uppskattar verkligen att du tar hand om den så bra. @@ -13,7 +15,8 @@ Jag ville bara säga att din bil är jättefin! Det syns att den är väl omhän Ha en trevlig dag!`, }, { - name: 'Jag vill köpa din bil', + name: 'Köpförfrågan', + icon: '🚗', body: `Hej! Jag är intresserad av att köpa din bil. Om du någon gång funderar på att sälja den, så får du gärna höra av dig. @@ -25,6 +28,7 @@ Vänliga hälsningar, }, { name: 'Tips / servicebehov', + icon: '🔧', body: `Hej! Jag ville tipsa dig om att jag märkte att din bil behöver lite uppmärksamhet. Det kan vara bra att kolla upp det så snart som möjligt. @@ -32,7 +36,8 @@ Jag ville tipsa dig om att jag märkte att din bil behöver lite uppmärksamhet. Hoppas detta var till hjälp!`, }, { - name: 'Synpunkter på körbeteende', + name: 'Körbeteende', + icon: '🛣️', body: `Hej! Jag ville uppmärksamma dig på en situation i trafiken där jag reagerade på ditt körbeteende. Jag menar inget illa utan vill bara ge en vänlig påminnelse om att vara extra uppmärksam. @@ -41,14 +46,28 @@ Tack för att du lyssnar!`, }, { name: 'Tuta / frustration', + icon: '📢', body: `Hej! Jag ville nämna en situation i trafiken där vi båda kanske blev lite frustrerade. Det är lätt att det blir så ibland, men jag ville nå ut för att lösa det på ett trevligt sätt. Ha det bra!`, + }, + { + name: 'Mindre parkeringsskada', + icon: '🅿️', + body: `Hej! + +Jag råkade skada din bil lite när jag parkerade. Det var inte meningen och jag ber om ursäkt. Jag vill gärna att vi löser det här tillsammans. + +Du kan nå mig på: [din e-postadress eller telefonnummer] + +Vänliga hälsningar, +[Ditt namn]`, }, { name: 'Fritt meddelande', + icon: '✏️', body: '', }, ] diff --git a/frontend/src/pages/ComposePage.vue b/frontend/src/pages/ComposePage.vue index 44e7b24..a133c27 100644 --- a/frontend/src/pages/ComposePage.vue +++ b/frontend/src/pages/ComposePage.vue @@ -2,6 +2,8 @@ import { ref, computed } from 'vue' import { useRouter, useRoute } from 'vue-router' import { createOrder } from '@/api/orders' +import { type LetterTemplate } from '@/data/templates' +import TemplatePicker from '@/components/TemplatePicker.vue' const router = useRouter() const route = useRoute() @@ -10,6 +12,7 @@ const plate = computed(() => (route.query.plate as string) || '') const letterText = ref('') const submitting = ref(false) const errorMessage = ref('') +const showPicker = ref(false) const charCount = computed(() => letterText.value.length) const maxChars = 1000 @@ -20,6 +23,10 @@ const canSubmit = computed( const GDPR_FOOTER = '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' +function handleTemplateSelect(template: LetterTemplate) { + letterText.value = template.body +} + async function handleSubmit() { if (!canSubmit.value) return @@ -50,7 +57,16 @@ async function handleSubmit() {