Răsfoiți Sursa

Create toaster

Lee Morgan 3 săptămâni în urmă
părinte
comite
0ac8c67e31

+ 97 - 0
src/lib/components/Toaster.svelte

@@ -0,0 +1,97 @@
+<script>
+    import { fly } from "svelte/transition";
+    import { toastStore, dismiss } from "$lib/toast.svelte.js";
+</script>
+
+<div class="toaster">
+    {#each toastStore.items as t (t.id)}
+        <div class="toast {t.type}" transition:fly={{ x: 60, duration: 250, opacity: 0 }}>
+            <span class="icon">
+                {#if t.type === "success"}
+                    <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
+                        <polyline points="20 6 9 17 4 12"/>
+                    </svg>
+                {:else}
+                    <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
+                        <line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>
+                    </svg>
+                {/if}
+            </span>
+            <span class="message">{t.message}</span>
+            <button class="close" onclick={() => dismiss(t.id)} aria-label="Dismiss">
+                <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
+                    <line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>
+                </svg>
+            </button>
+        </div>
+    {/each}
+</div>
+
+<style>
+    .toaster {
+        position: fixed;
+        top: 1.5rem;
+        right: 1.5rem;
+        z-index: 9999;
+        display: flex;
+        flex-direction: column;
+        gap: 0.625rem;
+        pointer-events: none;
+    }
+
+    .toast {
+        display: flex;
+        align-items: center;
+        gap: 0.625rem;
+        padding: 0.75rem 1rem;
+        border-radius: 10px;
+        font-size: 0.9rem;
+        font-weight: 500;
+        min-width: 260px;
+        max-width: 380px;
+        pointer-events: all;
+        border: 1px solid transparent;
+    }
+
+    .toast.success {
+        background: #0f2a1a;
+        border-color: #1a5c30;
+        color: #4ade80;
+    }
+
+    .toast.error {
+        background: #2a0f0f;
+        border-color: #5c1a1a;
+        color: #f87171;
+    }
+
+    .icon {
+        display: flex;
+        align-items: center;
+        flex-shrink: 0;
+    }
+
+    .message {
+        flex: 1;
+        color: #e8e8f0;
+        line-height: 1.4;
+    }
+
+    .close {
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        background: none;
+        border: none;
+        cursor: pointer;
+        color: #666;
+        padding: 2px;
+        flex-shrink: 0;
+        border-radius: 4px;
+        transition: color 0.15s;
+    }
+
+    .close:hover {
+        color: #aaa;
+    }
+</style>

+ 12 - 0
src/lib/toast.svelte.js

@@ -0,0 +1,12 @@
+export const toastStore = $state({ items: [] });
+
+export function toast(message, type = "success") {
+    const id = crypto.randomUUID();
+    toastStore.items.push({ id, message, type });
+    setTimeout(() => dismiss(id), 3500);
+}
+
+export function dismiss(id) {
+    const idx = toastStore.items.findIndex(t => t.id === id);
+    if (idx !== -1) toastStore.items.splice(idx, 1);
+}

+ 2 - 0
src/routes/+layout.svelte

@@ -1,5 +1,6 @@
 <script>
 	import favicon from '$lib/assets/favicon.png';
+	import Toaster from '$lib/components/Toaster.svelte';
 
 	let { children } = $props();
 </script>
@@ -9,6 +10,7 @@
 	<title>ChatRPG — AI-Powered Text RPG Adventures</title>
 </svelte:head>
 
+<Toaster />
 {@render children()}
 
 <style>

+ 3 - 2
src/routes/register/+page.svelte

@@ -2,6 +2,7 @@
     import "$lib/global.css";
     import {PUBLIC_API_URL} from "$env/static/public";
     import {goto} from "$app/navigation";
+    import {toast} from "$lib/toast.svelte.js";
     import favicon from "$lib/assets/favicon.png";
 
     let name = $state("");
@@ -27,8 +28,8 @@
 
         const responseBody = await response.json();
         if(!response.ok){
-            console.log("error");
-            console.log(responseBody);
+            toast(responseBody.error.message, "error")
+            console.log(responseBody.error.message);
         }else{
             goto("/login");
         }