feat(ui): custom confirmation dialog replacing native window.confirm
All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 51s
All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 51s
Single reusable dialog with a promise-based API: confirmAction({...})
returns Promise<boolean>. Supports title, optional message body,
confirm/cancel labels, and a 'destructive' flag that paints the confirm
button red.
Accessibility: Escape cancels, Enter confirms, confirm button auto-focus,
role=dialog + aria-labelledby, backdrop click = cancel.
Rolled out to: recipe delete, domain remove, profile delete, wishlist
remove. Native confirm() is gone from the codebase.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
import { onMount } from 'svelte';
|
||||
import { profileStore } from '$lib/client/profile.svelte';
|
||||
import ProfileSwitcher from '$lib/components/ProfileSwitcher.svelte';
|
||||
import ConfirmDialog from '$lib/components/ConfirmDialog.svelte';
|
||||
|
||||
let { children } = $props();
|
||||
|
||||
@@ -10,6 +11,8 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<ConfirmDialog />
|
||||
|
||||
<header class="bar">
|
||||
<a href="/" class="brand">Kochwas</a>
|
||||
<div class="bar-right">
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import type { AllowedDomain } from '$lib/types';
|
||||
import { confirmAction } from '$lib/client/confirm.svelte';
|
||||
|
||||
let domains = $state<AllowedDomain[]>([]);
|
||||
let loading = $state(true);
|
||||
@@ -38,9 +39,15 @@
|
||||
await load();
|
||||
}
|
||||
|
||||
async function remove(id: number) {
|
||||
if (!confirm('Domain entfernen?')) return;
|
||||
await fetch(`/api/domains/${id}`, { method: 'DELETE' });
|
||||
async function remove(d: AllowedDomain) {
|
||||
const ok = await confirmAction({
|
||||
title: 'Domain entfernen?',
|
||||
message: `${d.domain} wird nicht mehr durchsucht. Gespeicherte Rezepte bleiben erhalten.`,
|
||||
confirmLabel: 'Entfernen',
|
||||
destructive: true
|
||||
});
|
||||
if (!ok) return;
|
||||
await fetch(`/api/domains/${d.id}`, { method: 'DELETE' });
|
||||
await load();
|
||||
}
|
||||
|
||||
@@ -80,7 +87,7 @@
|
||||
<div class="dom">{d.domain}</div>
|
||||
{#if d.display_name}<div class="label">{d.display_name}</div>{/if}
|
||||
</div>
|
||||
<button class="btn danger" onclick={() => remove(d.id)}>Löschen</button>
|
||||
<button class="btn danger" onclick={() => remove(d)}>Löschen</button>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { profileStore } from '$lib/client/profile.svelte';
|
||||
import { confirmAction } from '$lib/client/confirm.svelte';
|
||||
|
||||
let newName = $state('');
|
||||
let newEmoji = $state('🍳');
|
||||
@@ -36,8 +37,15 @@
|
||||
await profileStore.load();
|
||||
}
|
||||
|
||||
async function remove(id: number) {
|
||||
if (!confirm('Profil wirklich löschen? Bewertungen, Favoriten und Kochjournal dieses Profils werden mit gelöscht.')) return;
|
||||
async function remove(id: number, name: string) {
|
||||
const ok = await confirmAction({
|
||||
title: `Profil „${name}" löschen?`,
|
||||
message:
|
||||
'Bewertungen, Favoriten und Kochjournal-Einträge dieses Profils werden mit gelöscht. Rezepte und Kommentare bleiben erhalten.',
|
||||
confirmLabel: 'Löschen',
|
||||
destructive: true
|
||||
});
|
||||
if (!ok) return;
|
||||
await fetch(`/api/profiles/${id}`, { method: 'DELETE' });
|
||||
if (profileStore.activeId === id) profileStore.clear();
|
||||
await profileStore.load();
|
||||
@@ -82,7 +90,7 @@
|
||||
</div>
|
||||
<div class="actions">
|
||||
<button class="btn" onclick={() => rename(p.id, p.name)}>Umbenennen</button>
|
||||
<button class="btn danger" onclick={() => remove(p.id)}>Löschen</button>
|
||||
<button class="btn danger" onclick={() => remove(p.id, p.name)}>Löschen</button>
|
||||
</div>
|
||||
</li>
|
||||
{/each}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import RecipeView from '$lib/components/RecipeView.svelte';
|
||||
import StarRating from '$lib/components/StarRating.svelte';
|
||||
import { profileStore } from '$lib/client/profile.svelte';
|
||||
import { confirmAction } from '$lib/client/confirm.svelte';
|
||||
import type { CommentRow } from '$lib/server/recipes/actions';
|
||||
|
||||
let { data } = $props();
|
||||
@@ -109,7 +110,13 @@
|
||||
}
|
||||
|
||||
async function deleteRecipe() {
|
||||
if (!confirm(`Rezept „${data.recipe.title}" wirklich löschen?`)) return;
|
||||
const ok = await confirmAction({
|
||||
title: 'Rezept löschen?',
|
||||
message: `„${data.recipe.title}" wird endgültig entfernt — mit Bewertungen, Kommentaren und Kochjournal-Einträgen.`,
|
||||
confirmLabel: 'Löschen',
|
||||
destructive: true
|
||||
});
|
||||
if (!ok) return;
|
||||
await fetch(`/api/recipes/${data.recipe.id}`, { method: 'DELETE' });
|
||||
goto('/');
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { profileStore } from '$lib/client/profile.svelte';
|
||||
import { confirmAction } from '$lib/client/confirm.svelte';
|
||||
import type { WishlistEntry, SortKey } from '$lib/server/wishlist/repository';
|
||||
|
||||
let entries = $state<WishlistEntry[]>([]);
|
||||
@@ -39,7 +40,13 @@
|
||||
}
|
||||
|
||||
async function remove(entry: WishlistEntry) {
|
||||
if (!confirm(`„${entry.title}" von der Wunschliste entfernen?`)) return;
|
||||
const ok = await confirmAction({
|
||||
title: 'Von der Wunschliste entfernen?',
|
||||
message: `„${entry.title}" wird aus der Wunschliste entfernt. Das Rezept selbst bleibt gespeichert.`,
|
||||
confirmLabel: 'Entfernen',
|
||||
destructive: true
|
||||
});
|
||||
if (!ok) return;
|
||||
await fetch(`/api/wishlist/${entry.recipe_id}`, { method: 'DELETE' });
|
||||
await load();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user