From ed57ff7bb7352dedc6dd9b8a76e71fc95cd9ea22 Mon Sep 17 00:00:00 2001 From: Jon Staab Date: Thu, 26 Feb 2026 15:43:49 -0800 Subject: [PATCH] Add toast --- frontend/src/components/Toast.tsx | 74 +++++++++++++++++++++++++ frontend/src/pages/relays/RelayList.tsx | 2 +- frontend/src/pages/relays/RelayNew.tsx | 48 ++++++++-------- 3 files changed, 101 insertions(+), 23 deletions(-) create mode 100644 frontend/src/components/Toast.tsx diff --git a/frontend/src/components/Toast.tsx b/frontend/src/components/Toast.tsx new file mode 100644 index 0000000..a1aeea2 --- /dev/null +++ b/frontend/src/components/Toast.tsx @@ -0,0 +1,74 @@ +import { Show, createEffect, createSignal, onCleanup } from "solid-js" + +type ToastProps = { + message?: string + duration?: number + onClear?: () => void +} + +export default function Toast(props: ToastProps) { + const [visible, setVisible] = createSignal(false) + + let hideTimer: number | undefined + let clearTimer: number | undefined + let rafOne: number | undefined + let rafTwo: number | undefined + + function clearTimers() { + if (hideTimer) window.clearTimeout(hideTimer) + if (clearTimer) window.clearTimeout(clearTimer) + if (rafOne) window.cancelAnimationFrame(rafOne) + if (rafTwo) window.cancelAnimationFrame(rafTwo) + hideTimer = undefined + clearTimer = undefined + rafOne = undefined + rafTwo = undefined + } + + createEffect(() => { + const message = props.message?.trim() + clearTimers() + + if (!message) { + setVisible(false) + return + } + + setVisible(false) + rafOne = window.requestAnimationFrame(() => { + rafTwo = window.requestAnimationFrame(() => { + setVisible(true) + rafOne = undefined + rafTwo = undefined + }) + }) + + hideTimer = window.setTimeout(() => { + setVisible(false) + clearTimer = window.setTimeout(() => { + props.onClear?.() + clearTimer = undefined + }, 250) + hideTimer = undefined + }, props.duration ?? 10_000) + }) + + onCleanup(() => { + clearTimers() + }) + + return ( + + + + ) +} diff --git a/frontend/src/pages/relays/RelayList.tsx b/frontend/src/pages/relays/RelayList.tsx index bb209a4..1ce045f 100644 --- a/frontend/src/pages/relays/RelayList.tsx +++ b/frontend/src/pages/relays/RelayList.tsx @@ -37,7 +37,7 @@ export default function RelayList() {

{relay.name}

-

{relay.subdomain}

+

https://{relay.subdomain}.spaces.coracle.social

{relay.status}

diff --git a/frontend/src/pages/relays/RelayNew.tsx b/frontend/src/pages/relays/RelayNew.tsx index 09a1ad8..f923225 100644 --- a/frontend/src/pages/relays/RelayNew.tsx +++ b/frontend/src/pages/relays/RelayNew.tsx @@ -1,6 +1,6 @@ -import { createSignal } from "solid-js" +import { Show, createSignal } from "solid-js" import { useNavigate } from "@solidjs/router" -import { createTenantRelay, listTenantRelays } from "../../lib/api" +import { createTenantRelay } from "../../lib/api" const PLANS = [ { id: "free", label: "Free", price: 0, members: "Up to 10", blossom: false, livekit: false }, @@ -28,38 +28,31 @@ export default function RelayNew() { const [description, setDescription] = createSignal("") const [plan, setPlan] = createSignal("free") const [submitting, setSubmitting] = createSignal(false) - const [error, setError] = createSignal("") + const [subdomainError, setSubdomainError] = createSignal("") function handleNameInput(value: string) { setName(value) setSubdomain(slugify(value)) + setSubdomainError("") } async function handleSubmit(e: Event) { e.preventDefault() - setError("") + setSubdomainError("") setSubmitting(true) try { - const normalizedSubdomain = slugify(subdomain()) - const existingRelays = await listTenantRelays() - const hasDuplicate = existingRelays.some(relay => relay.subdomain.toLowerCase() === normalizedSubdomain.toLowerCase()) - - if (hasDuplicate) { - throw new Error("You already have a relay with this subdomain") - } - const relay = await createTenantRelay({ name: name().trim(), - subdomain: normalizedSubdomain, + subdomain: slugify(subdomain()), icon: icon().trim(), description: description().trim(), plan: plan(), }) navigate(`/relays/${relay.id}`) } catch (e) { - setError(e instanceof Error ? e.message : "Failed to create relay") + setSubdomainError(e instanceof Error ? e.message : "Failed to create relay") } finally { setSubmitting(false) } @@ -69,12 +62,6 @@ export default function RelayNew() {

New Relay

- {error() && ( -
- {error()} -
- )} -
-
+
+
setSubdomain(e.currentTarget.value)} + onInput={e => { + setSubdomain(e.currentTarget.value) + setSubdomainError("") + }} placeholder="my-community" class="flex-1 px-3 py-2 focus:outline-none" /> .spaces.coracle.social +
+ + +
+ {subdomainError()} +
+