From 0893ed7daa2673c12ca3c2b8e0e0ec862def181d Mon Sep 17 00:00:00 2001 From: Till-JS <101404291+Till-JS@users.noreply.github.com> Date: Sat, 29 Nov 2025 23:02:52 +0100 Subject: [PATCH] feat(chat): add toast notification system and docker AI config MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add global Toast component with success/error/warning/info types - Create toastStore using Svelte 5 runes for centralized notifications - Add GOOGLE_GENAI_API_KEY to mana-core-auth docker-compose config 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../apps/web/src/lib/components/Toast.svelte | 63 +++++++++++ .../src/lib/stores/conversations.svelte.ts | 9 +- .../apps/web/src/lib/stores/toast.svelte.ts | 100 ++++++++++++++++++ apps/chat/apps/web/src/routes/+layout.svelte | 4 + docker-compose.dev.yml | 1 + 5 files changed, 175 insertions(+), 2 deletions(-) create mode 100644 apps/chat/apps/web/src/lib/components/Toast.svelte create mode 100644 apps/chat/apps/web/src/lib/stores/toast.svelte.ts diff --git a/apps/chat/apps/web/src/lib/components/Toast.svelte b/apps/chat/apps/web/src/lib/components/Toast.svelte new file mode 100644 index 000000000..af43a277e --- /dev/null +++ b/apps/chat/apps/web/src/lib/components/Toast.svelte @@ -0,0 +1,63 @@ + + +{#if toasts.length > 0} +
+ {#each toasts as toast (toast.id)} + {@const Icon = icons[toast.type]} + + {/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 1fb0cf9a4..912f8676b 100644 --- a/apps/chat/apps/web/src/lib/stores/conversations.svelte.ts +++ b/apps/chat/apps/web/src/lib/stores/conversations.svelte.ts @@ -3,6 +3,7 @@ */ import { conversationService } from '$lib/services/conversation'; +import { toastStore } from './toast.svelte'; import type { Conversation } from '@chat/types'; // State @@ -36,7 +37,9 @@ export const conversationsStore = { try { conversations = await conversationService.getConversations(spaceId); } catch (e) { - error = e instanceof Error ? e.message : 'Failed to load conversations'; + const message = e instanceof Error ? e.message : 'Konversationen konnten nicht geladen werden'; + error = message; + toastStore.error(message); conversations = []; } finally { isLoading = false; @@ -53,7 +56,9 @@ export const conversationsStore = { try { archivedConversations = await conversationService.getArchivedConversations(); } catch (e) { - error = e instanceof Error ? e.message : 'Failed to load archived conversations'; + const message = e instanceof Error ? e.message : 'Archivierte Konversationen konnten nicht geladen werden'; + error = message; + toastStore.error(message); archivedConversations = []; } finally { isLoading = false; diff --git a/apps/chat/apps/web/src/lib/stores/toast.svelte.ts b/apps/chat/apps/web/src/lib/stores/toast.svelte.ts new file mode 100644 index 000000000..af686ceab --- /dev/null +++ b/apps/chat/apps/web/src/lib/stores/toast.svelte.ts @@ -0,0 +1,100 @@ +/** + * 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/+layout.svelte b/apps/chat/apps/web/src/routes/+layout.svelte index 6123a0073..cb7ce45ce 100644 --- a/apps/chat/apps/web/src/routes/+layout.svelte +++ b/apps/chat/apps/web/src/routes/+layout.svelte @@ -2,6 +2,7 @@ import '../app.css'; import { onMount } from 'svelte'; import { theme } from '$lib/stores/theme'; + import Toast from '$lib/components/Toast.svelte'; let { children } = $props(); @@ -14,3 +15,6 @@
{@render children()}
+ + + diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index f3adfed32..606309043 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -68,6 +68,7 @@ services: CORS_ORIGINS: ${CORS_ORIGINS:-http://localhost:3000,http://localhost:5173,http://localhost:8081} CREDITS_SIGNUP_BONUS: ${CREDITS_SIGNUP_BONUS:-150} CREDITS_DAILY_FREE: ${CREDITS_DAILY_FREE:-5} + GOOGLE_GENAI_API_KEY: ${GOOGLE_GENAI_API_KEY} depends_on: postgres: condition: service_healthy