feat(ui): custom dialog replaces all remaining window.alert() calls
All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 53s
All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 53s
alertAction({title, message}) returns Promise<void> and renders the
same ConfirmDialog with infoOnly:true — single OK button, no Abbrechen.
Replaces:
- 'Bitte Profil wählen.' (recipe rating / favorite / cooked / comment)
- 'Bitte Profil wählen, um zu liken.' (wishlist)
- 'Profil konnte nicht angelegt werden' (ProfileSwitcher)
- 'Umbenennen fehlgeschlagen' (admin/profiles)
- 'Speichern fehlgeschlagen' (preview)
No window.alert() or window.confirm() left in the codebase.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,8 @@ export type ConfirmOptions = {
|
|||||||
confirmLabel?: string;
|
confirmLabel?: string;
|
||||||
cancelLabel?: string;
|
cancelLabel?: string;
|
||||||
destructive?: boolean;
|
destructive?: boolean;
|
||||||
|
/** If true, hide the cancel button — used for simple info/alert dialogs. */
|
||||||
|
infoOnly?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type PendingRequest = ConfirmOptions & {
|
type PendingRequest = ConfirmOptions & {
|
||||||
@@ -39,3 +41,14 @@ export function confirmAction(options: ConfirmOptions): Promise<boolean> {
|
|||||||
if (typeof window === 'undefined') return Promise.resolve(false);
|
if (typeof window === 'undefined') return Promise.resolve(false);
|
||||||
return confirmStore.ask(options);
|
return confirmStore.ask(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show a modal info dialog with a single OK button. Resolves when dismissed.
|
||||||
|
* Use instead of window.alert().
|
||||||
|
*/
|
||||||
|
export function alertAction(options: Omit<ConfirmOptions, 'destructive' | 'cancelLabel' | 'infoOnly'>): Promise<void> {
|
||||||
|
if (typeof window === 'undefined') return Promise.resolve();
|
||||||
|
return confirmStore
|
||||||
|
.ask({ ...options, infoOnly: true, confirmLabel: options.confirmLabel ?? 'OK' })
|
||||||
|
.then(() => undefined);
|
||||||
|
}
|
||||||
|
|||||||
@@ -41,13 +41,15 @@
|
|||||||
<p class="message">{p.message}</p>
|
<p class="message">{p.message}</p>
|
||||||
{/if}
|
{/if}
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<button
|
{#if !p.infoOnly}
|
||||||
type="button"
|
<button
|
||||||
class="btn cancel"
|
type="button"
|
||||||
onclick={() => confirmStore.answer(false)}
|
class="btn cancel"
|
||||||
>
|
onclick={() => confirmStore.answer(false)}
|
||||||
{p.cancelLabel ?? 'Abbrechen'}
|
>
|
||||||
</button>
|
{p.cancelLabel ?? 'Abbrechen'}
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="btn confirm"
|
class="btn confirm"
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { profileStore } from '$lib/client/profile.svelte';
|
import { profileStore } from '$lib/client/profile.svelte';
|
||||||
|
import { alertAction } from '$lib/client/confirm.svelte';
|
||||||
|
|
||||||
let showModal = $state(false);
|
let showModal = $state(false);
|
||||||
let newName = $state('');
|
let newName = $state('');
|
||||||
@@ -13,7 +14,10 @@
|
|||||||
newName = '';
|
newName = '';
|
||||||
showModal = false;
|
showModal = false;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
alert((e as Error).message);
|
await alertAction({
|
||||||
|
title: 'Profil konnte nicht angelegt werden',
|
||||||
|
message: (e as Error).message
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { profileStore } from '$lib/client/profile.svelte';
|
import { profileStore } from '$lib/client/profile.svelte';
|
||||||
import { confirmAction } from '$lib/client/confirm.svelte';
|
import { confirmAction, alertAction } from '$lib/client/confirm.svelte';
|
||||||
|
|
||||||
let newName = $state('');
|
let newName = $state('');
|
||||||
let newEmoji = $state('🍳');
|
let newEmoji = $state('🍳');
|
||||||
@@ -31,7 +31,10 @@
|
|||||||
});
|
});
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
const body = await res.json().catch(() => ({}));
|
const body = await res.json().catch(() => ({}));
|
||||||
alert(`Fehler: ${body.message ?? res.status}`);
|
await alertAction({
|
||||||
|
title: 'Umbenennen fehlgeschlagen',
|
||||||
|
message: body.message ?? `HTTP ${res.status}`
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await profileStore.load();
|
await profileStore.load();
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import RecipeView from '$lib/components/RecipeView.svelte';
|
import RecipeView from '$lib/components/RecipeView.svelte';
|
||||||
|
import { alertAction } from '$lib/client/confirm.svelte';
|
||||||
import type { Recipe } from '$lib/types';
|
import type { Recipe } from '$lib/types';
|
||||||
|
|
||||||
let targetUrl = $state(($page.url.searchParams.get('url') ?? '').trim());
|
let targetUrl = $state(($page.url.searchParams.get('url') ?? '').trim());
|
||||||
@@ -45,7 +46,10 @@
|
|||||||
saving = false;
|
saving = false;
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
const body = await res.json().catch(() => ({}));
|
const body = await res.json().catch(() => ({}));
|
||||||
alert(`Speichern fehlgeschlagen: ${body.message ?? res.status}`);
|
await alertAction({
|
||||||
|
title: 'Speichern fehlgeschlagen',
|
||||||
|
message: body.message ?? `HTTP ${res.status}`
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const body = await res.json();
|
const body = await res.json();
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
import RecipeView from '$lib/components/RecipeView.svelte';
|
import RecipeView from '$lib/components/RecipeView.svelte';
|
||||||
import StarRating from '$lib/components/StarRating.svelte';
|
import StarRating from '$lib/components/StarRating.svelte';
|
||||||
import { profileStore } from '$lib/client/profile.svelte';
|
import { profileStore } from '$lib/client/profile.svelte';
|
||||||
import { confirmAction } from '$lib/client/confirm.svelte';
|
import { confirmAction, alertAction } from '$lib/client/confirm.svelte';
|
||||||
import type { CommentRow } from '$lib/server/recipes/actions';
|
import type { CommentRow } from '$lib/server/recipes/actions';
|
||||||
|
|
||||||
let { data } = $props();
|
let { data } = $props();
|
||||||
@@ -40,7 +40,10 @@
|
|||||||
|
|
||||||
async function setRating(stars: number) {
|
async function setRating(stars: number) {
|
||||||
if (!profileStore.active) {
|
if (!profileStore.active) {
|
||||||
alert('Bitte erst Profil wählen.');
|
await alertAction({
|
||||||
|
title: 'Kein Profil gewählt',
|
||||||
|
message: 'Tippe oben rechts auf „Profil wählen", dann klappt die Aktion.'
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await fetch(`/api/recipes/${data.recipe.id}/rating`, {
|
await fetch(`/api/recipes/${data.recipe.id}/rating`, {
|
||||||
@@ -55,7 +58,10 @@
|
|||||||
|
|
||||||
async function toggleFavorite() {
|
async function toggleFavorite() {
|
||||||
if (!profileStore.active) {
|
if (!profileStore.active) {
|
||||||
alert('Bitte erst Profil wählen.');
|
await alertAction({
|
||||||
|
title: 'Kein Profil gewählt',
|
||||||
|
message: 'Tippe oben rechts auf „Profil wählen", dann klappt die Aktion.'
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const method = isFav ? 'DELETE' : 'PUT';
|
const method = isFav ? 'DELETE' : 'PUT';
|
||||||
@@ -69,7 +75,10 @@
|
|||||||
|
|
||||||
async function logCooked() {
|
async function logCooked() {
|
||||||
if (!profileStore.active) {
|
if (!profileStore.active) {
|
||||||
alert('Bitte erst Profil wählen.');
|
await alertAction({
|
||||||
|
title: 'Kein Profil gewählt',
|
||||||
|
message: 'Tippe oben rechts auf „Profil wählen", dann klappt die Aktion.'
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const res = await fetch(`/api/recipes/${data.recipe.id}/cooked`, {
|
const res = await fetch(`/api/recipes/${data.recipe.id}/cooked`, {
|
||||||
@@ -83,7 +92,10 @@
|
|||||||
|
|
||||||
async function addComment() {
|
async function addComment() {
|
||||||
if (!profileStore.active) {
|
if (!profileStore.active) {
|
||||||
alert('Bitte erst Profil wählen.');
|
await alertAction({
|
||||||
|
title: 'Kein Profil gewählt',
|
||||||
|
message: 'Tippe oben rechts auf „Profil wählen", dann klappt die Aktion.'
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const text = newComment.trim();
|
const text = newComment.trim();
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { profileStore } from '$lib/client/profile.svelte';
|
import { profileStore } from '$lib/client/profile.svelte';
|
||||||
import { confirmAction } from '$lib/client/confirm.svelte';
|
import { confirmAction, alertAction } from '$lib/client/confirm.svelte';
|
||||||
import type { WishlistEntry, SortKey } from '$lib/server/wishlist/repository';
|
import type { WishlistEntry, SortKey } from '$lib/server/wishlist/repository';
|
||||||
|
|
||||||
let entries = $state<WishlistEntry[]>([]);
|
let entries = $state<WishlistEntry[]>([]);
|
||||||
@@ -27,7 +27,10 @@
|
|||||||
|
|
||||||
async function toggleLike(entry: WishlistEntry) {
|
async function toggleLike(entry: WishlistEntry) {
|
||||||
if (!profileStore.active) {
|
if (!profileStore.active) {
|
||||||
alert('Bitte Profil wählen, um zu liken.');
|
await alertAction({
|
||||||
|
title: 'Kein Profil gewählt',
|
||||||
|
message: 'Tippe oben rechts auf „Profil wählen", um zu liken.'
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const method = entry.liked_by_me ? 'DELETE' : 'PUT';
|
const method = entry.liked_by_me ? 'DELETE' : 'PUT';
|
||||||
|
|||||||
Reference in New Issue
Block a user