feat: replace browser confirm() with reusable ConfirmDialog component

Promise-based modal dialog with backdrop, keyboard support, and animations,
replacing all 18 native confirm() call sites across fitness, cospend, recipes,
and tasks pages.
This commit is contained in:
2026-04-08 16:47:21 +02:00
parent 7fb47717f4
commit 376fbf1ba7
20 changed files with 206 additions and 25 deletions
+3 -1
View File
@@ -2,6 +2,7 @@
import '../app.css';
import { onNavigate } from '$app/navigation';
import Toast from '$lib/components/Toast.svelte';
import ConfirmDialog from '$lib/components/ConfirmDialog.svelte';
let { children } = $props();
onNavigate((navigation) => {
@@ -22,4 +23,5 @@
</script>
{@render children()}
<Toast />
<Toast />
<ConfirmDialog />
@@ -5,6 +5,7 @@
import ProfilePicture from '$lib/components/cospend/ProfilePicture.svelte';
import { getCategoryEmoji } from '$lib/utils/categories';
import { toast } from '$lib/js/toast.svelte';
import { confirm } from '$lib/js/confirmDialog.svelte';
import { isSettlementPayment, getSettlementIcon, getSettlementReceiver } from '$lib/utils/settlements';
import AddButton from '$lib/components/AddButton.svelte';
import { detectCospendLang, cospendRoot, t, locale, splitDescription, paymentCategoryName } from '$lib/js/cospendI18n';
@@ -82,7 +83,7 @@
}
async function deletePayment(/** @type {string} */ paymentId) {
if (!confirm(t('delete_payment_confirm', lang))) {
if (!await confirm(t('delete_payment_confirm', lang))) {
return;
}
@@ -3,6 +3,7 @@
import { goto } from '$app/navigation';
import { page } from '$app/stores';
import { detectCospendLang, cospendRoot, locale, t, getCategoryOptionsI18n } from '$lib/js/cospendI18n';
import { confirm } from '$lib/js/confirmDialog.svelte';
import FormSection from '$lib/components/FormSection.svelte';
import ImageUpload from '$lib/components/ImageUpload.svelte';
import SaveFab from '$lib/components/SaveFab.svelte';
@@ -264,7 +265,7 @@
let deleting = $state(false);
async function deletePayment() {
if (!confirm('Are you sure you want to delete this payment? This action cannot be undone.')) {
if (!await confirm('Are you sure you want to delete this payment? This action cannot be undone.')) {
return;
}
@@ -3,6 +3,7 @@
import { getCategoryEmoji } from '$lib/utils/categories';
import ProfilePicture from '$lib/components/cospend/ProfilePicture.svelte';
import { toast } from '$lib/js/toast.svelte';
import { confirm } from '$lib/js/confirmDialog.svelte';
import AddButton from '$lib/components/AddButton.svelte';
import { formatCurrency } from '$lib/utils/formatters';
import Toggle from '$lib/components/Toggle.svelte';
@@ -65,7 +66,7 @@
}
async function deleteRecurringPayment(/** @type {string} */ paymentId, /** @type {string} */ title) {
if (!confirm(`${t('delete_recurring_confirm', lang)} "${title}"?`)) {
if (!await confirm(`${t('delete_recurring_confirm', lang)} "${title}"?`)) {
return;
}
@@ -1,5 +1,6 @@
<script>
import ToTryCard from '$lib/components/recipes/ToTryCard.svelte';
import { confirm } from '$lib/js/confirmDialog.svelte';
let { data } = $props();
@@ -102,7 +103,7 @@
/** @param {any} id */
async function handleDelete(id) {
const msg = isEnglish ? 'Delete this recipe?' : 'Dieses Rezept löschen?';
if (!confirm(msg)) return;
if (!await confirm(msg)) return;
const res = await fetch(`/api/${data.recipeLang}/to-try`, {
method: 'DELETE',
@@ -3,6 +3,7 @@
import { page } from '$app/stores';
import { Clock, Weight, Trophy, Trash2, Pencil, Plus, Upload, Route, X, RefreshCw, Gauge, Flame, Info } from '@lucide/svelte';
import { detectFitnessLang, fitnessSlugs, t } from '$lib/js/fitnessI18n';
import { confirm } from '$lib/js/confirmDialog.svelte';
import { toast } from '$lib/js/toast.svelte';
const lang = $derived(detectFitnessLang($page.url.pathname));
@@ -216,7 +217,7 @@
}
async function deleteSession() {
if (!confirm(t('delete_session_confirm', lang))) return;
if (!await confirm(t('delete_session_confirm', lang))) return;
deleting = true;
try {
const res = await fetch(`/api/fitness/sessions/${session._id}`, { method: 'DELETE' });
@@ -446,7 +447,7 @@
/** @param {number} exIdx */
async function removeGpx(exIdx) {
if (!confirm(t('remove_gps_confirm', lang))) return;
if (!await confirm(t('remove_gps_confirm', lang))) return;
try {
const res = await fetch(`/api/fitness/sessions/${session._id}/gpx`, {
method: 'DELETE',
@@ -3,6 +3,7 @@
import { Pencil, Trash2, ChevronRight, Venus, Mars } from '@lucide/svelte';
import { detectFitnessLang, t } from '$lib/js/fitnessI18n';
import { toast } from '$lib/js/toast.svelte';
import { confirm } from '$lib/js/confirmDialog.svelte';
const lang = $derived(detectFitnessLang($page.url.pathname));
const measureSlug = $derived(lang === 'en' ? 'measure' : 'messen');
@@ -93,7 +94,7 @@
/** @param {string} id */
async function deleteMeasurement(id) {
if (!confirm(t('delete_measurement_confirm', lang))) return;
if (!await confirm(t('delete_measurement_confirm', lang))) return;
try {
const res = await fetch(`/api/fitness/measurements/${id}`, { method: 'DELETE' });
if (res.ok) {
@@ -3,6 +3,7 @@
import { goto } from '$app/navigation';
import { detectFitnessLang, t } from '$lib/js/fitnessI18n';
import { toast } from '$lib/js/toast.svelte';
import { confirm } from '$lib/js/confirmDialog.svelte';
import { Trash2 } from '@lucide/svelte';
import SaveFab from '$lib/components/SaveFab.svelte';
@@ -85,7 +86,7 @@
}
async function deleteMeasurement() {
if (!confirm(t('delete_measurement_confirm', lang))) return;
if (!await confirm(t('delete_measurement_confirm', lang))) return;
deleting = true;
try {
const res = await fetch(`/api/fitness/measurements/${m._id}`, { method: 'DELETE' });
@@ -6,6 +6,7 @@
import AddButton from '$lib/components/AddButton.svelte';
import FoodSearch from '$lib/components/fitness/FoodSearch.svelte';
import { toast } from '$lib/js/toast.svelte';
import { confirm } from '$lib/js/confirmDialog.svelte';
import { getDRI, NUTRIENT_META } from '$lib/data/dailyReferenceIntake';
const lang = $derived(detectFitnessLang($page.url.pathname));
@@ -725,7 +726,7 @@
}
async function deleteEntry(id) {
if (!confirm(t('delete_entry_confirm', lang))) return;
if (!await confirm(t('delete_entry_confirm', lang))) return;
try {
const res = await fetch(`/api/fitness/food-log/${id}`, { method: 'DELETE' });
if (res.ok) {
@@ -4,6 +4,7 @@
import { ChevronLeft, Plus, Trash2, Pencil, UtensilsCrossed, X } from '@lucide/svelte';
import { detectFitnessLang, fitnessSlugs, t } from '$lib/js/fitnessI18n';
import { toast } from '$lib/js/toast.svelte';
import { confirm } from '$lib/js/confirmDialog.svelte';
import FoodSearch from '$lib/components/fitness/FoodSearch.svelte';
const lang = $derived(detectFitnessLang($page.url.pathname));
@@ -127,7 +128,7 @@
}
async function deleteMeal(meal) {
if (!confirm(t('delete_meal_confirm', lang))) return;
if (!await confirm(t('delete_meal_confirm', lang))) return;
try {
const res = await fetch(`/api/fitness/custom-meals/${meal._id}`, { method: 'DELETE' });
if (res.ok) {
@@ -3,6 +3,7 @@
import { page } from '$app/stores';
import { Trash2, Play, Pause, Trophy, Clock, Dumbbell, Route, RefreshCw, Check, ChevronUp, ChevronDown, Flame, MapPin, Volume2, X, Timer, Plus, GripVertical } from '@lucide/svelte';
import { detectFitnessLang, fitnessSlugs, t } from '$lib/js/fitnessI18n';
import { confirm } from '$lib/js/confirmDialog.svelte';
const lang = $derived(detectFitnessLang($page.url.pathname));
const sl = $derived(fitnessSlugs(lang));
@@ -161,7 +162,7 @@
}
async function deleteInterval(/** @type {string} */ id) {
if (!confirm(t('delete_interval_confirm', lang))) return;
if (!await confirm(t('delete_interval_confirm', lang))) return;
await fetch(`/api/fitness/intervals/${id}`, { method: 'DELETE' });
if (selectedIntervalId === id) selectedIntervalId = null;
await fetchIntervalTemplates();
+2 -1
View File
@@ -1,5 +1,6 @@
<script>
import { invalidateAll } from '$app/navigation';
import { confirm } from '$lib/js/confirmDialog.svelte';
import { formatDistanceToNow, isPast, isToday, differenceInDays, format } from 'date-fns';
import { de } from 'date-fns/locale';
import { Plus, Check, Pencil, Trash2, Tag, Users, RotateCcw, Calendar,
@@ -124,7 +125,7 @@
/** @param {any} task */
async function deleteTask(task) {
if (!confirm(`"${task.title}" wirklich löschen?`)) return;
if (!await confirm(`"${task.title}" wirklich löschen?`)) return;
const res = await fetch(`/api/tasks/${task._id}`, { method: 'DELETE' });
if (res.ok) await refreshTasks();
}
+2 -1
View File
@@ -1,5 +1,6 @@
<script>
import { invalidateAll } from '$app/navigation';
import { confirm } from '$lib/js/confirmDialog.svelte';
import { STICKERS, getStickerById, getRarityColor } from '$lib/utils/stickers';
import { formatDistanceToNow } from 'date-fns';
import { de } from 'date-fns/locale';
@@ -74,7 +75,7 @@
}
async function clearHistory() {
if (!confirm('Deinen gesamten Verlauf und alle Sticker wirklich löschen? Das kann nicht rückgängig gemacht werden.')) return;
if (!await confirm('Deinen gesamten Verlauf und alle Sticker wirklich löschen? Das kann nicht rückgängig gemacht werden.')) return;
const res = await fetch('/api/tasks/stats', { method: 'DELETE' });
if (res.ok) await invalidateAll();
}