Polish Store endpoints: 409 conflicts and confirm-before-delete
Backend pre-checks duplicate names on POST/PUT and returns 409 Conflict (replacing the previous 500 from the unique constraint). DELETE-with-active-lists also returns 409 instead of 400 for semantic accuracy. Frontend addStore and saveEdit now surface API errors via toast instead of failing silently. Delete is now gated by a confirmation modal so accidental clicks no longer destroy data.
This commit is contained in:
@@ -14,6 +14,7 @@
|
||||
let editingId = $state<number | null>(null);
|
||||
let editName = $state('');
|
||||
let loading = $state(true);
|
||||
let pendingDelete = $state<Store | null>(null);
|
||||
|
||||
onMount(async () => {
|
||||
stores = await api<Store[]>('/api/stores');
|
||||
@@ -22,12 +23,16 @@
|
||||
|
||||
async function addStore() {
|
||||
if (!newName.trim()) return;
|
||||
await api('/api/stores', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ name: newName, sortOrder: stores.length })
|
||||
});
|
||||
newName = '';
|
||||
stores = await api<Store[]>('/api/stores');
|
||||
try {
|
||||
await api('/api/stores', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ name: newName, sortOrder: stores.length })
|
||||
});
|
||||
newName = '';
|
||||
stores = await api<Store[]>('/api/stores');
|
||||
} catch (e) {
|
||||
toast.error(e instanceof Error ? e.message : 'Failed to add store');
|
||||
}
|
||||
}
|
||||
|
||||
function startEdit(store: Store) {
|
||||
@@ -38,20 +43,31 @@
|
||||
async function saveEdit() {
|
||||
if (!editName.trim() || !editingId) return;
|
||||
const store = stores.find((s) => s.id === editingId)!;
|
||||
await api(`/api/stores/${editingId}`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify({ name: editName, sortOrder: store.sortOrder })
|
||||
});
|
||||
editingId = null;
|
||||
stores = await api<Store[]>('/api/stores');
|
||||
try {
|
||||
await api(`/api/stores/${editingId}`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify({ name: editName, sortOrder: store.sortOrder })
|
||||
});
|
||||
editingId = null;
|
||||
stores = await api<Store[]>('/api/stores');
|
||||
} catch (e) {
|
||||
toast.error(e instanceof Error ? e.message : 'Failed to update store');
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteStore(id: number) {
|
||||
function requestDelete(store: Store) {
|
||||
pendingDelete = store;
|
||||
}
|
||||
|
||||
async function confirmDelete() {
|
||||
if (!pendingDelete) return;
|
||||
const id = pendingDelete.id;
|
||||
pendingDelete = null;
|
||||
try {
|
||||
await api(`/api/stores/${id}`, { method: 'DELETE' });
|
||||
stores = stores.filter((s) => s.id !== id);
|
||||
} catch (e: any) {
|
||||
toast.error(e.message);
|
||||
} catch (e) {
|
||||
toast.error(e instanceof Error ? e.message : 'Failed to delete store');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -93,10 +109,42 @@
|
||||
{:else}
|
||||
<span class="flex-1 font-medium">{store.name}</span>
|
||||
<button onclick={() => startEdit(store)} class="text-sm text-gray-400">Edit</button>
|
||||
<button onclick={() => deleteStore(store.id)} class="text-sm text-danger">Delete</button>
|
||||
<button onclick={() => requestDelete(store)} class="text-sm text-danger">Delete</button>
|
||||
{/if}
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if pendingDelete}
|
||||
<div
|
||||
class="fixed inset-0 z-40 flex items-center justify-center bg-black/40 px-4"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="delete-store-title"
|
||||
>
|
||||
<div class="w-full max-w-sm rounded-lg bg-white p-5 shadow-xl">
|
||||
<h3 id="delete-store-title" class="mb-2 text-lg font-semibold">Delete store?</h3>
|
||||
<p class="mb-5 text-sm text-gray-600">
|
||||
Delete <span class="font-medium">{pendingDelete.name}</span>? This can't be undone.
|
||||
</p>
|
||||
<div class="flex justify-end gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => (pendingDelete = null)}
|
||||
class="rounded-lg border border-gray-300 px-4 py-2 text-sm font-medium text-gray-700"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onclick={confirmDelete}
|
||||
class="rounded-lg bg-danger px-4 py-2 text-sm font-semibold text-white"
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
Reference in New Issue
Block a user