diff --git a/apps/calendar/apps/web/src/lib/components/ToastContainer.svelte b/apps/calendar/apps/web/src/lib/components/ToastContainer.svelte
deleted file mode 100644
index 8ef3a8637..000000000
--- a/apps/calendar/apps/web/src/lib/components/ToastContainer.svelte
+++ /dev/null
@@ -1,184 +0,0 @@
-
-
-
- {#each toasts as toastItem (toastItem.id)}
-
-
- {@html getIcon(toastItem.type)}
-
-
{toastItem.message}
-
-
- {/each}
-
-
-
diff --git a/apps/calendar/apps/web/src/lib/components/event/EventContextMenu.svelte b/apps/calendar/apps/web/src/lib/components/event/EventContextMenu.svelte
index 0d7c36e2b..fc0d49e90 100644
--- a/apps/calendar/apps/web/src/lib/components/event/EventContextMenu.svelte
+++ b/apps/calendar/apps/web/src/lib/components/event/EventContextMenu.svelte
@@ -1,10 +1,9 @@
-
-{#if toasts.length > 0}
-
- {#each toasts as toast (toast.id)}
- {@const Icon = icons[toast.type]}
-
-
-
{toast.message}
-
-
- {/each}
-
-{/if}
-
-
diff --git a/apps/chat/apps/web/src/lib/stores/conversations.svelte.ts b/apps/chat/apps/web/src/lib/stores/conversations.svelte.ts
index 7a12cf6e7..6e85b520d 100644
--- a/apps/chat/apps/web/src/lib/stores/conversations.svelte.ts
+++ b/apps/chat/apps/web/src/lib/stores/conversations.svelte.ts
@@ -4,7 +4,7 @@
*/
import { conversationService } from '$lib/services/conversation';
-import { toastStore } from './toast.svelte';
+import { toastStore } from '@manacore/shared-ui';
import { sessionConversationsStore } from './session-conversations.svelte';
import { authStore } from './auth.svelte';
import type { Conversation } from '@chat/types';
diff --git a/apps/chat/apps/web/src/lib/stores/toast.svelte.ts b/apps/chat/apps/web/src/lib/stores/toast.svelte.ts
deleted file mode 100644
index 5528237ff..000000000
--- a/apps/chat/apps/web/src/lib/stores/toast.svelte.ts
+++ /dev/null
@@ -1,103 +0,0 @@
-/**
- * Toast Store - Centralized notification system using Svelte 5 runes
- */
-
-export type ToastType = 'success' | 'error' | 'warning' | 'info';
-
-export interface Toast {
- id: string;
- type: ToastType;
- message: string;
- duration: number;
-}
-
-// State
-let toasts = $state([]);
-
-// Auto-incrementing ID
-let nextId = 0;
-
-function generateId(): string {
- return `toast-${++nextId}-${Date.now()}`;
-}
-
-export const toastStore = {
- // Getter for reading toasts
- get toasts() {
- return toasts;
- },
-
- /**
- * Show a toast notification
- */
- show(message: string, type: ToastType = 'info', duration: number = 4000) {
- const id = generateId();
- const toast: Toast = { id, type, message, duration };
-
- toasts = [...toasts, toast];
-
- // Auto-remove after duration
- if (duration > 0) {
- setTimeout(() => {
- this.dismiss(id);
- }, duration);
- }
-
- return id;
- },
-
- /**
- * Show success toast
- */
- success(message: string, duration?: number) {
- return this.show(message, 'success', duration);
- },
-
- /**
- * Show error toast
- */
- error(message: string, duration: number = 6000) {
- return this.show(message, 'error', duration);
- },
-
- /**
- * Show warning toast
- */
- warning(message: string, duration?: number) {
- return this.show(message, 'warning', duration);
- },
-
- /**
- * Show info toast
- */
- info(message: string, duration?: number) {
- return this.show(message, 'info', duration);
- },
-
- /**
- * Dismiss a specific toast
- */
- dismiss(id: string) {
- toasts = toasts.filter((t) => t.id !== id);
- },
-
- /**
- * Dismiss all toasts
- */
- dismissAll() {
- toasts = [];
- },
-};
-
-/**
- * Helper function for API error handling
- * Use this in services/stores to show user-friendly error messages
- */
-export function handleApiError(
- error: unknown,
- fallbackMessage: string = 'Ein Fehler ist aufgetreten'
-): string {
- const message = error instanceof Error ? error.message : fallbackMessage;
- toastStore.error(message);
- return message;
-}
diff --git a/apps/chat/apps/web/src/routes/(protected)/chat/[id]/+page.svelte b/apps/chat/apps/web/src/routes/(protected)/chat/[id]/+page.svelte
index 5262047bf..c4cb78612 100644
--- a/apps/chat/apps/web/src/routes/(protected)/chat/[id]/+page.svelte
+++ b/apps/chat/apps/web/src/routes/(protected)/chat/[id]/+page.svelte
@@ -5,7 +5,7 @@
import { documentService } from '$lib/services/document';
import { authStore } from '$lib/stores/auth.svelte';
import { conversationsStore } from '$lib/stores/conversations.svelte';
- import { toastStore } from '$lib/stores/toast.svelte';
+ import { toastStore } from '@manacore/shared-ui';
import MessageList from '$lib/components/chat/MessageList.svelte';
import ChatInput from '$lib/components/chat/ChatInput.svelte';
import ChatLayout from '$lib/components/chat/ChatLayout.svelte';
diff --git a/apps/chat/apps/web/src/routes/+layout.svelte b/apps/chat/apps/web/src/routes/+layout.svelte
index cb7ce45ce..1e6853364 100644
--- a/apps/chat/apps/web/src/routes/+layout.svelte
+++ b/apps/chat/apps/web/src/routes/+layout.svelte
@@ -2,7 +2,7 @@
import '../app.css';
import { onMount } from 'svelte';
import { theme } from '$lib/stores/theme';
- import Toast from '$lib/components/Toast.svelte';
+ import { ToastContainer } from '@manacore/shared-ui';
let { children } = $props();
@@ -17,4 +17,4 @@
-
+
diff --git a/apps/clock/apps/web/src/lib/components/ToastContainer.svelte b/apps/clock/apps/web/src/lib/components/ToastContainer.svelte
deleted file mode 100644
index 28d60f24c..000000000
--- a/apps/clock/apps/web/src/lib/components/ToastContainer.svelte
+++ /dev/null
@@ -1,72 +0,0 @@
-
-
-
- {#each $toasts as toast (toast.id)}
-
-
- {getIcon(toast.type)}
-
- {toast.message}
-
-
- {/each}
-
-
-
diff --git a/apps/clock/apps/web/src/lib/stores/toast.ts b/apps/clock/apps/web/src/lib/stores/toast.ts
deleted file mode 100644
index 61dc340b0..000000000
--- a/apps/clock/apps/web/src/lib/stores/toast.ts
+++ /dev/null
@@ -1,47 +0,0 @@
-import { writable } from 'svelte/store';
-
-export interface Toast {
- id: string;
- type: 'success' | 'error' | 'info' | 'warning';
- message: string;
- duration?: number;
-}
-
-function createToastStore() {
- const { subscribe, update } = writable([]);
-
- function addToast(toast: Omit) {
- const id = crypto.randomUUID();
- const newToast = { ...toast, id };
-
- update((toasts) => [...toasts, newToast]);
-
- // Auto-remove after duration
- const duration = toast.duration || 5000;
- setTimeout(() => {
- removeToast(id);
- }, duration);
-
- return id;
- }
-
- function removeToast(id: string) {
- update((toasts) => toasts.filter((t) => t.id !== id));
- }
-
- return {
- subscribe,
- success: (message: string, duration?: number) =>
- addToast({ type: 'success', message, duration }),
- error: (message: string, duration?: number) => addToast({ type: 'error', message, duration }),
- info: (message: string, duration?: number) => addToast({ type: 'info', message, duration }),
- warning: (message: string, duration?: number) =>
- addToast({ type: 'warning', message, duration }),
- remove: removeToast,
- };
-}
-
-export const toasts = createToastStore();
-
-// Alias for compatibility with different import styles
-export const toast = toasts;
diff --git a/apps/clock/apps/web/src/routes/(app)/alarms/+page.svelte b/apps/clock/apps/web/src/routes/(app)/alarms/+page.svelte
index ae6f686c7..d96cb6f09 100644
--- a/apps/clock/apps/web/src/routes/(app)/alarms/+page.svelte
+++ b/apps/clock/apps/web/src/routes/(app)/alarms/+page.svelte
@@ -1,10 +1,9 @@
-
-
- {#each $toasts as toast (toast.id)}
-
-
- {getIcon(toast.type)}
-
- {toast.message}
-
-
- {/each}
-
-
-
diff --git a/apps/contacts/apps/web/src/lib/stores/toast.ts b/apps/contacts/apps/web/src/lib/stores/toast.ts
deleted file mode 100644
index eb45c4b3c..000000000
--- a/apps/contacts/apps/web/src/lib/stores/toast.ts
+++ /dev/null
@@ -1,44 +0,0 @@
-import { writable } from 'svelte/store';
-
-export interface Toast {
- id: string;
- type: 'success' | 'error' | 'info' | 'warning';
- message: string;
- duration?: number;
-}
-
-function createToastStore() {
- const { subscribe, update } = writable([]);
-
- function addToast(toast: Omit) {
- const id = crypto.randomUUID();
- const newToast = { ...toast, id };
-
- update((toasts) => [...toasts, newToast]);
-
- // Auto-remove after duration
- const duration = toast.duration || 5000;
- setTimeout(() => {
- removeToast(id);
- }, duration);
-
- return id;
- }
-
- function removeToast(id: string) {
- update((toasts) => toasts.filter((t) => t.id !== id));
- }
-
- return {
- subscribe,
- success: (message: string, duration?: number) =>
- addToast({ type: 'success', message, duration }),
- error: (message: string, duration?: number) => addToast({ type: 'error', message, duration }),
- info: (message: string, duration?: number) => addToast({ type: 'info', message, duration }),
- warning: (message: string, duration?: number) =>
- addToast({ type: 'warning', message, duration }),
- remove: removeToast,
- };
-}
-
-export const toasts = createToastStore();
diff --git a/apps/contacts/apps/web/src/routes/(app)/duplicates/+page.svelte b/apps/contacts/apps/web/src/routes/(app)/duplicates/+page.svelte
index eb4901f1b..88f90c783 100644
--- a/apps/contacts/apps/web/src/routes/(app)/duplicates/+page.svelte
+++ b/apps/contacts/apps/web/src/routes/(app)/duplicates/+page.svelte
@@ -4,7 +4,7 @@
import { duplicatesApi, type DuplicateGroup } from '$lib/api/duplicates';
import MergeModal from '$lib/components/duplicates/MergeModal.svelte';
import { DuplicateListSkeleton } from '$lib/components/skeletons';
- import { toasts } from '$lib/stores/toast';
+ import { toastStore } from '@manacore/shared-ui';
let duplicates = $state([]);
let loading = $state(true);
@@ -75,14 +75,14 @@
async function handleMerge(primaryId: string, mergeIds: string[]) {
try {
await duplicatesApi.mergeContacts(primaryId, mergeIds);
- toasts.success(`${mergeIds.length + 1} Kontakte wurden zusammengeführt`);
+ toastStore.success(`${mergeIds.length + 1} Kontakte wurden zusammengeführt`);
// Remove the merged group from the list
if (selectedGroup) {
duplicates = duplicates.filter((d) => d.id !== selectedGroup!.id);
}
handleCloseMergeModal();
} catch (e) {
- toasts.error(e instanceof Error ? e.message : 'Fehler beim Zusammenführen');
+ toastStore.error(e instanceof Error ? e.message : 'Fehler beim Zusammenführen');
}
}
@@ -91,10 +91,10 @@
try {
await duplicatesApi.dismissDuplicate(selectedGroup.id);
duplicates = duplicates.filter((d) => d.id !== selectedGroup!.id);
- toasts.info('Duplikat-Gruppe wurde ignoriert');
+ toastStore.info('Duplikat-Gruppe wurde ignoriert');
handleCloseMergeModal();
} catch (e) {
- toasts.error('Fehler beim Ignorieren');
+ toastStore.error('Fehler beim Ignorieren');
}
}
diff --git a/apps/contacts/apps/web/src/routes/(app)/mana/+page.svelte b/apps/contacts/apps/web/src/routes/(app)/mana/+page.svelte
index a3e5f7e91..b2c97a600 100644
--- a/apps/contacts/apps/web/src/routes/(app)/mana/+page.svelte
+++ b/apps/contacts/apps/web/src/routes/(app)/mana/+page.svelte
@@ -1,15 +1,15 @@
diff --git a/apps/contacts/apps/web/src/routes/+layout.svelte b/apps/contacts/apps/web/src/routes/+layout.svelte
index 27f8460a2..35cbef0d2 100644
--- a/apps/contacts/apps/web/src/routes/+layout.svelte
+++ b/apps/contacts/apps/web/src/routes/+layout.svelte
@@ -6,8 +6,7 @@
import { isLoading as i18nLoading } from 'svelte-i18n';
import { theme } from '$lib/stores/theme';
import { authStore } from '$lib/stores/auth.svelte';
- import { toasts } from '$lib/stores/toast';
- import ToastContainer from '$lib/components/ToastContainer.svelte';
+ import { toastStore, ToastContainer } from '@manacore/shared-ui';
import { AppLoadingSkeleton } from '$lib/components/skeletons';
let { children } = $props();
@@ -49,7 +48,7 @@
}
// Show toast notification
- toasts.error(message);
+ toastStore.error(message);
// Prevent default browser error handling
event.preventDefault();
@@ -59,17 +58,17 @@
window.addEventListener('error', (event) => {
// Only handle non-script errors (network failures for resources, etc.)
if (event.message && !event.filename) {
- toasts.error('Ein Fehler ist aufgetreten');
+ toastStore.error('Ein Fehler ist aufgetreten');
}
});
// Handle offline/online status
window.addEventListener('offline', () => {
- toasts.warning('Keine Internetverbindung', 10000);
+ toastStore.warning('Keine Internetverbindung', 10000);
});
window.addEventListener('online', () => {
- toasts.success('Verbindung wiederhergestellt');
+ toastStore.success('Verbindung wiederhergestellt');
});
}
diff --git a/apps/picture/apps/web/src/lib/components/board/ImagePickerModal.svelte b/apps/picture/apps/web/src/lib/components/board/ImagePickerModal.svelte
index 10738f5e6..187c29141 100644
--- a/apps/picture/apps/web/src/lib/components/board/ImagePickerModal.svelte
+++ b/apps/picture/apps/web/src/lib/components/board/ImagePickerModal.svelte
@@ -5,7 +5,7 @@
import { canvasItems, addCanvasItem } from '$lib/stores/canvas';
import { getImages } from '$lib/api/images';
import { addBoardItem } from '$lib/api/boardItems';
- import { showToast } from '$lib/stores/toast';
+ import { toastStore } from '@manacore/shared-ui';
import Modal from '$lib/components/ui/Modal.svelte';
import Button from '$lib/components/ui/Button.svelte';
import { MagnifyingGlass, Image as ImageIcon, Check } from '@manacore/shared-icons';
@@ -47,7 +47,7 @@
hasMore = data.length === 50;
} catch (error) {
console.error('Error loading images:', error);
- showToast('Fehler beim Laden der Bilder', 'error');
+ toastStore.show('Fehler beim Laden der Bilder', 'error');
} finally {
isLoadingImages.set(false);
}
@@ -104,11 +104,14 @@
selectedImages.clear();
selectedImages = new Set();
- showToast(`${addedCount} ${addedCount === 1 ? 'Bild' : 'Bilder'} hinzugefügt`, 'success');
+ toastStore.show(
+ `${addedCount} ${addedCount === 1 ? 'Bild' : 'Bilder'} hinzugefügt`,
+ 'success'
+ );
onClose();
} catch (error) {
console.error('Error adding images:', error);
- showToast('Fehler beim Hinzufügen', 'error');
+ toastStore.show('Fehler beim Hinzufügen', 'error');
} finally {
isAdding = false;
}
diff --git a/apps/picture/apps/web/src/lib/components/board/ImagePropertiesPanel.svelte b/apps/picture/apps/web/src/lib/components/board/ImagePropertiesPanel.svelte
index 4e6c8aeb3..f2e914ef5 100644
--- a/apps/picture/apps/web/src/lib/components/board/ImagePropertiesPanel.svelte
+++ b/apps/picture/apps/web/src/lib/components/board/ImagePropertiesPanel.svelte
@@ -2,7 +2,7 @@
import { selectedItems, updateCanvasItem, removeSelectedItems } from '$lib/stores/canvas';
import { updateBoardItem, changeBoardItemZIndex, isImageItem } from '$lib/api/boardItems';
import Button from '$lib/components/ui/Button.svelte';
- import { showToast } from '$lib/stores/toast';
+ import { toastStore } from '@manacore/shared-ui';
import {
Image,
CaretDoubleUp,
@@ -51,7 +51,7 @@
await updateBoardItem(selectedItem.id, updates);
} catch (error) {
console.error('Error updating position:', error);
- showToast('Fehler beim Speichern', 'error');
+ toastStore.show('Fehler beim Speichern', 'error');
}
}
@@ -75,7 +75,7 @@
await updateBoardItem(selectedItem.id, updates);
} catch (error) {
console.error('Error updating scale:', error);
- showToast('Fehler beim Speichern', 'error');
+ toastStore.show('Fehler beim Speichern', 'error');
}
}
@@ -89,7 +89,7 @@
await updateBoardItem(selectedItem.id, updates);
} catch (error) {
console.error('Error updating rotation:', error);
- showToast('Fehler beim Speichern', 'error');
+ toastStore.show('Fehler beim Speichern', 'error');
}
}
@@ -104,7 +104,7 @@
await updateBoardItem(selectedItem.id, updates);
} catch (error) {
console.error('Error updating opacity:', error);
- showToast('Fehler beim Speichern', 'error');
+ toastStore.show('Fehler beim Speichern', 'error');
}
}
@@ -113,10 +113,10 @@
try {
await changeBoardItemZIndex(selectedItem.id, direction);
- showToast('Layer-Reihenfolge geändert', 'success');
+ toastStore.show('Layer-Reihenfolge geändert', 'success');
} catch (error) {
console.error('Error changing layer:', error);
- showToast('Fehler beim Ändern der Layer-Reihenfolge', 'error');
+ toastStore.show('Fehler beim Ändern der Layer-Reihenfolge', 'error');
}
}
@@ -139,7 +139,7 @@
function handleDelete() {
removeSelectedItems();
- showToast('Bild entfernt', 'success');
+ toastStore.show('Bild entfernt', 'success');
}
diff --git a/apps/picture/apps/web/src/lib/components/gallery/ImageDetailModal.svelte b/apps/picture/apps/web/src/lib/components/gallery/ImageDetailModal.svelte
index fa188bab2..4c4a0ec93 100644
--- a/apps/picture/apps/web/src/lib/components/gallery/ImageDetailModal.svelte
+++ b/apps/picture/apps/web/src/lib/components/gallery/ImageDetailModal.svelte
@@ -9,7 +9,7 @@
unpublishImage,
} from '$lib/api/images';
import { images, selectedImage } from '$lib/stores/images';
- import { showToast } from '$lib/stores/toast';
+ import { toastStore } from '@manacore/shared-ui';
import { fade, fly } from 'svelte/transition';
import { getImageTags, getAllTags, addTagToImage, removeTagFromImage } from '$lib/api/tags';
import {
@@ -104,11 +104,11 @@
await archiveImage(imageId);
// Update store
images.update((current) => current.filter((img) => img.id !== imageId));
- showToast('Bild erfolgreich archiviert', 'success');
+ toastStore.show('Bild erfolgreich archiviert', 'success');
onClose();
} catch (error) {
console.error('Error archiving image:', error);
- showToast('Fehler beim Archivieren des Bildes', 'error');
+ toastStore.show('Fehler beim Archivieren des Bildes', 'error');
} finally {
isArchiving = false;
}
@@ -129,11 +129,11 @@
await deleteImage(imageId);
// Update store
images.update((current) => current.filter((img) => img.id !== imageId));
- showToast('Bild erfolgreich gelöscht', 'success');
+ toastStore.show('Bild erfolgreich gelöscht', 'success');
onClose();
} catch (error) {
console.error('Error deleting image:', error);
- showToast('Fehler beim Löschen des Bildes', 'error');
+ toastStore.show('Fehler beim Löschen des Bildes', 'error');
} finally {
isDeleting = false;
}
@@ -143,7 +143,7 @@
if (!image || !image.publicUrl) return;
const filename = `picture-${image.id}.png`;
downloadImage(image.publicUrl, filename);
- showToast('Download gestartet', 'success');
+ toastStore.show('Download gestartet', 'success');
}
function formatDate(dateString: string) {
@@ -164,7 +164,7 @@
allTags = await getAllTags();
} catch (error) {
console.error('Error loading tags:', error);
- showToast('Fehler beim Laden der Tags', 'error');
+ toastStore.show('Fehler beim Laden der Tags', 'error');
} finally {
isLoadingTags = false;
}
@@ -183,15 +183,15 @@
if (isTagged) {
await removeTagFromImage(image.id, tag.id);
imageTags = imageTags.filter((t) => t.id !== tag.id);
- showToast('Tag entfernt', 'success');
+ toastStore.show('Tag entfernt', 'success');
} else {
await addTagToImage(image.id, tag.id);
imageTags = [...imageTags, tag];
- showToast('Tag hinzugefügt', 'success');
+ toastStore.show('Tag hinzugefügt', 'success');
}
} catch (error) {
console.error('Error toggling tag:', error);
- showToast('Fehler beim Aktualisieren des Tags', 'error');
+ toastStore.show('Fehler beim Aktualisieren des Tags', 'error');
}
}
@@ -213,11 +213,11 @@
if (image) {
image = { ...image, isPublic: true };
}
- showToast('Bild erfolgreich veröffentlicht!', 'success');
+ toastStore.show('Bild erfolgreich veröffentlicht!', 'success');
closePublishModal();
} catch (error) {
console.error('Error publishing image:', error);
- showToast('Fehler beim Veröffentlichen des Bildes', 'error');
+ toastStore.show('Fehler beim Veröffentlichen des Bildes', 'error');
} finally {
isPublishing = false;
}
@@ -233,11 +233,11 @@
if (image) {
image = { ...image, isPublic: false };
}
- showToast('Bild nicht mehr öffentlich', 'success');
+ toastStore.show('Bild nicht mehr öffentlich', 'success');
closePublishModal();
} catch (error) {
console.error('Error unpublishing image:', error);
- showToast('Fehler beim Entfernen der Veröffentlichung', 'error');
+ toastStore.show('Fehler beim Entfernen der Veröffentlichung', 'error');
} finally {
isPublishing = false;
}
diff --git a/apps/picture/apps/web/src/lib/components/gallery/QuickGenerateBar.svelte b/apps/picture/apps/web/src/lib/components/gallery/QuickGenerateBar.svelte
index c79742684..ce51f215e 100644
--- a/apps/picture/apps/web/src/lib/components/gallery/QuickGenerateBar.svelte
+++ b/apps/picture/apps/web/src/lib/components/gallery/QuickGenerateBar.svelte
@@ -6,7 +6,7 @@
import { isSidebarCollapsed } from '$lib/stores/sidebar';
import { getActiveModels } from '$lib/api/models';
import { generateImageAsync, subscribeToGenerationUpdates } from '$lib/api/generate-async';
- import { showToast } from '$lib/stores/toast';
+ import { toastStore } from '@manacore/shared-ui';
import { onMount } from 'svelte';
import AdvancedSettingsModal, {
type AdvancedSettings,
@@ -59,7 +59,7 @@
}
} catch (error) {
console.error('Error loading models:', error);
- showToast('Fehler beim Laden der Modelle', 'error');
+ toastStore.show('Fehler beim Laden der Modelle', 'error');
} finally {
isLoadingModels.set(false);
}
@@ -124,7 +124,7 @@
// Success
generationProgress.set('Fertig!');
- showToast(
+ toastStore.show(
totalImages > 1
? `${totalImages} Bilder erfolgreich generiert!`
: 'Bild erfolgreich generiert!',
@@ -137,7 +137,7 @@
console.error('Generation error:', error);
const errorMessage = error instanceof Error ? error.message : 'Generierung fehlgeschlagen';
generationError.set(errorMessage);
- showToast(errorMessage, 'error');
+ toastStore.show(errorMessage, 'error');
} finally {
setTimeout(() => {
isGenerating.set(false);
diff --git a/apps/picture/apps/web/src/lib/components/ui/ContextMenu.svelte b/apps/picture/apps/web/src/lib/components/ui/ContextMenu.svelte
index 5064c92c9..3a4262789 100644
--- a/apps/picture/apps/web/src/lib/components/ui/ContextMenu.svelte
+++ b/apps/picture/apps/web/src/lib/components/ui/ContextMenu.svelte
@@ -12,7 +12,7 @@
import { archiveImage, unarchiveImage, deleteImage, toggleFavorite } from '$lib/api/images';
import { images } from '$lib/stores/images';
import { archivedImages } from '$lib/stores/archive';
- import { showToast } from '$lib/stores/toast';
+ import { toastStore } from '@manacore/shared-ui';
import type { Tag } from '$lib/api/tags';
import {
DownloadSimple,
@@ -76,10 +76,10 @@
try {
await addTagToImage($contextMenu.image.id, tag.id);
await loadImageTags($contextMenu.image.id);
- showToast(`Tag "${tag.name}" hinzugefügt`, 'success');
+ toastStore.show(`Tag "${tag.name}" hinzugefügt`, 'success');
} catch (error) {
console.error('Error adding tag:', error);
- showToast('Fehler beim Hinzufügen des Tags', 'error');
+ toastStore.show('Fehler beim Hinzufügen des Tags', 'error');
}
}
@@ -89,10 +89,10 @@
try {
await removeTagFromImage($contextMenu.image.id, tag.id);
await loadImageTags($contextMenu.image.id);
- showToast(`Tag "${tag.name}" entfernt`, 'success');
+ toastStore.show(`Tag "${tag.name}" entfernt`, 'success');
} catch (error) {
console.error('Error removing tag:', error);
- showToast('Fehler beim Entfernen des Tags', 'error');
+ toastStore.show('Fehler beim Entfernen des Tags', 'error');
}
}
@@ -104,7 +104,7 @@
link.download = $contextMenu.image.filename || 'image.png';
link.click();
hideContextMenu();
- showToast('Download gestartet', 'success');
+ toastStore.show('Download gestartet', 'success');
}
function handleCopyLink() {
@@ -112,7 +112,7 @@
navigator.clipboard.writeText($contextMenu.image.publicUrl);
hideContextMenu();
- showToast('Link kopiert', 'success');
+ toastStore.show('Link kopiert', 'success');
}
async function handleDelete() {
@@ -124,10 +124,10 @@
// Remove from store
images.update((current) => current.filter((img) => img.id !== $contextMenu.image?.id));
hideContextMenu();
- showToast('Bild gelöscht', 'success');
+ toastStore.show('Bild gelöscht', 'success');
} catch (error) {
console.error('Error deleting image:', error);
- showToast('Fehler beim Löschen des Bildes', 'error');
+ toastStore.show('Fehler beim Löschen des Bildes', 'error');
}
}
}
@@ -144,18 +144,18 @@
current.filter((img) => img.id !== $contextMenu.image?.id)
);
hideContextMenu();
- showToast('Bild wiederhergestellt', 'success');
+ toastStore.show('Bild wiederhergestellt', 'success');
} else {
// Archive: Move to archive
await archiveImage($contextMenu.image.id);
// Remove from gallery store
images.update((current) => current.filter((img) => img.id !== $contextMenu.image?.id));
hideContextMenu();
- showToast('Bild archiviert', 'success');
+ toastStore.show('Bild archiviert', 'success');
}
} catch (error) {
console.error('Error archiving/unarchiving image:', error);
- showToast('Fehler beim Archivieren des Bildes', 'error');
+ toastStore.show('Fehler beim Archivieren des Bildes', 'error');
}
}
@@ -179,13 +179,13 @@
);
hideContextMenu();
- showToast(
+ toastStore.show(
newFavoriteStatus ? 'Zu Favoriten hinzugefügt' : 'Aus Favoriten entfernt',
'success'
);
} catch (error) {
console.error('Error toggling favorite:', error);
- showToast('Fehler beim Aktualisieren der Favoriten', 'error');
+ toastStore.show('Fehler beim Aktualisieren der Favoriten', 'error');
}
}
diff --git a/apps/picture/apps/web/src/lib/components/ui/Toast.svelte b/apps/picture/apps/web/src/lib/components/ui/Toast.svelte
deleted file mode 100644
index 9ee618233..000000000
--- a/apps/picture/apps/web/src/lib/components/ui/Toast.svelte
+++ /dev/null
@@ -1,53 +0,0 @@
-
-
-
- {#each $toasts as toast (toast.id)}
- {@const bgColor = getToastBgColor(toast.type)}
-
-
- {#if toast.type === 'success'}
-
- {:else if toast.type === 'error'}
-
- {:else if toast.type === 'warning'}
-
- {:else}
-
- {/if}
-
-
-
{toast.message}
-
-
-
- {/each}
-
diff --git a/apps/picture/apps/web/src/lib/stores/toast.svelte.ts b/apps/picture/apps/web/src/lib/stores/toast.svelte.ts
deleted file mode 100644
index 02382f895..000000000
--- a/apps/picture/apps/web/src/lib/stores/toast.svelte.ts
+++ /dev/null
@@ -1,80 +0,0 @@
-/**
- * Toast Store - Svelte 5 Runes Version
- */
-
-export type ToastType = 'success' | 'error' | 'info' | 'warning';
-
-export interface Toast {
- id: string;
- message: string;
- type: ToastType;
- duration?: number;
-}
-
-let toasts = $state([]);
-let toastId = 0;
-
-export const toastStore = {
- get toasts() {
- return toasts;
- },
-
- show(message: string, type: ToastType = 'info', duration = 5000): string {
- const id = `toast-${toastId++}`;
- const toast: Toast = { id, message, type, duration };
-
- toasts = [...toasts, toast];
-
- if (duration > 0) {
- setTimeout(() => {
- toastStore.dismiss(id);
- }, duration);
- }
-
- return id;
- },
-
- dismiss(id: string) {
- toasts = toasts.filter((toast) => toast.id !== id);
- },
-
- clear() {
- toasts = [];
- },
-
- success(message: string, duration = 5000) {
- return toastStore.show(message, 'success', duration);
- },
-
- error(message: string, duration = 5000) {
- return toastStore.show(message, 'error', duration);
- },
-
- warning(message: string, duration = 5000) {
- return toastStore.show(message, 'warning', duration);
- },
-
- info(message: string, duration = 5000) {
- return toastStore.show(message, 'info', duration);
- },
-};
-
-// Export for backwards compatibility
-export function showToast(message: string, type: ToastType = 'info', duration = 5000) {
- return toastStore.show(message, type, duration);
-}
-
-export function dismissToast(id: string) {
- toastStore.dismiss(id);
-}
-
-export function clearToasts() {
- toastStore.clear();
-}
-
-export function getToasts() {
- return toasts;
-}
-
-// Re-export for compatibility
-export { toasts };
diff --git a/apps/picture/apps/web/src/lib/stores/toast.ts b/apps/picture/apps/web/src/lib/stores/toast.ts
deleted file mode 100644
index daffc4f28..000000000
--- a/apps/picture/apps/web/src/lib/stores/toast.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-import { writable } from 'svelte/store';
-
-export type ToastType = 'success' | 'error' | 'info' | 'warning';
-
-export interface Toast {
- id: string;
- message: string;
- type: ToastType;
- duration?: number;
-}
-
-export const toasts = writable([]);
-
-let toastId = 0;
-
-export function showToast(message: string, type: ToastType = 'info', duration = 5000) {
- const id = `toast-${toastId++}`;
- const toast: Toast = { id, message, type, duration };
-
- toasts.update((current) => [...current, toast]);
-
- if (duration > 0) {
- setTimeout(() => {
- dismissToast(id);
- }, duration);
- }
-
- return id;
-}
-
-export function dismissToast(id: string) {
- toasts.update((current) => current.filter((toast) => toast.id !== id));
-}
-
-export function clearToasts() {
- toasts.set([]);
-}
diff --git a/apps/picture/apps/web/src/routes/+layout.svelte b/apps/picture/apps/web/src/routes/+layout.svelte
index 65f8926c9..10de2e8ec 100644
--- a/apps/picture/apps/web/src/routes/+layout.svelte
+++ b/apps/picture/apps/web/src/routes/+layout.svelte
@@ -2,7 +2,7 @@
import '../app.css';
import favicon from '$lib/assets/favicon.svg';
import { authStore } from '$lib/stores/auth.svelte';
- import Toast from '$lib/components/ui/Toast.svelte';
+ import { ToastContainer } from '@manacore/shared-ui';
import { onMount } from 'svelte';
// Import and initialize theme
@@ -43,4 +43,4 @@
{@render children?.()}
-
+
diff --git a/apps/picture/apps/web/src/routes/app/board/+page.svelte b/apps/picture/apps/web/src/routes/app/board/+page.svelte
index c87e06b43..3907416bf 100644
--- a/apps/picture/apps/web/src/routes/app/board/+page.svelte
+++ b/apps/picture/apps/web/src/routes/app/board/+page.svelte
@@ -17,7 +17,7 @@
import { PageHeader } from '@manacore/shared-ui';
import Button from '$lib/components/ui/Button.svelte';
import Modal from '$lib/components/ui/Modal.svelte';
- import { showToast } from '$lib/stores/toast';
+ import { toastStore } from '@manacore/shared-ui';
import { Plus, SquaresFour, Image, Trash } from '@manacore/shared-icons';
let loadingMore = $state(false);
@@ -70,7 +70,7 @@
hasBoardsMore.set(data.length === 20);
} catch (error) {
console.error('Error loading boards:', error);
- showToast('Fehler beim Laden der Boards', 'error');
+ toastStore.show('Fehler beim Laden der Boards', 'error');
} finally {
isLoadingBoards.set(false);
}
@@ -112,10 +112,10 @@
showCreateBoardModal.set(false);
boardName = '';
boardDescription = '';
- showToast('Board erstellt', 'success');
+ toastStore.show('Board erstellt', 'success');
} catch (error) {
console.error('Error creating board:', error);
- showToast('Fehler beim Erstellen', 'error');
+ toastStore.show('Fehler beim Erstellen', 'error');
} finally {
isCreating = false;
}
@@ -129,10 +129,10 @@
removeBoardFromList(deletingBoard);
showDeleteModal = false;
deletingBoard = null;
- showToast('Board gelöscht', 'success');
+ toastStore.show('Board gelöscht', 'success');
} catch (error) {
console.error('Error deleting board:', error);
- showToast('Fehler beim Löschen', 'error');
+ toastStore.show('Fehler beim Löschen', 'error');
}
}
@@ -142,10 +142,10 @@
try {
const newBoard = await duplicateBoard(boardId);
addBoard({ ...newBoard, itemCount: 0 });
- showToast('Board dupliziert', 'success');
+ toastStore.show('Board dupliziert', 'success');
} catch (error) {
console.error('Error duplicating board:', error);
- showToast('Fehler beim Duplizieren', 'error');
+ toastStore.show('Fehler beim Duplizieren', 'error');
}
}
diff --git a/apps/picture/apps/web/src/routes/app/board/[id]/+page.svelte b/apps/picture/apps/web/src/routes/app/board/[id]/+page.svelte
index 7ca6d069d..84139ff79 100644
--- a/apps/picture/apps/web/src/routes/app/board/[id]/+page.svelte
+++ b/apps/picture/apps/web/src/routes/app/board/[id]/+page.svelte
@@ -14,7 +14,7 @@
} from '$lib/stores/canvas';
import { getBoardById } from '$lib/api/boards';
import { getBoardItems, addTextToBoard } from '$lib/api/boardItems';
- import { showToast } from '$lib/stores/toast';
+ import { toastStore } from '@manacore/shared-ui';
import BoardCanvas from '$lib/components/board/BoardCanvas.svelte';
import CanvasToolbar from '$lib/components/board/CanvasToolbar.svelte';
import ImagePickerModal from '$lib/components/board/ImagePickerModal.svelte';
@@ -50,7 +50,7 @@
// Check if user has access
if (board.userId !== authStore.user.id && !board.isPublic) {
- showToast('Zugriff verweigert', 'error');
+ toastStore.show('Zugriff verweigert', 'error');
goto('/app/board');
return;
}
@@ -63,7 +63,7 @@
canvasItems.set(items);
} catch (error) {
console.error('Error loading board:', error);
- showToast('Fehler beim Laden des Boards', 'error');
+ toastStore.show('Fehler beim Laden des Boards', 'error');
goto('/app/board');
} finally {
isLoading = false;
@@ -88,10 +88,10 @@
// Add to canvas
addCanvasItem(text);
- showToast('Text hinzugefügt', 'success');
+ toastStore.show('Text hinzugefügt', 'success');
} catch (error) {
console.error('Error adding text:', error);
- showToast('Fehler beim Hinzufügen des Textes', 'error');
+ toastStore.show('Fehler beim Hinzufügen des Textes', 'error');
}
}
diff --git a/apps/picture/apps/web/src/routes/app/mana/+page.svelte b/apps/picture/apps/web/src/routes/app/mana/+page.svelte
index c47bf3433..4c20c9ab7 100644
--- a/apps/picture/apps/web/src/routes/app/mana/+page.svelte
+++ b/apps/picture/apps/web/src/routes/app/mana/+page.svelte
@@ -1,15 +1,19 @@
diff --git a/apps/picture/apps/web/src/routes/app/tags/+page.svelte b/apps/picture/apps/web/src/routes/app/tags/+page.svelte
index 63a55e58c..9c4d0e398 100644
--- a/apps/picture/apps/web/src/routes/app/tags/+page.svelte
+++ b/apps/picture/apps/web/src/routes/app/tags/+page.svelte
@@ -3,7 +3,7 @@
import { tags, isLoadingTags } from '$lib/stores/tags';
import { getAllTags, createTag, updateTag, deleteTag } from '$lib/api/tags';
import type { Tag } from '$lib/api/tags';
- import { showToast } from '$lib/stores/toast';
+ import { toastStore } from '@manacore/shared-ui';
import { PageHeader } from '@manacore/shared-ui';
import { Plus, Tag as TagIcon, PencilSimple, Trash } from '@manacore/shared-icons';
@@ -37,7 +37,7 @@
tags.set(data);
} catch (error) {
console.error('Error loading tags:', error);
- showToast('Fehler beim Laden der Tags', 'error');
+ toastStore.show('Fehler beim Laden der Tags', 'error');
} finally {
isLoadingTags.set(false);
}
@@ -52,13 +52,13 @@
color: newTagColor,
});
await loadTags();
- showToast('Tag erfolgreich erstellt', 'success');
+ toastStore.show('Tag erfolgreich erstellt', 'success');
newTagName = '';
newTagColor = '#3B82F6';
showCreateModal = false;
} catch (error) {
console.error('Error creating tag:', error);
- showToast('Fehler beim Erstellen des Tags', 'error');
+ toastStore.show('Fehler beim Erstellen des Tags', 'error');
}
}
@@ -78,12 +78,12 @@
color: editTagColor,
});
await loadTags();
- showToast('Tag erfolgreich aktualisiert', 'success');
+ toastStore.show('Tag erfolgreich aktualisiert', 'success');
showEditModal = false;
editingTag = null;
} catch (error) {
console.error('Error updating tag:', error);
- showToast('Fehler beim Aktualisieren des Tags', 'error');
+ toastStore.show('Fehler beim Aktualisieren des Tags', 'error');
}
}
@@ -93,10 +93,10 @@
try {
await deleteTag(tagId);
await loadTags();
- showToast('Tag erfolgreich gelöscht', 'success');
+ toastStore.show('Tag erfolgreich gelöscht', 'success');
} catch (error) {
console.error('Error deleting tag:', error);
- showToast('Fehler beim Löschen des Tags', 'error');
+ toastStore.show('Fehler beim Löschen des Tags', 'error');
}
}
diff --git a/apps/picture/apps/web/src/routes/app/upload/+page.svelte b/apps/picture/apps/web/src/routes/app/upload/+page.svelte
index 7775d878d..1b92ef97b 100644
--- a/apps/picture/apps/web/src/routes/app/upload/+page.svelte
+++ b/apps/picture/apps/web/src/routes/app/upload/+page.svelte
@@ -3,7 +3,7 @@
import { goto } from '$app/navigation';
import { uploadMultipleImages } from '$lib/api/upload';
import type { UploadProgress } from '$lib/api/upload';
- import { showToast } from '$lib/stores/toast';
+ import { toastStore } from '@manacore/shared-ui';
import { PageHeader } from '@manacore/shared-ui';
import DropZone from '$lib/components/upload/DropZone.svelte';
import { images } from '$lib/stores/images';
@@ -15,7 +15,7 @@
async function handleFilesSelected(files: File[]) {
if (!authStore.user) {
- showToast('Bitte melde dich an', 'error');
+ toastStore.show('Bitte melde dich an', 'error');
return;
}
@@ -33,12 +33,15 @@
images.update((current) => [...uploadedImages, ...current]);
if (successCount === files.length) {
- showToast(
+ toastStore.show(
`${successCount} ${successCount === 1 ? 'Bild' : 'Bilder'} erfolgreich hochgeladen`,
'success'
);
} else {
- showToast(`${successCount} von ${files.length} Bildern erfolgreich hochgeladen`, 'warning');
+ toastStore.show(
+ `${successCount} von ${files.length} Bildern erfolgreich hochgeladen`,
+ 'warning'
+ );
}
// Redirect to gallery after successful upload
@@ -47,7 +50,7 @@
}, 2000);
} catch (error) {
console.error('Upload error:', error);
- showToast('Fehler beim Hochladen der Bilder', 'error');
+ toastStore.show('Fehler beim Hochladen der Bilder', 'error');
} finally {
uploading = false;
}
diff --git a/apps/storage/apps/web/src/lib/components/ToastContainer.svelte b/apps/storage/apps/web/src/lib/components/ToastContainer.svelte
deleted file mode 100644
index 3019a2990..000000000
--- a/apps/storage/apps/web/src/lib/components/ToastContainer.svelte
+++ /dev/null
@@ -1,188 +0,0 @@
-
-
-
- {#each toasts as toastItem (toastItem.id)}
-
-
- {@html getIcon(toastItem.type)}
-
-
{toastItem.message}
-
-
- {/each}
-
-
-
diff --git a/apps/storage/apps/web/src/lib/stores/toast.ts b/apps/storage/apps/web/src/lib/stores/toast.ts
deleted file mode 100644
index c7f1bd9c8..000000000
--- a/apps/storage/apps/web/src/lib/stores/toast.ts
+++ /dev/null
@@ -1,63 +0,0 @@
-/**
- * Toast Store - Manages toast notifications
- */
-
-import { writable } from 'svelte/store';
-
-export interface Toast {
- id: string;
- type: 'success' | 'error' | 'warning' | 'info';
- message: string;
- duration?: number;
-}
-
-function createToastStore() {
- const { subscribe, update } = writable([]);
-
- function add(toast: Omit) {
- const id = crypto.randomUUID();
- const duration = toast.duration ?? 5000;
-
- update((toasts) => [...toasts, { ...toast, id }]);
-
- if (duration > 0) {
- setTimeout(() => {
- remove(id);
- }, duration);
- }
-
- return id;
- }
-
- function remove(id: string) {
- update((toasts) => toasts.filter((t) => t.id !== id));
- }
-
- function success(message: string, duration?: number) {
- return add({ type: 'success', message, duration });
- }
-
- function error(message: string, duration?: number) {
- return add({ type: 'error', message, duration });
- }
-
- function warning(message: string, duration?: number) {
- return add({ type: 'warning', message, duration });
- }
-
- function info(message: string, duration?: number) {
- return add({ type: 'info', message, duration });
- }
-
- return {
- subscribe,
- add,
- remove,
- success,
- error,
- warning,
- info,
- };
-}
-
-export const toast = createToastStore();
diff --git a/apps/storage/apps/web/src/routes/+layout.svelte b/apps/storage/apps/web/src/routes/+layout.svelte
index 137cd9893..96addf775 100644
--- a/apps/storage/apps/web/src/routes/+layout.svelte
+++ b/apps/storage/apps/web/src/routes/+layout.svelte
@@ -16,7 +16,7 @@
import { getLanguageDropdownItems, getCurrentLanguageLabel } from '@manacore/shared-i18n';
import { getPillAppItems } from '@manacore/shared-branding';
import { setLocale, supportedLocales } from '$lib/i18n';
- import ToastContainer from '$lib/components/ToastContainer.svelte';
+ import { ToastContainer } from '@manacore/shared-ui';
import '../app.css';
// App switcher items
diff --git a/apps/storage/apps/web/src/routes/favorites/+page.svelte b/apps/storage/apps/web/src/routes/favorites/+page.svelte
index e08743c8c..15e2588cf 100644
--- a/apps/storage/apps/web/src/routes/favorites/+page.svelte
+++ b/apps/storage/apps/web/src/routes/favorites/+page.svelte
@@ -5,7 +5,7 @@
import { searchApi } from '$lib/api/client';
import type { StorageFile, StorageFolder } from '$lib/api/client';
import { filesStore } from '$lib/stores/files.svelte';
- import { toast } from '$lib/stores/toast';
+ import { toastStore } from '@manacore/shared-ui';
import FileGrid from '$lib/components/files/FileGrid.svelte';
import FileList from '$lib/components/files/FileList.svelte';
@@ -47,7 +47,7 @@
const result = await filesStore.toggleFileFavorite(file.id);
if (!result.error) {
files = files.filter((f) => f.id !== file.id);
- toast.success('Favorit entfernt');
+ toastStore.success('Favorit entfernt');
}
}
}
@@ -57,7 +57,7 @@
const result = await filesStore.toggleFolderFavorite(folder.id);
if (!result.error) {
folders = folders.filter((f) => f.id !== folder.id);
- toast.success('Favorit entfernt');
+ toastStore.success('Favorit entfernt');
}
}
}
diff --git a/apps/storage/apps/web/src/routes/feedback/+page.svelte b/apps/storage/apps/web/src/routes/feedback/+page.svelte
index 7346bb0be..379cf58d8 100644
--- a/apps/storage/apps/web/src/routes/feedback/+page.svelte
+++ b/apps/storage/apps/web/src/routes/feedback/+page.svelte
@@ -1,6 +1,6 @@
+
+{#if toasts.length > 0}
+
+ {#each toasts as toast (toast.id)}
+ {@const Icon = icons[toast.type]}
+
+
+
{toast.message}
+
+
+ {/each}
+
+{/if}
+
+
diff --git a/packages/shared-ui/src/toast/index.ts b/packages/shared-ui/src/toast/index.ts
new file mode 100644
index 000000000..67db8c244
--- /dev/null
+++ b/packages/shared-ui/src/toast/index.ts
@@ -0,0 +1,3 @@
+export { toastStore, toast, handleApiError } from './toast.svelte';
+export type { Toast, ToastType } from './toast.svelte';
+export { default as ToastContainer } from './ToastContainer.svelte';
diff --git a/packages/shared-ui/src/toast/toast.svelte.ts b/packages/shared-ui/src/toast/toast.svelte.ts
new file mode 100644
index 000000000..d02e60ba2
--- /dev/null
+++ b/packages/shared-ui/src/toast/toast.svelte.ts
@@ -0,0 +1,146 @@
+/**
+ * Toast Store - Centralized notification system using Svelte 5 runes
+ *
+ * Usage:
+ * ```ts
+ * import { toastStore } from '@manacore/shared-ui';
+ *
+ * // Show notifications
+ * toastStore.success('Saved successfully');
+ * toastStore.error('Something went wrong');
+ * toastStore.warning('Please check your input');
+ * toastStore.info('New update available');
+ *
+ * // Manual control
+ * const id = toastStore.show('Custom message', 'info', 5000);
+ * toastStore.dismiss(id);
+ * toastStore.dismissAll();
+ * ```
+ */
+
+export type ToastType = 'success' | 'error' | 'warning' | 'info';
+
+export interface Toast {
+ id: string;
+ type: ToastType;
+ message: string;
+ duration: number;
+}
+
+// State
+let toasts = $state([]);
+
+// Auto-incrementing ID with timestamp for uniqueness
+let nextId = 0;
+
+function generateId(): string {
+ return `toast-${++nextId}-${Date.now()}`;
+}
+
+export const toastStore = {
+ /**
+ * Get all active toasts (reactive)
+ */
+ get toasts() {
+ return toasts;
+ },
+
+ /**
+ * Show a toast notification
+ * @param message - The message to display
+ * @param type - Toast type: 'success' | 'error' | 'warning' | 'info'
+ * @param duration - Duration in ms (0 = permanent, default: 4000)
+ * @returns The toast ID for manual dismissal
+ */
+ show(message: string, type: ToastType = 'info', duration = 4000): string {
+ const id = generateId();
+ const toast: Toast = { id, type, message, duration };
+
+ toasts = [...toasts, toast];
+
+ // Auto-remove after duration (unless permanent)
+ if (duration > 0) {
+ setTimeout(() => {
+ this.dismiss(id);
+ }, duration);
+ }
+
+ return id;
+ },
+
+ /**
+ * Show a success toast (green)
+ * @param message - The message to display
+ * @param duration - Duration in ms (default: 4000)
+ */
+ success(message: string, duration?: number): string {
+ return this.show(message, 'success', duration);
+ },
+
+ /**
+ * Show an error toast (red) - longer default duration
+ * @param message - The message to display
+ * @param duration - Duration in ms (default: 6000)
+ */
+ error(message: string, duration = 6000): string {
+ return this.show(message, 'error', duration);
+ },
+
+ /**
+ * Show a warning toast (amber)
+ * @param message - The message to display
+ * @param duration - Duration in ms (default: 4000)
+ */
+ warning(message: string, duration?: number): string {
+ return this.show(message, 'warning', duration);
+ },
+
+ /**
+ * Show an info toast (blue)
+ * @param message - The message to display
+ * @param duration - Duration in ms (default: 4000)
+ */
+ info(message: string, duration?: number): string {
+ return this.show(message, 'info', duration);
+ },
+
+ /**
+ * Dismiss a specific toast by ID
+ * @param id - The toast ID to dismiss
+ */
+ dismiss(id: string): void {
+ toasts = toasts.filter((t) => t.id !== id);
+ },
+
+ /**
+ * Dismiss all active toasts
+ */
+ dismissAll(): void {
+ toasts = [];
+ },
+};
+
+/**
+ * Helper function for API error handling
+ * Shows an error toast and returns the error message
+ *
+ * @example
+ * ```ts
+ * try {
+ * await api.save(data);
+ * } catch (error) {
+ * handleApiError(error, 'Could not save data');
+ * }
+ * ```
+ */
+export function handleApiError(
+ error: unknown,
+ fallbackMessage = 'Ein Fehler ist aufgetreten'
+): string {
+ const message = error instanceof Error ? error.message : fallbackMessage;
+ toastStore.error(message);
+ return message;
+}
+
+// Backwards compatible alias
+export const toast = toastStore;