From 61ccf63ac0fe93eddadda227f71e202d9f022b46 Mon Sep 17 00:00:00 2001 From: borja Date: Mon, 13 Oct 2025 22:40:05 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20a=C3=B1adir=20UI=20de=20integraciones?= =?UTF-8?q?=20ICS=20con=20FeedCard=20y=20toasts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: aider (openrouter/openai/gpt-5) --- apps/web/src/lib/stores/toasts.ts | 41 +++++++++++ apps/web/src/lib/ui/data/FeedCard.svelte | 73 +++++++++++++++++++ .../web/src/lib/ui/feedback/EmptyState.svelte | 13 ++++ .../src/lib/ui/feedback/ErrorBanner.svelte | 16 ++++ apps/web/src/lib/ui/feedback/Toast.svelte | 59 +++++++++++++++ apps/web/src/lib/ui/layout/AppShell.svelte | 3 + apps/web/src/lib/utils/copy.ts | 22 ++++++ .../src/routes/app/integrations/+page.svelte | 36 +++++++++ .../src/routes/app/preferences/+page.svelte | 2 + 9 files changed, 265 insertions(+) create mode 100644 apps/web/src/lib/stores/toasts.ts create mode 100644 apps/web/src/lib/ui/data/FeedCard.svelte create mode 100644 apps/web/src/lib/ui/feedback/EmptyState.svelte create mode 100644 apps/web/src/lib/ui/feedback/ErrorBanner.svelte create mode 100644 apps/web/src/lib/ui/feedback/Toast.svelte create mode 100644 apps/web/src/lib/utils/copy.ts create mode 100644 apps/web/src/routes/app/integrations/+page.svelte diff --git a/apps/web/src/lib/stores/toasts.ts b/apps/web/src/lib/stores/toasts.ts new file mode 100644 index 0000000..0e8d06b --- /dev/null +++ b/apps/web/src/lib/stores/toasts.ts @@ -0,0 +1,41 @@ +import { writable } from 'svelte/store'; + +export type ToastType = 'info' | 'success' | 'error'; + +export type ToastItem = { + id: string; + type: ToastType; + message: string; + timeout?: number; +}; + +export const toasts = writable([]); + +function uid(): string { + return Math.random().toString(36).slice(2) + Date.now().toString(36); +} + +export function show(message: string, type: ToastType = 'info', timeout = 2500): string { + const id = uid(); + toasts.update((list) => [...list, { id, type, message, timeout }]); + if (timeout > 0) { + setTimeout(() => dismiss(id), timeout); + } + return id; +} + +export function success(message: string, timeout = 2500): string { + return show(message, 'success', timeout); +} + +export function error(message: string, timeout = 3500): string { + return show(message, 'error', timeout); +} + +export function info(message: string, timeout = 2500): string { + return show(message, 'info', timeout); +} + +export function dismiss(id: string): void { + toasts.update((list) => list.filter((t) => t.id !== id)); +} diff --git a/apps/web/src/lib/ui/data/FeedCard.svelte b/apps/web/src/lib/ui/data/FeedCard.svelte new file mode 100644 index 0000000..7d22c10 --- /dev/null +++ b/apps/web/src/lib/ui/data/FeedCard.svelte @@ -0,0 +1,73 @@ + + + +
+
+
{title}
+ {#if description}
{description}
{/if} + {#if url}
{url}
{/if} +
+
+ + +
+
+
+ + diff --git a/apps/web/src/lib/ui/feedback/EmptyState.svelte b/apps/web/src/lib/ui/feedback/EmptyState.svelte new file mode 100644 index 0000000..2a15e76 --- /dev/null +++ b/apps/web/src/lib/ui/feedback/EmptyState.svelte @@ -0,0 +1,13 @@ +
+ +
+ + diff --git a/apps/web/src/lib/ui/feedback/ErrorBanner.svelte b/apps/web/src/lib/ui/feedback/ErrorBanner.svelte new file mode 100644 index 0000000..af0cfa3 --- /dev/null +++ b/apps/web/src/lib/ui/feedback/ErrorBanner.svelte @@ -0,0 +1,16 @@ + + + diff --git a/apps/web/src/lib/ui/feedback/Toast.svelte b/apps/web/src/lib/ui/feedback/Toast.svelte new file mode 100644 index 0000000..c4c5c11 --- /dev/null +++ b/apps/web/src/lib/ui/feedback/Toast.svelte @@ -0,0 +1,59 @@ + + +
+ {#each $toasts as t (t.id)} +
+
{t.message}
+ +
+ {/each} +
+ + diff --git a/apps/web/src/lib/ui/layout/AppShell.svelte b/apps/web/src/lib/ui/layout/AppShell.svelte index a65fb21..baffe54 100644 --- a/apps/web/src/lib/ui/layout/AppShell.svelte +++ b/apps/web/src/lib/ui/layout/AppShell.svelte @@ -1,5 +1,6 @@
@@ -20,6 +21,8 @@ + +