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:
parent
5fa903d9af
commit
6ab5e2f707
21 changed files with 51 additions and 222 deletions
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import java.util.UUID;
|
|||
public record OrderResponse(
|
||||
UUID id,
|
||||
String plate,
|
||||
String template,
|
||||
String status,
|
||||
String trackingId,
|
||||
Instant createdAt
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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"));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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'),
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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(() => {
|
||||
|
|
|
|||
|
|
@ -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 }),
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
<script setup lang="ts">
|
||||
</script>
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<template>
|
||||
<div class="admin">
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
})
|
||||
|
|
|
|||
Loading…
Reference in a new issue