From 90b02c4690cc7d7114a7e682a3fda6cb27dce963 Mon Sep 17 00:00:00 2001 From: Nils-Johan Gynther Date: Sun, 19 Apr 2026 17:52:43 +0200 Subject: [PATCH] feat(product): implement updateProductWithTags function for updating product and tags --- .../app/admin/products/EditProductForm.tsx | 5 +- frontend/app/admin/products/actions.ts | 53 +++++++++++++++++++ 2 files changed, 55 insertions(+), 3 deletions(-) diff --git a/frontend/app/admin/products/EditProductForm.tsx b/frontend/app/admin/products/EditProductForm.tsx index 97de8b53..d5119adb 100644 --- a/frontend/app/admin/products/EditProductForm.tsx +++ b/frontend/app/admin/products/EditProductForm.tsx @@ -2,7 +2,7 @@ import { useState, useTransition, useEffect } from 'react'; import type { Product } from '../../../features/inventory/types'; -import { updateProduct, deleteProduct, setProductTags, suggestProductCategory } from './actions'; +import { updateProductWithTags, deleteProduct, suggestProductCategory } from './actions'; type CategoryNode = { id: number; @@ -104,8 +104,7 @@ export default function EditProductForm({ product }: Props) { const rawTags = tagInput.split(',').map((t) => t.trim().toLowerCase()).filter(Boolean); startTransition(async () => { try { - await updateProduct(formData); - await setProductTags(product.id, rawTags); + await updateProductWithTags(formData, rawTags); setSuccess(true); setIsOpen(false); } catch (err) { diff --git a/frontend/app/admin/products/actions.ts b/frontend/app/admin/products/actions.ts index 277d0eea..bc8cfca9 100644 --- a/frontend/app/admin/products/actions.ts +++ b/frontend/app/admin/products/actions.ts @@ -59,6 +59,59 @@ export async function setProductTags(productId: number, tags: string[]) { revalidatePath('/admin/products'); } +export async function updateProductWithTags(formData: FormData, tags: string[]) { + const id = Number(formData.get('id')); + const name = String(formData.get('name') || '').trim(); + const canonicalName = String(formData.get('canonicalName') || '').trim(); + const category = String(formData.get('category') || '').trim(); + const subcategory = String(formData.get('subcategory') || '').trim(); + const brand = String(formData.get('brand') || '').trim(); + const categoryIdRaw = formData.get('categoryId'); + const categoryId = categoryIdRaw !== '' && categoryIdRaw != null ? Number(categoryIdRaw) : null; + + if (!name) throw new Error('Namn får inte vara tomt.'); + if (name.length > 100) throw new Error('Namn får inte vara längre än 100 tecken.'); + if (canonicalName.length > 100) throw new Error('Canonical name får inte vara längre än 100 tecken.'); + if (category.length > 100) throw new Error('Kategori får inte vara längre än 100 tecken.'); + if (subcategory.length > 100) throw new Error('Underkategori får inte vara längre än 100 tecken.'); + if (brand.length > 100) throw new Error('Varumärke får inte vara längre än 100 tecken.'); + + const authHeaders = await getAuthHeaders(); + + const res = await fetch(`${API_BASE}/api/products/${id}`, { + method: 'PATCH', + headers: { 'Content-Type': 'application/json', ...authHeaders }, + body: JSON.stringify({ + name: name || undefined, + canonicalName: canonicalName || undefined, + category: category || null, + subcategory: subcategory || null, + brand: brand || null, + categoryId, + }), + cache: 'no-store', + }); + + if (!res.ok) { + const text = await res.text(); + throw new Error(`Kunde inte uppdatera produkt: ${text}`); + } + + const tagsRes = await fetch(`${API_BASE}/api/products/${id}/tags`, { + method: 'PUT', + headers: { 'Content-Type': 'application/json', ...authHeaders }, + body: JSON.stringify({ tags }), + cache: 'no-store', + }); + + if (!tagsRes.ok) { + const text = await tagsRes.text(); + throw new Error(`Kunde inte uppdatera taggar: ${text}`); + } + + revalidatePath('/admin/products'); +} + export async function deleteProduct(id: number) { const res = await fetch(`${API_BASE}/api/products/${id}`, { method: 'DELETE',