Refactor relay form
This commit is contained in:
@@ -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>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export const RELAY_PLAN_IDS = ["free", "basic", "growth"] as const
|
||||||
|
|
||||||
|
export type RelayPlanId = (typeof RELAY_PLAN_IDS)[number]
|
||||||
@@ -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, "")
|
||||||
|
}
|
||||||
@@ -1,19 +1,9 @@
|
|||||||
import { A, useNavigate, useParams } from "@solidjs/router"
|
import { A, useNavigate, useParams } from "@solidjs/router"
|
||||||
import { Show, createEffect, createResource, createSignal } from "solid-js"
|
import { Show, createEffect, createResource, createSignal } from "solid-js"
|
||||||
import { adminGetRelay, adminUpdateRelay } from "../../lib/api"
|
import { adminGetRelay, adminUpdateRelay } from "../../lib/api"
|
||||||
|
import RelayForm from "../../components/RelayForm"
|
||||||
const PLANS = ["free", "basic", "growth"] as const
|
import { RELAY_PLAN_IDS, type RelayPlanId } from "../../lib/relayPlans"
|
||||||
type PlanId = (typeof PLANS)[number]
|
import { slugify } from "../../lib/slugify"
|
||||||
|
|
||||||
function slugify(s: string) {
|
|
||||||
return s
|
|
||||||
.toLowerCase()
|
|
||||||
.trim()
|
|
||||||
.normalize("NFD")
|
|
||||||
.replace(/[\u0300-\u036f]/g, "")
|
|
||||||
.replace(/[^a-z0-9]+/g, "-")
|
|
||||||
.replace(/^-+|-+$/g, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function AdminRelayEdit() {
|
export default function AdminRelayEdit() {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
@@ -25,7 +15,7 @@ export default function AdminRelayEdit() {
|
|||||||
const [subdomain, setSubdomain] = createSignal("")
|
const [subdomain, setSubdomain] = createSignal("")
|
||||||
const [icon, setIcon] = createSignal("")
|
const [icon, setIcon] = createSignal("")
|
||||||
const [description, setDescription] = createSignal("")
|
const [description, setDescription] = createSignal("")
|
||||||
const [plan, setPlan] = createSignal<PlanId>("free")
|
const [plan, setPlan] = createSignal<RelayPlanId>("free")
|
||||||
const [error, setError] = createSignal("")
|
const [error, setError] = createSignal("")
|
||||||
const [submitting, setSubmitting] = createSignal(false)
|
const [submitting, setSubmitting] = createSignal(false)
|
||||||
|
|
||||||
@@ -36,7 +26,7 @@ export default function AdminRelayEdit() {
|
|||||||
setSubdomain(data.subdomain)
|
setSubdomain(data.subdomain)
|
||||||
setIcon(data.icon)
|
setIcon(data.icon)
|
||||||
setDescription(data.description)
|
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) {
|
async function handleSubmit(e: Event) {
|
||||||
@@ -74,71 +64,24 @@ export default function AdminRelayEdit() {
|
|||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
<Show when={relay() && !relay.loading}>
|
<Show when={relay() && !relay.loading}>
|
||||||
<form onSubmit={handleSubmit} class="space-y-4">
|
<RelayForm
|
||||||
<div>
|
name={name()}
|
||||||
<label class="block text-sm font-medium text-gray-700 mb-1">Relay Name</label>
|
setName={setName}
|
||||||
<input
|
subdomain={subdomain()}
|
||||||
value={name()}
|
setSubdomain={setSubdomain}
|
||||||
onInput={e => setName(e.currentTarget.value)}
|
icon={icon()}
|
||||||
required
|
setIcon={setIcon}
|
||||||
class="w-full border border-gray-300 rounded-lg px-3 py-2"
|
description={description()}
|
||||||
/>
|
setDescription={setDescription}
|
||||||
</div>
|
plan={plan()}
|
||||||
<div>
|
setPlan={setPlan}
|
||||||
<label class="block text-sm font-medium text-gray-700 mb-1">Subdomain</label>
|
plans={RELAY_PLAN_IDS}
|
||||||
<div class="flex items-center border border-gray-300 rounded-lg overflow-hidden">
|
onSubmit={handleSubmit}
|
||||||
<input
|
submitting={submitting()}
|
||||||
value={subdomain()}
|
error={error()}
|
||||||
onInput={e => setSubdomain(e.currentTarget.value)}
|
submitLabel="Save Changes"
|
||||||
required
|
submittingLabel="Saving..."
|
||||||
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>
|
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,19 +1,9 @@
|
|||||||
import { A, useNavigate, useParams } from "@solidjs/router"
|
import { A, useNavigate, useParams } from "@solidjs/router"
|
||||||
import { Show, createEffect, createResource, createSignal } from "solid-js"
|
import { Show, createEffect, createResource, createSignal } from "solid-js"
|
||||||
import { getTenantRelay, updateTenantRelay } from "../../lib/api"
|
import { getTenantRelay, updateTenantRelay } from "../../lib/api"
|
||||||
|
import RelayForm from "../../components/RelayForm"
|
||||||
const PLANS = ["free", "basic", "growth"] as const
|
import { RELAY_PLAN_IDS, type RelayPlanId } from "../../lib/relayPlans"
|
||||||
type PlanId = (typeof PLANS)[number]
|
import { slugify } from "../../lib/slugify"
|
||||||
|
|
||||||
function slugify(s: string) {
|
|
||||||
return s
|
|
||||||
.toLowerCase()
|
|
||||||
.trim()
|
|
||||||
.normalize("NFD")
|
|
||||||
.replace(/[\u0300-\u036f]/g, "")
|
|
||||||
.replace(/[^a-z0-9]+/g, "-")
|
|
||||||
.replace(/^-+|-+$/g, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function RelayEdit() {
|
export default function RelayEdit() {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
@@ -25,7 +15,7 @@ export default function RelayEdit() {
|
|||||||
const [subdomain, setSubdomain] = createSignal("")
|
const [subdomain, setSubdomain] = createSignal("")
|
||||||
const [icon, setIcon] = createSignal("")
|
const [icon, setIcon] = createSignal("")
|
||||||
const [description, setDescription] = createSignal("")
|
const [description, setDescription] = createSignal("")
|
||||||
const [plan, setPlan] = createSignal<PlanId>("free")
|
const [plan, setPlan] = createSignal<RelayPlanId>("free")
|
||||||
const [error, setError] = createSignal("")
|
const [error, setError] = createSignal("")
|
||||||
const [submitting, setSubmitting] = createSignal(false)
|
const [submitting, setSubmitting] = createSignal(false)
|
||||||
|
|
||||||
@@ -36,7 +26,7 @@ export default function RelayEdit() {
|
|||||||
setSubdomain(data.subdomain)
|
setSubdomain(data.subdomain)
|
||||||
setIcon(data.icon)
|
setIcon(data.icon)
|
||||||
setDescription(data.description)
|
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) {
|
async function handleSubmit(e: Event) {
|
||||||
@@ -74,71 +64,24 @@ export default function RelayEdit() {
|
|||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
<Show when={relay() && !relay.loading}>
|
<Show when={relay() && !relay.loading}>
|
||||||
<form onSubmit={handleSubmit} class="space-y-4">
|
<RelayForm
|
||||||
<div>
|
name={name()}
|
||||||
<label class="block text-sm font-medium text-gray-700 mb-1">Relay Name</label>
|
setName={setName}
|
||||||
<input
|
subdomain={subdomain()}
|
||||||
value={name()}
|
setSubdomain={setSubdomain}
|
||||||
onInput={e => setName(e.currentTarget.value)}
|
icon={icon()}
|
||||||
required
|
setIcon={setIcon}
|
||||||
class="w-full border border-gray-300 rounded-lg px-3 py-2"
|
description={description()}
|
||||||
/>
|
setDescription={setDescription}
|
||||||
</div>
|
plan={plan()}
|
||||||
<div>
|
setPlan={setPlan}
|
||||||
<label class="block text-sm font-medium text-gray-700 mb-1">Subdomain</label>
|
plans={RELAY_PLAN_IDS}
|
||||||
<div class="flex items-center border border-gray-300 rounded-lg overflow-hidden">
|
onSubmit={handleSubmit}
|
||||||
<input
|
submitting={submitting()}
|
||||||
value={subdomain()}
|
error={error()}
|
||||||
onInput={e => setSubdomain(e.currentTarget.value)}
|
submitLabel="Save Changes"
|
||||||
required
|
submittingLabel="Saving..."
|
||||||
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>
|
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Show, createSignal } from "solid-js"
|
import { Show, createSignal } from "solid-js"
|
||||||
import { useNavigate } from "@solidjs/router"
|
import { useNavigate } from "@solidjs/router"
|
||||||
import { createTenantRelay } from "../../lib/api"
|
import { createTenantRelay } from "../../lib/api"
|
||||||
|
import { slugify } from "../../lib/slugify"
|
||||||
|
|
||||||
const PLANS = [
|
const PLANS = [
|
||||||
{ id: "free", label: "Free", price: 0, members: "Up to 10", blossom: false, livekit: false },
|
{ 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"]
|
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() {
|
export default function RelayNew() {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const [name, setName] = createSignal("")
|
const [name, setName] = createSignal("")
|
||||||
|
|||||||
Reference in New Issue
Block a user