From f1143551d60cf80f26468b91338318d77989b1ec Mon Sep 17 00:00:00 2001 From: axel Date: Sat, 19 Apr 2025 20:47:43 +0200 Subject: [PATCH] feat: basic toast notification system - closes #5 --- src/lib/v2/globalStore.svelte.ts | 3 ++ src/lib/v2/snippets/EditPage.svelte | 11 ++++++- src/lib/v2/toast/Notification.svelte | 43 +++++++++++++++++++++++++ src/lib/v2/toast/notification.ts | 43 +++++++++++++++++++++++++ src/routes/+layout.svelte | 10 ++++++ src/routes/dash/account/+page.svelte | 3 +- src/routes/dash/users/[id]/+page.svelte | 1 - src/routes/login/+page.svelte | 18 ++++++++++- 8 files changed, 127 insertions(+), 5 deletions(-) create mode 100644 src/lib/v2/toast/Notification.svelte create mode 100644 src/lib/v2/toast/notification.ts diff --git a/src/lib/v2/globalStore.svelte.ts b/src/lib/v2/globalStore.svelte.ts index 51d2430..35c4835 100644 --- a/src/lib/v2/globalStore.svelte.ts +++ b/src/lib/v2/globalStore.svelte.ts @@ -1,7 +1,10 @@ import { env } from '$env/dynamic/public'; +import type { NotificationData } from './toast/notification'; export const store = $state<{ pageTitle: string; + notifications: NotificationData[]; }>({ pageTitle: env.PUBLIC_SITE_NAME ?? '', + notifications: [], }); diff --git a/src/lib/v2/snippets/EditPage.svelte b/src/lib/v2/snippets/EditPage.svelte index 346a7b2..2afbcb0 100644 --- a/src/lib/v2/snippets/EditPage.svelte +++ b/src/lib/v2/snippets/EditPage.svelte @@ -4,6 +4,7 @@ import IconDeviceFloppy from '~icons/tabler/device-floppy'; import IconTrash from '~icons/tabler/trash'; import IconX from '~icons/tabler/x'; + import { Toast } from '../toast/notification'; let { children, createOnly } = $props(); @@ -13,7 +14,15 @@ method="POST" action="?/update" use:enhance={() => { - return async ({ update }) => { + return async ({ update, result }) => { + if (result.type == 'failure' && result.data?.error) { + Toast.add({ + Icon: IconX, + content: result.data.error as string, + theme: 'error', + }); + } + update({ reset: false }); }; }} diff --git a/src/lib/v2/toast/Notification.svelte b/src/lib/v2/toast/Notification.svelte new file mode 100644 index 0000000..af54f46 --- /dev/null +++ b/src/lib/v2/toast/Notification.svelte @@ -0,0 +1,43 @@ + + + diff --git a/src/lib/v2/toast/notification.ts b/src/lib/v2/toast/notification.ts new file mode 100644 index 0000000..9f2fbe4 --- /dev/null +++ b/src/lib/v2/toast/notification.ts @@ -0,0 +1,43 @@ +import { nanoid } from 'nanoid'; +import { store } from '../globalStore.svelte'; + +export type NotificationTheme = 'neutral' | 'error' | 'success'; + +export type NotificationData = { + Icon?: any; + id: string; + content: string; + dismissable: boolean; + timeout: number; + spin: boolean; + theme: NotificationTheme; +}; + +export type BasicNotificationData = Omit, 'id'> & { content: string }; + +export namespace Toast { + export function add(options: BasicNotificationData) { + const data: NotificationData = Object.assign( + { + id: nanoid(), + dismissable: true, + timeout: 10, + theme: 'neutral', + spin: false, + }, + options, + ); + + store.notifications.push(data); + return data.id; + } + + export function remove(notificationId: string) { + const currentNotifications = store.notifications; + store.notifications = currentNotifications.filter((n) => n.id !== notificationId); + } + + export function clear() { + store.notifications = []; + } +} diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 5d83456..608db38 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -1,7 +1,17 @@ {@render children()} + +
+ {#each store.notifications as n (n.id)} + + {/each} +
diff --git a/src/routes/dash/account/+page.svelte b/src/routes/dash/account/+page.svelte index c9054c9..c2bcd15 100644 --- a/src/routes/dash/account/+page.svelte +++ b/src/routes/dash/account/+page.svelte @@ -3,12 +3,11 @@ import { store } from '$lib/v2/globalStore.svelte.js'; import EditPage from '$lib/v2/snippets/EditPage.svelte'; - let { data, form } = $props(); + let { data } = $props(); store.pageTitle = 'Account details'; -

{form?.error}

-

{form?.error}

+ import { enhance } from '$app/forms'; import { env } from '$env/dynamic/public'; import InputText from '$lib/v2/forms/InputText.svelte'; + import { Toast } from '$lib/v2/toast/notification'; import { Button } from 'bits-ui'; + import { untrack } from 'svelte'; import IconLogin2 from '~icons/tabler/login2'; + import IconX from '~icons/tabler/x'; import type { PageProps } from './$types'; let { form }: PageProps = $props(); + + $effect(() => { + if (form?.error) { + untrack(() => { + Toast.add({ + Icon: IconX, + content: form.error, + theme: 'error', + }); + }); + } + }); @@ -17,7 +33,7 @@ class="flex flex-col m-4 w-full max-w-120 bg-neutral-950 p-8 sm:p-12 sm:px rounded-2xl border border-neutral-700 shadow-xl" > -
+