refactor: remove template from order flow

Templates serve as a brand shield (showing the platform facilitates all
kinds of messaging), not as a compose-flow form control. Remove them from
the data model and compose page. Templates will live as branding elements
on the landing page in a future commit.

Backend:
- Remove template field from Order entity (getter/setter removed)
- Remove template from CreateOrderRequest DTO
- Remove template from OrderResponse DTO
- Remove template param from OrderService.createOrder()
- Remove template passthrough in OrderController
- Remove /api/templates permitAll from SecurityConfig
- Edit V5 migration: remove template column from orders table
- Edit V6 migration: remove template from seed data
- Update OrderControllerTest (remove template from assertions/requests)
- Update OrderServiceTest (remove template from createOrder calls)

Frontend:
- Remove template from Order interface in api/orders.ts
- Remove template param from createOrder() function
- Remove template display from OrdersPage.vue cards
- Rewrite ComposePage.vue: remove template selector, keep textarea + preview + submit
- Update ComposePage.spec.ts (remove template tests, add preview/GDPR tests)
- Update OrdersPage.spec.ts (remove template from mock data and display test)
- Update compose.spec.ts E2E (remove template selector interactions)
- Update order-history.spec.ts E2E (remove template names test)
- Fix unused import in Router.spec.ts
- Also includes minor Prettier formatting in AppHeader.spec.ts, AdminPage.vue, authStore.ts
This commit is contained in:
Joakim Mörling 2026-05-14 16:55:59 +02:00
parent 5fa903d9af
commit 6ab5e2f707
21 changed files with 51 additions and 222 deletions

View file

@ -37,7 +37,6 @@ public class SecurityConfig {
.requestMatchers("/api/auth/register", "/api/auth/login").permitAll()
.requestMatchers("/api/webhooks/**").permitAll()
.requestMatchers("/api/vehicles/**").permitAll()
.requestMatchers("/api/templates").permitAll()
.anyRequest().authenticated())
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);

View file

@ -51,7 +51,6 @@ public class OrderController {
Order order = orderService.createOrder(
user.getId(),
request.plate(),
request.template(),
request.letterText()
);
@ -62,7 +61,6 @@ public class OrderController {
return new OrderResponse(
order.getId(),
order.getPlate(),
order.getTemplate(),
order.getStatus().getValue(),
order.getTrackingId(),
order.getCreatedAt()

View file

@ -9,9 +9,6 @@ public record CreateOrderRequest(
@Pattern(regexp = "^[A-Za-z]{3}\\d{2}[A-Za-z0-9]$", message = "Ogiltigt registreringsnummer")
String plate,
@Size(max = 50, message = "Mallnamn får vara max 50 tecken")
String template,
@NotBlank(message = "Brevtext krävs")
@Size(min = 1, max = 1000, message = "Brevtexten måste vara mellan 1 och 1000 tecken")
String letterText

View file

@ -6,7 +6,6 @@ import java.util.UUID;
public record OrderResponse(
UUID id,
String plate,
String template,
String status,
String trackingId,
Instant createdAt

View file

@ -24,9 +24,6 @@ public class Order {
@Column(name = "plate", nullable = false, length = 10)
private String plate;
@Column(name = "template", length = 50)
private String template;
@Column(name = "letter_text", nullable = false, columnDefinition = "text")
private String letterText;
@ -86,14 +83,6 @@ public class Order {
this.plate = plate;
}
public String getTemplate() {
return template;
}
public void setTemplate(String template) {
this.template = template;
}
public String getLetterText() {
return letterText;
}

View file

@ -16,11 +16,10 @@ public class OrderService {
private final OrderRepository orderRepository;
public Order createOrder(UUID userId, String plate, String template, String letterText) {
public Order createOrder(UUID userId, String plate, String letterText) {
Order order = new Order();
order.setUserId(userId);
order.setPlate(plate.toUpperCase().trim());
order.setTemplate(template);
order.setLetterText(letterText);
order.setStatus(OrderStatus.PENDING_PAYMENT);
return orderRepository.save(order);

View file

@ -2,7 +2,6 @@ CREATE TABLE orders (
id UUID NOT NULL,
user_id UUID NOT NULL,
plate VARCHAR(10) NOT NULL,
template VARCHAR(50),
letter_text TEXT NOT NULL,
status VARCHAR(30) NOT NULL DEFAULT 'pending_payment',
amount_paid DECIMAL(10,2),

View file

@ -1,6 +1,6 @@
-- Seed orders for test user (test@bilhalsning.se, id: a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11)
INSERT INTO orders (id, user_id, plate, template, letter_text, status, amount_paid, tracking_id, created_at, updated_at)
INSERT INTO orders (id, user_id, plate, letter_text, status, amount_paid, tracking_id, created_at, updated_at)
VALUES
('c1eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'ABC123', 'Komplimang', 'Hej! Jag ville bara säga att du har en väldigt fin bil. Hälsningar från en bilentusiast!', 'sent', 49.00, 'PN123456789', TIMESTAMP '2026-05-11 12:00:00', TIMESTAMP '2026-05-13 12:00:00'),
('c2eebc99-9c0b-4ef8-bb6d-6bb9bd380a12', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'DEF456', 'Jag vill köpa din bil', 'Hej! Jag är intresserad av att köpa din bil. Kontakta mig gärna på test@example.com så kan vi diskutera ett pris.', 'pending_payment', NULL, NULL, TIMESTAMP '2026-05-14 13:00:00', TIMESTAMP '2026-05-14 13:00:00'),
('c3eebc99-9c0b-4ef8-bb6d-6bb9bd380a13', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'GHI789', 'Tips / servicebehov', 'Hej! Jag noterade att ditt bakre högra hjul har lite för lågt lufttryck. Tänkte det kan vara bra att veta!', 'delivered', 49.00, 'PN987654321', TIMESTAMP '2026-05-07 10:00:00', TIMESTAMP '2026-05-12 10:00:00');
('c1eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'ABC123', 'Hej! Jag ville bara säga att du har en väldigt fin bil. Hälsningar från en bilentusiast!', 'sent', 49.00, 'PN123456789', TIMESTAMP '2026-05-11 12:00:00', TIMESTAMP '2026-05-13 12:00:00'),
('c2eebc99-9c0b-4ef8-bb6d-6bb9bd380a12', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'DEF456', 'Hej! Jag är intresserad av att köpa din bil. Kontakta mig gärna på test@example.com så kan vi diskutera ett pris.', 'pending_payment', NULL, NULL, TIMESTAMP '2026-05-14 13:00:00', TIMESTAMP '2026-05-14 13:00:00'),
('c3eebc99-9c0b-4ef8-bb6d-6bb9bd380a13', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'GHI789', 'Hej! Jag noterade att ditt bakre högra hjul har lite för lågt lufttryck. Tänkte det kan vara bra att veta!', 'delivered', 49.00, 'PN987654321', TIMESTAMP '2026-05-07 10:00:00', TIMESTAMP '2026-05-12 10:00:00');

View file

@ -51,11 +51,6 @@ class OrderControllerTest {
when(userService.findByEmail("test@bilhalsning.se")).thenReturn(Optional.of(user));
OrderResponse order1 = new OrderResponse(
UUID.randomUUID(), "ABC123", "Komplimang", "sent", "PN123456789", Instant.now());
OrderResponse order2 = new OrderResponse(
UUID.randomUUID(), "DEF456", null, "pending_payment", null, Instant.now());
when(orderService.getOrdersByUserId(userId)).thenReturn(List.of());
mockMvc.perform(get("/api/orders"))
@ -78,7 +73,6 @@ class OrderControllerTest {
order.setId(UUID.fromString("c1eebc99-9c0b-4ef8-bb6d-6bb9bd380a11"));
order.setUserId(userId);
order.setPlate("ABC123");
order.setTemplate("Komplimang");
order.setLetterText("Test letter");
order.setStatus(se.bilhalsning.entity.OrderStatus.SENT);
order.setTrackingId("PN123456789");
@ -89,7 +83,6 @@ class OrderControllerTest {
.andExpect(status().isOk())
.andExpect(jsonPath("$[0].id").value("c1eebc99-9c0b-4ef8-bb6d-6bb9bd380a11"))
.andExpect(jsonPath("$[0].plate").value("ABC123"))
.andExpect(jsonPath("$[0].template").value("Komplimang"))
.andExpect(jsonPath("$[0].status").value("sent"))
.andExpect(jsonPath("$[0].trackingId").value("PN123456789"));
}
@ -125,20 +118,18 @@ class OrderControllerTest {
savedOrder.setId(UUID.fromString("d1eebc99-9c0b-4ef8-bb6d-6bb9bd380a11"));
savedOrder.setUserId(userId);
savedOrder.setPlate("ABC123");
savedOrder.setTemplate("Komplimang");
savedOrder.setLetterText("Hej fin bil!");
savedOrder.setStatus(se.bilhalsning.entity.OrderStatus.PENDING_PAYMENT);
when(orderService.createOrder(userId, "ABC123", "Komplimang", "Hej fin bil!"))
when(orderService.createOrder(userId, "ABC123", "Hej fin bil!"))
.thenReturn(savedOrder);
mockMvc.perform(post("/api/orders")
.contentType("application/json")
.content("{\"plate\":\"ABC123\",\"template\":\"Komplimang\",\"letterText\":\"Hej fin bil!\"}"))
.content("{\"plate\":\"ABC123\",\"letterText\":\"Hej fin bil!\"}"))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.id").value("d1eebc99-9c0b-4ef8-bb6d-6bb9bd380a11"))
.andExpect(jsonPath("$.plate").value("ABC123"))
.andExpect(jsonPath("$.template").value("Komplimang"))
.andExpect(jsonPath("$.status").value("pending_payment"));
}

View file

@ -37,7 +37,7 @@ class OrderServiceTest {
UUID userId = UUID.randomUUID();
when(orderRepository.save(any(Order.class))).thenAnswer(inv -> inv.getArgument(0));
Order result = orderService.createOrder(userId, "ABC123", "Komplimang", "Hej fin bil!");
Order result = orderService.createOrder(userId, "ABC123", "Hej fin bil!");
assertEquals(OrderStatus.PENDING_PAYMENT, result.getStatus());
verify(orderRepository).save(orderCaptor.capture());
@ -55,7 +55,7 @@ class OrderServiceTest {
return order;
});
Order result = orderService.createOrder(userId, "ABC123", null, "Test text");
Order result = orderService.createOrder(userId, "ABC123", "Test text");
assertNotNull(result.getId());
}
@ -65,7 +65,7 @@ class OrderServiceTest {
UUID userId = UUID.randomUUID();
when(orderRepository.save(any(Order.class))).thenAnswer(inv -> inv.getArgument(0));
Order result = orderService.createOrder(userId, "abc123", null, "Test text");
Order result = orderService.createOrder(userId, "abc123", "Test text");
assertEquals("ABC123", result.getPlate());
}
@ -75,7 +75,7 @@ class OrderServiceTest {
UUID userId = UUID.randomUUID();
when(orderRepository.save(any(Order.class))).thenAnswer(inv -> inv.getArgument(0));
Order result = orderService.createOrder(userId, " ABC123 ", null, "Test text");
Order result = orderService.createOrder(userId, " ABC123 ", "Test text");
assertEquals("ABC123", result.getPlate());
}
@ -85,11 +85,10 @@ class OrderServiceTest {
UUID userId = UUID.randomUUID();
when(orderRepository.save(any(Order.class))).thenAnswer(inv -> inv.getArgument(0));
Order result = orderService.createOrder(userId, "ABC123", "Komplimang", "Hej fin bil!");
Order result = orderService.createOrder(userId, "ABC123", "Hej fin bil!");
assertEquals(userId, result.getUserId());
assertEquals("ABC123", result.getPlate());
assertEquals("Komplimang", result.getTemplate());
assertEquals("Hej fin bil!", result.getLetterText());
assertNull(result.getAmountPaid());
assertNull(result.getTrackingId());

View file

@ -19,7 +19,7 @@ test.describe('Compose flow', () => {
await expect(page.getByText('Inget registreringsnummer valt')).toBeVisible()
})
test('displays plate and template selector', async ({ page }) => {
test('displays plate and textarea', async ({ page }) => {
await page.goto('/logga-in')
await page.getByLabel('E-postadress').fill('test@bilhalsning.se')
await page.getByLabel('Lösenord').fill('test1234')
@ -32,37 +32,7 @@ test.describe('Compose flow', () => {
page.getByRole('heading', { name: 'Skriv ditt brev' }),
).toBeVisible()
await expect(page.getByText('ABC123')).toBeVisible()
await expect(page.getByLabel('Välj mall')).toBeVisible()
})
test('selecting template fills textarea', 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.getByLabel('Välj mall').selectOption('Komplimang')
const textarea = page.getByLabel('Ditt meddelande')
await expect(textarea).toHaveValue(/jättefin/)
})
test('can edit textarea after selecting template', 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.getByLabel('Välj mall').selectOption('Komplimang')
const textarea = page.getByLabel('Ditt meddelande')
await textarea.fill('Custom text')
await expect(textarea).toHaveValue('Custom text')
await expect(page.getByLabel('Ditt meddelande')).toBeVisible()
})
test('submit button disabled when textarea is empty', async ({ page }) => {
@ -87,7 +57,7 @@ test.describe('Compose flow', () => {
await page.goto('/compose?plate=ABC123')
await page.getByLabel('Välj mall').selectOption('Komplimang')
await page.getByLabel('Ditt meddelande').fill('Hej fin bil!')
const button = page.getByRole('button', { name: 'Skicka brev (49 kr)' })
await expect(button).toBeEnabled()
await button.click()
@ -107,7 +77,7 @@ test.describe('Compose flow', () => {
await page.goto('/compose?plate=ABC123')
await page.getByLabel('Välj mall').selectOption('Komplimang')
await page.getByLabel('Ditt meddelande').fill('Testmeddelande')
await expect(
page.getByText('Detta brev skickades via BilHej.se'),

View file

@ -71,18 +71,4 @@ test.describe('Order history', () => {
const trackingLink2 = page.getByRole('link', { name: 'PN987654321' })
await expect(trackingLink2).toBeVisible()
})
test('shows template names', 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('/orders')
await expect(page.getByText('Komplimang')).toBeVisible()
await expect(page.getByText('Jag vill köpa din bil')).toBeVisible()
await expect(page.getByText('Tips / servicebehov')).toBeVisible()
})
})

View file

@ -67,9 +67,7 @@ describe('AppHeader', () => {
global: { plugins: [router, createPinia()] },
})
const links = wrapper.findAll('a')
const loginLink = links.find(
(a) => a.attributes('href') === '/logga-in',
)
const loginLink = links.find((a) => a.attributes('href') === '/logga-in')
expect(loginLink).toBeTruthy()
expect(loginLink?.text()).toBe('Logga in')
})
@ -109,9 +107,7 @@ describe('AppHeader', () => {
global: { plugins: [router, createPinia()] },
})
const links = wrapper.findAll('a')
const ordersLink = links.find(
(a) => a.attributes('href') === '/orders',
)
const ordersLink = links.find((a) => a.attributes('href') === '/orders')
expect(ordersLink).toBeUndefined()
})
})
@ -143,9 +139,7 @@ describe('AppHeader', () => {
it('does not show login link', () => {
const wrapper = mountAuthenticated()
const links = wrapper.findAll('a')
const loginLink = links.find(
(a) => a.attributes('href') === '/logga-in',
)
const loginLink = links.find((a) => a.attributes('href') === '/logga-in')
expect(loginLink).toBeUndefined()
})
@ -161,9 +155,7 @@ describe('AppHeader', () => {
it('shows orders link', () => {
const wrapper = mountAuthenticated()
const links = wrapper.findAll('a')
const ordersLink = links.find(
(a) => a.attributes('href') === '/orders',
)
const ordersLink = links.find((a) => a.attributes('href') === '/orders')
expect(ordersLink).toBeTruthy()
expect(ordersLink?.text()).toBe('Mina beställningar')
})

View file

@ -67,27 +67,10 @@ describe('ComposePage', () => {
expect(wrapper.text()).toContain('Inget registreringsnummer valt')
})
it('has all 6 template options', async () => {
it('shows textarea for letter input', async () => {
const { wrapper } = await mountPage()
const select = wrapper.find('select')
const options = select.findAll('option')
expect(options).toHaveLength(6)
expect(options[0].text()).toBe('Komplimang')
expect(options[5].text()).toBe('Fritt meddelande')
})
it('selects Fritt meddelande by default', async () => {
const { wrapper } = await mountPage()
const select = wrapper.find('select')
expect((select.element as HTMLSelectElement).value).toBe('Fritt meddelande')
})
it('fills textarea when template is selected', async () => {
const { wrapper } = await mountPage()
const select = wrapper.find('select')
await select.setValue('Komplimang')
const textarea = wrapper.find('textarea')
expect(textarea.element.value).toContain('jättefin')
expect(textarea.exists()).toBe(true)
})
it('updates character counter on input', async () => {
@ -123,24 +106,19 @@ describe('ComposePage', () => {
mockCreateOrder.mockResolvedValue({
id: 'order-1',
plate: 'ABC123',
template: 'Komplimang',
status: 'pending_payment',
trackingId: null,
createdAt: '2025-01-01T00:00:00Z',
})
const { wrapper } = await mountPage()
const select = wrapper.find('select')
await select.setValue('Komplimang')
const textarea = wrapper.find('textarea')
await textarea.setValue('Hej fin bil!')
const button = wrapper.find('button[type="submit"]')
await button.trigger('submit')
await vi.waitFor(() => {
expect(mockCreateOrder).toHaveBeenCalledWith(
'ABC123',
'Komplimang',
expect.stringContaining('jättefin'),
)
expect(mockCreateOrder).toHaveBeenCalledWith('ABC123', 'Hej fin bil!')
})
})
@ -148,7 +126,6 @@ describe('ComposePage', () => {
mockCreateOrder.mockResolvedValue({
id: 'order-1',
plate: 'ABC123',
template: null,
status: 'pending_payment',
trackingId: null,
createdAt: '2025-01-01T00:00:00Z',
@ -179,28 +156,16 @@ describe('ComposePage', () => {
})
})
it('sends null template for Fritt meddelande', async () => {
mockCreateOrder.mockResolvedValue({
id: 'order-1',
plate: 'ABC123',
template: null,
status: 'pending_payment',
trackingId: null,
createdAt: '2025-01-01T00:00:00Z',
})
it('shows preview with letter content', async () => {
const { wrapper } = await mountPage()
const textarea = wrapper.find('textarea')
await textarea.setValue('Custom text')
const button = wrapper.find('button[type="submit"]')
await button.trigger('submit')
await textarea.setValue('Hej!')
expect(wrapper.text()).toContain('Förhandsvisning')
expect(wrapper.text()).toContain('Hej!')
})
await vi.waitFor(() => {
expect(mockCreateOrder).toHaveBeenCalledWith(
'ABC123',
null,
'Custom text',
)
})
it('shows GDPR footer in preview', async () => {
const { wrapper } = await mountPage()
expect(wrapper.text()).toContain('Detta brev skickades via BilHej.se')
})
})

View file

@ -38,7 +38,6 @@ const mockOrders = [
{
id: 'c1eebc99-9c0b-4ef8-bb6d-6bb9bd380a11',
plate: 'ABC123',
template: 'Komplimang',
status: 'sent',
trackingId: 'PN123456789',
createdAt: '2026-05-11T12:00:00Z',
@ -46,7 +45,6 @@ const mockOrders = [
{
id: 'c2eebc99-9c0b-4ef8-bb6d-6bb9bd380a12',
plate: 'DEF456',
template: null,
status: 'pending_payment',
trackingId: null,
createdAt: '2026-05-14T13:00:00Z',
@ -66,7 +64,9 @@ describe('OrdersPage', () => {
const { wrapper } = mountPage()
await new Promise((r) => setTimeout(r, 50))
expect(wrapper.text()).toContain('Mina beställningar')
expect(wrapper.text()).toContain('Här kan du se dina tidigare beställningar')
expect(wrapper.text()).toContain(
'Här kan du se dina tidigare beställningar',
)
})
it('shows loading state initially', async () => {
@ -91,13 +91,6 @@ describe('OrdersPage', () => {
expect(wrapper.text()).toContain('DEF456')
})
it('renders template name or fallback for null', async () => {
const { wrapper } = mountPage()
await new Promise((r) => setTimeout(r, 50))
expect(wrapper.text()).toContain('Komplimang')
expect(wrapper.text()).toContain('Fritt meddelande')
})
it('renders Swedish status labels', async () => {
const { wrapper } = mountPage()
await new Promise((r) => setTimeout(r, 50))
@ -119,7 +112,6 @@ describe('OrdersPage', () => {
{
id: 'c2eebc99-9c0b-4ef8-bb6d-6bb9bd380a12',
plate: 'DEF456',
template: null,
status: 'pending_payment',
trackingId: null,
createdAt: '2026-05-14T13:00:00Z',

View file

@ -1,7 +1,6 @@
import { describe, it, expect, beforeEach } from 'vitest'
import { setActivePinia, createPinia } from 'pinia'
import router from '@/router'
import { useAuthStore } from '@/stores/authStore'
describe('Router', () => {
beforeEach(() => {

View file

@ -3,7 +3,6 @@ import { request } from './client'
export interface Order {
id: string
plate: string
template: string | null
status: string
trackingId: string | null
createdAt: string
@ -13,13 +12,9 @@ export function fetchOrders(): Promise<Order[]> {
return request<Order[]>('/orders')
}
export function createOrder(
plate: string,
template: string | null,
letterText: string,
): Promise<Order> {
export function createOrder(plate: string, letterText: string): Promise<Order> {
return request<Order>('/orders', {
method: 'POST',
body: JSON.stringify({ plate, template, letterText }),
body: JSON.stringify({ plate, letterText }),
})
}

View file

@ -1,5 +1,4 @@
<script setup lang="ts">
</script>
<script setup lang="ts"></script>
<template>
<div class="admin">

View file

@ -1,14 +1,12 @@
<script setup lang="ts">
import { ref, computed, watch } from 'vue'
import { ref, computed } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { templates } from '@/data/templates'
import { createOrder } from '@/api/orders'
const router = useRouter()
const route = useRoute()
const plate = computed(() => (route.query.plate as string) || '')
const selectedTemplate = ref('Fritt meddelande')
const letterText = ref('')
const submitting = ref(false)
const errorMessage = ref('')
@ -22,13 +20,6 @@ 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'
watch(selectedTemplate, (name) => {
const template = templates.find((t) => t.name === name)
if (template) {
letterText.value = template.body
}
})
async function handleSubmit() {
if (!canSubmit.value) return
@ -36,11 +27,7 @@ async function handleSubmit() {
errorMessage.value = ''
try {
const templateName =
selectedTemplate.value === 'Fritt meddelande'
? null
: selectedTemplate.value
await createOrder(plate.value, templateName, letterText.value)
await createOrder(plate.value, letterText.value)
await router.push({ name: 'orders' })
} catch {
errorMessage.value = 'Kunde inte skapa beställningen. Försök igen senare.'
@ -62,19 +49,6 @@ async function handleSubmit() {
</p>
<form v-if="plate" class="compose__form" @submit.prevent="handleSubmit">
<div class="compose__field">
<label for="template" class="compose__label">Välj mall</label>
<select
id="template"
v-model="selectedTemplate"
class="compose__select"
>
<option v-for="t in templates" :key="t.name" :value="t.name">
{{ t.name }}
</option>
</select>
</div>
<div class="compose__field">
<label for="letter" class="compose__label">Ditt meddelande</label>
<textarea
@ -170,23 +144,6 @@ async function handleSubmit() {
color: #4a5568;
}
.compose__select {
width: 100%;
padding: 0.75rem 1rem;
font-size: 1rem;
border: 2px solid #cbd5e0;
border-radius: 0.5rem;
outline: none;
background: #fff;
transition: border-color 0.15s ease;
box-sizing: border-box;
}
.compose__select:focus {
border-color: #4299e1;
box-shadow: 0 0 0 3px rgba(66, 153, 225, 0.25);
}
.compose__textarea {
width: 100%;
padding: 0.75rem 1rem;

View file

@ -69,11 +69,6 @@ onMounted(async () => {
</div>
<div class="orders__card-body">
<div class="orders__detail">
<span class="orders__label">Mall</span>
<span class="orders__value">{{ order.template || 'Fritt meddelande' }}</span>
</div>
<div class="orders__detail">
<span class="orders__label">Datum</span>
<span class="orders__value">{{ formatDate(order.createdAt) }}</span>

View file

@ -47,5 +47,14 @@ export const useAuthStore = defineStore('auth', () => {
clearToken()
}
return { token, role, email, isAuthenticated, isAdmin, registerUser, loginUser, logout }
return {
token,
role,
email,
isAuthenticated,
isAdmin,
registerUser,
loginUser,
logout,
}
})