Refactor relay form

This commit is contained in:
Jon Staab
2026-02-27 13:11:42 -08:00
parent 6017a65cf4
commit 5d102ad215
6 changed files with 145 additions and 170 deletions
+86
View File
@@ -0,0 +1,86 @@
type RelayFormProps = {
name: string
setName: (value: string) => void
subdomain: string
setSubdomain: (value: string) => void
icon: string
setIcon: (value: string) => void
description: string
setDescription: (value: string) => void
plan: string
setPlan: (value: string) => void
plans: readonly string[]
onSubmit: (e: Event) => void
submitting: boolean
error?: string
submitLabel: string
submittingLabel: string
}
export default function RelayForm(props: RelayFormProps) {
return (
<form onSubmit={props.onSubmit} class="space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Relay Name</label>
<input
value={props.name}
onInput={e => props.setName(e.currentTarget.value)}
required
class="w-full border border-gray-300 rounded-lg px-3 py-2"
/>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Subdomain</label>
<div class="flex items-center border border-gray-300 rounded-lg overflow-hidden">
<input
value={props.subdomain}
onInput={e => props.setSubdomain(e.currentTarget.value)}
required
class="flex-1 px-3 py-2"
/>
<span class="px-3 py-2 bg-gray-50 text-gray-500 text-sm border-l border-gray-300">.spaces.coracle.social</span>
</div>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Icon URL</label>
<input
type="url"
value={props.icon}
onInput={e => props.setIcon(e.currentTarget.value)}
class="w-full border border-gray-300 rounded-lg px-3 py-2"
/>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Description</label>
<textarea
value={props.description}
onInput={e => props.setDescription(e.currentTarget.value)}
rows={3}
class="w-full border border-gray-300 rounded-lg px-3 py-2"
/>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Plan</label>
<div class="flex gap-2">
{props.plans.map(p => (
<button
type="button"
class={`rounded-lg border px-3 py-2 text-sm capitalize ${props.plan === p ? "border-blue-600 text-blue-700" : "border-gray-300 text-gray-700"}`}
onClick={() => props.setPlan(p)}
>
{p}
</button>
))}
</div>
</div>
{props.error && <p class="text-sm text-red-600">{props.error}</p>}
<button
type="submit"
disabled={props.submitting}
class="w-full py-2 px-4 bg-blue-600 text-white font-medium rounded-lg hover:bg-blue-700 disabled:opacity-50"
>
{props.submitting ? props.submittingLabel : props.submitLabel}
</button>
</form>
)
}
+3
View File
@@ -0,0 +1,3 @@
export const RELAY_PLAN_IDS = ["free", "basic", "growth"] as const
export type RelayPlanId = (typeof RELAY_PLAN_IDS)[number]
+9
View File
@@ -0,0 +1,9 @@
export function slugify(value: string) {
return value
.toLowerCase()
.trim()
.normalize("NFD")
.replace(/[\u0300-\u036f]/g, "")
.replace(/[^a-z0-9]+/g, "-")
.replace(/^-+|-+$/g, "")
}
+23 -80
View File
@@ -1,19 +1,9 @@
import { A, useNavigate, useParams } from "@solidjs/router"
import { Show, createEffect, createResource, createSignal } from "solid-js"
import { adminGetRelay, adminUpdateRelay } from "../../lib/api"
const PLANS = ["free", "basic", "growth"] as const
type PlanId = (typeof PLANS)[number]
function slugify(s: string) {
return s
.toLowerCase()
.trim()
.normalize("NFD")
.replace(/[\u0300-\u036f]/g, "")
.replace(/[^a-z0-9]+/g, "-")
.replace(/^-+|-+$/g, "")
}
import RelayForm from "../../components/RelayForm"
import { RELAY_PLAN_IDS, type RelayPlanId } from "../../lib/relayPlans"
import { slugify } from "../../lib/slugify"
export default function AdminRelayEdit() {
const navigate = useNavigate()
@@ -25,7 +15,7 @@ export default function AdminRelayEdit() {
const [subdomain, setSubdomain] = createSignal("")
const [icon, setIcon] = createSignal("")
const [description, setDescription] = createSignal("")
const [plan, setPlan] = createSignal<PlanId>("free")
const [plan, setPlan] = createSignal<RelayPlanId>("free")
const [error, setError] = createSignal("")
const [submitting, setSubmitting] = createSignal(false)
@@ -36,7 +26,7 @@ export default function AdminRelayEdit() {
setSubdomain(data.subdomain)
setIcon(data.icon)
setDescription(data.description)
setPlan(PLANS.includes(data.plan as PlanId) ? (data.plan as PlanId) : "free")
setPlan(RELAY_PLAN_IDS.includes(data.plan as RelayPlanId) ? (data.plan as RelayPlanId) : "free")
})
async function handleSubmit(e: Event) {
@@ -74,71 +64,24 @@ export default function AdminRelayEdit() {
</Show>
<Show when={relay() && !relay.loading}>
<form onSubmit={handleSubmit} class="space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Relay Name</label>
<input
value={name()}
onInput={e => setName(e.currentTarget.value)}
required
class="w-full border border-gray-300 rounded-lg px-3 py-2"
/>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Subdomain</label>
<div class="flex items-center border border-gray-300 rounded-lg overflow-hidden">
<input
value={subdomain()}
onInput={e => setSubdomain(e.currentTarget.value)}
required
class="flex-1 px-3 py-2"
/>
<span class="px-3 py-2 bg-gray-50 text-gray-500 text-sm border-l border-gray-300">.spaces.coracle.social</span>
</div>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Icon URL</label>
<input
type="url"
value={icon()}
onInput={e => setIcon(e.currentTarget.value)}
class="w-full border border-gray-300 rounded-lg px-3 py-2"
/>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Description</label>
<textarea
value={description()}
onInput={e => setDescription(e.currentTarget.value)}
rows={3}
class="w-full border border-gray-300 rounded-lg px-3 py-2"
/>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Plan</label>
<div class="flex gap-2">
{PLANS.map(p => (
<button
type="button"
class={`rounded-lg border px-3 py-2 text-sm capitalize ${plan() === p ? "border-blue-600 text-blue-700" : "border-gray-300 text-gray-700"}`}
onClick={() => setPlan(p)}
>
{p}
</button>
))}
</div>
</div>
<Show when={error()}>
<p class="text-sm text-red-600">{error()}</p>
</Show>
<button
type="submit"
disabled={submitting()}
class="w-full py-2 px-4 bg-blue-600 text-white font-medium rounded-lg hover:bg-blue-700 disabled:opacity-50"
>
{submitting() ? "Saving..." : "Save Changes"}
</button>
</form>
<RelayForm
name={name()}
setName={setName}
subdomain={subdomain()}
setSubdomain={setSubdomain}
icon={icon()}
setIcon={setIcon}
description={description()}
setDescription={setDescription}
plan={plan()}
setPlan={setPlan}
plans={RELAY_PLAN_IDS}
onSubmit={handleSubmit}
submitting={submitting()}
error={error()}
submitLabel="Save Changes"
submittingLabel="Saving..."
/>
</Show>
</div>
)
+23 -80
View File
@@ -1,19 +1,9 @@
import { A, useNavigate, useParams } from "@solidjs/router"
import { Show, createEffect, createResource, createSignal } from "solid-js"
import { getTenantRelay, updateTenantRelay } from "../../lib/api"
const PLANS = ["free", "basic", "growth"] as const
type PlanId = (typeof PLANS)[number]
function slugify(s: string) {
return s
.toLowerCase()
.trim()
.normalize("NFD")
.replace(/[\u0300-\u036f]/g, "")
.replace(/[^a-z0-9]+/g, "-")
.replace(/^-+|-+$/g, "")
}
import RelayForm from "../../components/RelayForm"
import { RELAY_PLAN_IDS, type RelayPlanId } from "../../lib/relayPlans"
import { slugify } from "../../lib/slugify"
export default function RelayEdit() {
const navigate = useNavigate()
@@ -25,7 +15,7 @@ export default function RelayEdit() {
const [subdomain, setSubdomain] = createSignal("")
const [icon, setIcon] = createSignal("")
const [description, setDescription] = createSignal("")
const [plan, setPlan] = createSignal<PlanId>("free")
const [plan, setPlan] = createSignal<RelayPlanId>("free")
const [error, setError] = createSignal("")
const [submitting, setSubmitting] = createSignal(false)
@@ -36,7 +26,7 @@ export default function RelayEdit() {
setSubdomain(data.subdomain)
setIcon(data.icon)
setDescription(data.description)
setPlan(PLANS.includes(data.plan as PlanId) ? (data.plan as PlanId) : "free")
setPlan(RELAY_PLAN_IDS.includes(data.plan as RelayPlanId) ? (data.plan as RelayPlanId) : "free")
})
async function handleSubmit(e: Event) {
@@ -74,71 +64,24 @@ export default function RelayEdit() {
</Show>
<Show when={relay() && !relay.loading}>
<form onSubmit={handleSubmit} class="space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Relay Name</label>
<input
value={name()}
onInput={e => setName(e.currentTarget.value)}
required
class="w-full border border-gray-300 rounded-lg px-3 py-2"
/>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Subdomain</label>
<div class="flex items-center border border-gray-300 rounded-lg overflow-hidden">
<input
value={subdomain()}
onInput={e => setSubdomain(e.currentTarget.value)}
required
class="flex-1 px-3 py-2"
/>
<span class="px-3 py-2 bg-gray-50 text-gray-500 text-sm border-l border-gray-300">.spaces.coracle.social</span>
</div>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Icon URL</label>
<input
type="url"
value={icon()}
onInput={e => setIcon(e.currentTarget.value)}
class="w-full border border-gray-300 rounded-lg px-3 py-2"
/>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Description</label>
<textarea
value={description()}
onInput={e => setDescription(e.currentTarget.value)}
rows={3}
class="w-full border border-gray-300 rounded-lg px-3 py-2"
/>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Plan</label>
<div class="flex gap-2">
{PLANS.map(p => (
<button
type="button"
class={`rounded-lg border px-3 py-2 text-sm capitalize ${plan() === p ? "border-blue-600 text-blue-700" : "border-gray-300 text-gray-700"}`}
onClick={() => setPlan(p)}
>
{p}
</button>
))}
</div>
</div>
<Show when={error()}>
<p class="text-sm text-red-600">{error()}</p>
</Show>
<button
type="submit"
disabled={submitting()}
class="w-full py-2 px-4 bg-blue-600 text-white font-medium rounded-lg hover:bg-blue-700 disabled:opacity-50"
>
{submitting() ? "Saving..." : "Save Changes"}
</button>
</form>
<RelayForm
name={name()}
setName={setName}
subdomain={subdomain()}
setSubdomain={setSubdomain}
icon={icon()}
setIcon={setIcon}
description={description()}
setDescription={setDescription}
plan={plan()}
setPlan={setPlan}
plans={RELAY_PLAN_IDS}
onSubmit={handleSubmit}
submitting={submitting()}
error={error()}
submitLabel="Save Changes"
submittingLabel="Saving..."
/>
</Show>
</div>
)
+1 -10
View File
@@ -1,6 +1,7 @@
import { Show, createSignal } from "solid-js"
import { useNavigate } from "@solidjs/router"
import { createTenantRelay } from "../../lib/api"
import { slugify } from "../../lib/slugify"
const PLANS = [
{ id: "free", label: "Free", price: 0, members: "Up to 10", blossom: false, livekit: false },
@@ -10,16 +11,6 @@ const PLANS = [
type PlanId = (typeof PLANS)[number]["id"]
function slugify(s: string) {
return s
.toLowerCase()
.trim()
.normalize("NFD")
.replace(/[\u0300-\u036f]/g, "")
.replace(/[^a-z0-9]+/g, "-")
.replace(/^-+|-+$/g, "")
}
export default function RelayNew() {
const navigate = useNavigate()
const [name, setName] = createSignal("")