diff --git a/package-lock.json b/package-lock.json
index c6a082f..be1aa15 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2720,9 +2720,9 @@
}
},
"node_modules/caniuse-lite": {
- "version": "1.0.30001702",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001702.tgz",
- "integrity": "sha512-LoPe/D7zioC0REI5W73PeR1e1MLCipRGq/VkovJnd6Df+QVqT+vT33OXCp8QUd7kA7RZrHWxb1B36OQKI/0gOA==",
+ "version": "1.0.30001757",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001757.tgz",
+ "integrity": "sha512-r0nnL/I28Zi/yjk1el6ilj27tKcdjLsNqAOZr0yVjWPrSQyHgKI2INaEWw21bAQSv2LXRt1XuCS/GomNpWOxsQ==",
"dev": true,
"funding": [
{
diff --git a/src/lib/Toasts.svelte b/src/lib/Toasts.svelte
new file mode 100644
index 0000000..e7f33e0
--- /dev/null
+++ b/src/lib/Toasts.svelte
@@ -0,0 +1,84 @@
+
+
+
+
+{#if currentToast != undefined}
+
+ {currentToast.message}
+
+{/if}
+
+
diff --git a/src/lib/errors.test.ts b/src/lib/errors.test.ts
new file mode 100644
index 0000000..ad09d9e
--- /dev/null
+++ b/src/lib/errors.test.ts
@@ -0,0 +1,12 @@
+import { describe, expect, it } from "vitest";
+import { getErrorMessage } from "./errors";
+
+describe("getErrorMessage", () => {
+ it("should return string unchanged", () => {
+ expect(getErrorMessage("example string")).toBe("example string");
+ });
+
+ it("should get message from error", () => {
+ expect(getErrorMessage(new Error("example message"))).toBe("example message");
+ });
+});
diff --git a/src/lib/errors.ts b/src/lib/errors.ts
new file mode 100644
index 0000000..bfb4753
--- /dev/null
+++ b/src/lib/errors.ts
@@ -0,0 +1,42 @@
+import { showToast } from "./Toasts.svelte";
+
+export function getErrorMessage(error: unknown): string {
+ if (typeof error == "string") {
+ return error;
+ }
+
+ if (
+ typeof error == "object" &&
+ error != null &&
+ "message" in error &&
+ typeof error.message == "string"
+ ) {
+ return error.message;
+ }
+
+ console.error("unknown error", error);
+ return String(error);
+}
+
+export function toastError(error: unknown) {
+ console.error(error);
+
+ const message = getErrorMessage(error);
+
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-call -- this is just eslint getting confused by svelte
+ showToast({ message, style: "error" });
+}
+
+export async function assertOk(response: Response | Promise): Promise {
+ response = await response;
+
+ if (response.ok) {
+ return response;
+ }
+
+ if (response.headers.get("Content-Type")?.startsWith("application/json")) {
+ throw await response.json();
+ } else {
+ throw await response.text();
+ }
+}
diff --git a/src/lib/themes.css b/src/lib/themes.css
index 5fba398..6ac48ab 100644
--- a/src/lib/themes.css
+++ b/src/lib/themes.css
@@ -1,7 +1,7 @@
:root {
color-scheme: light dark;
- background-color: light-dark(white, #121212);
- color: light-dark(black, white);
+ background-color: var(--background-color);
+ color: var(--foreground-color);
accent-color: var(--primary);
--red: oklch(57% 0.26 28deg);
@@ -12,4 +12,6 @@
--purple: oklch(48% 0.24 311deg);
--pink: oklch(77% 0.19 356deg);
--primary: light-dark(var(--blue), var(--aqua));
+ --background-color: light-dark(white, #121212);
+ --foreground-color: light-dark(black, white);
}
diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte
index ce9bd52..e7cc81b 100644
--- a/src/routes/+layout.svelte
+++ b/src/routes/+layout.svelte
@@ -13,6 +13,8 @@
import Modal from "$lib/Modal.svelte";
import EditNameForm from "$lib/EditNameForm.svelte";
import AccountModal from "$lib/AccountModal.svelte";
+ import Toasts from "$lib/Toasts.svelte";
+ import { toastError } from "$lib/errors.js";
let { data, children } = $props();
let { supabase, session, user, profilePromise, lightModeCookie } = $derived(data);
@@ -54,6 +56,11 @@
+ toastError(event instanceof ErrorEvent ? event.error : event)}
+ onunhandledrejection={(event) => toastError(event.reason)}
+/>
+
@@ -78,6 +85,8 @@
{/if}
+
+