mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 18:41:08 +02:00
feat(chat): add toast notification system and docker AI config
- 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 <noreply@anthropic.com>
This commit is contained in:
parent
4f06301fe3
commit
0893ed7daa
5 changed files with 175 additions and 2 deletions
63
apps/chat/apps/web/src/lib/components/Toast.svelte
Normal file
63
apps/chat/apps/web/src/lib/components/Toast.svelte
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
<script lang="ts">
|
||||
import { toastStore, type Toast } from '$lib/stores/toast.svelte';
|
||||
import { X, CheckCircle, XCircle, Warning, Info } from '@manacore/shared-icons';
|
||||
|
||||
let toasts = $derived(toastStore.toasts);
|
||||
|
||||
const icons = {
|
||||
success: CheckCircle,
|
||||
error: XCircle,
|
||||
warning: Warning,
|
||||
info: Info,
|
||||
};
|
||||
|
||||
const colors = {
|
||||
success: 'bg-green-500/90 text-white',
|
||||
error: 'bg-destructive/90 text-destructive-foreground',
|
||||
warning: 'bg-amber-500/90 text-white',
|
||||
info: 'bg-primary/90 text-primary-foreground',
|
||||
};
|
||||
|
||||
function handleDismiss(id: string) {
|
||||
toastStore.dismiss(id);
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if toasts.length > 0}
|
||||
<div class="fixed bottom-6 right-6 z-[100] flex flex-col gap-3 max-w-sm">
|
||||
{#each toasts as toast (toast.id)}
|
||||
{@const Icon = icons[toast.type]}
|
||||
<div
|
||||
class="flex items-start gap-3 px-4 py-3 rounded-xl shadow-lg backdrop-blur-xl border border-white/20 animate-slide-in {colors[toast.type]}"
|
||||
role="alert"
|
||||
>
|
||||
<Icon size={20} weight="fill" class="flex-shrink-0 mt-0.5" />
|
||||
<p class="flex-1 text-sm font-medium">{toast.message}</p>
|
||||
<button
|
||||
onclick={() => handleDismiss(toast.id)}
|
||||
class="flex-shrink-0 p-1 rounded-lg hover:bg-white/20 transition-colors"
|
||||
aria-label="Schließen"
|
||||
>
|
||||
<X size={16} weight="bold" />
|
||||
</button>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
@keyframes slide-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateX(100%);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
.animate-slide-in {
|
||||
animation: slide-in 0.3s ease-out;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
100
apps/chat/apps/web/src/lib/stores/toast.svelte.ts
Normal file
100
apps/chat/apps/web/src/lib/stores/toast.svelte.ts
Normal file
|
|
@ -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<Toast[]>([]);
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
|
@ -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 @@
|
|||
<div class="min-h-screen bg-background text-foreground">
|
||||
{@render children()}
|
||||
</div>
|
||||
|
||||
<!-- Global Toast notifications -->
|
||||
<Toast />
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue