Opus refactor

This commit is contained in:
Jon Staab
2026-03-27 14:39:37 -07:00
parent 77ea366c69
commit 8986e5481d
16 changed files with 265 additions and 745 deletions
+10 -48
View File
@@ -2,54 +2,11 @@ import { A } from "@solidjs/router"
import { Show, createEffect, createSignal, onCleanup } from "solid-js"
import type { Relay, PlanId } from "@/lib/api"
import menuDotsIcon from "@/assets/menu-dots-2.svg"
import Field from "@/components/Field"
import PricingTable from "@/components/PricingTable"
function Field(props: { label: string; children: any }) {
return (
<div>
<dt class="text-xs font-medium text-gray-500 uppercase tracking-wide">{props.label}</dt>
<dd class="mt-0.5 text-sm text-gray-900">{props.children}</dd>
</div>
)
}
function ToggleField(props: { label: string; children: any }) {
return (
<div class="flex items-center justify-between gap-3">
<dt class="text-xs font-medium text-gray-500 uppercase tracking-wide">{props.label}</dt>
<dd class="text-sm text-gray-900">{props.children}</dd>
</div>
)
}
function ToggleButton(props: {
enabled: boolean
disabled?: boolean
onToggle?: () => void
onLabel?: string
offLabel?: string
}) {
const label = () => (props.enabled ? (props.onLabel ?? "Enabled") : (props.offLabel ?? "Disabled"))
return (
<div class="inline-flex items-center gap-2">
<button
type="button"
role="switch"
aria-checked={props.enabled}
aria-label={label()}
class={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors ${props.enabled ? "bg-blue-600" : "bg-gray-300"} disabled:opacity-50`}
onClick={props.onToggle}
disabled={props.disabled}
>
<span
class={`inline-block h-5 w-5 transform rounded-full bg-white shadow transition-transform ${props.enabled ? "translate-x-5" : "translate-x-0.5"}`}
/>
</button>
<span class={`text-xs font-medium ${props.enabled ? "text-blue-700" : "text-gray-500"}`}>{label()}</span>
</div>
)
}
import ToggleButton from "@/components/ToggleButton"
import ToggleField from "@/components/ToggleField"
import { setToastMessage } from "@/components/Toast"
function DetailSection(props: { title: string; children: any }) {
return (
@@ -116,7 +73,12 @@ export default function RelayDetailCard(props: RelayDetailCardProps) {
async function changePlan(plan: PlanId) {
setPlan(plan)
props.onUpdatePlan?.(plan)
try {
await props.onUpdatePlan?.(plan)
setToastMessage(`Plan updated to ${plan}`, "success")
} catch {
// error is handled by the caller
}
}
createEffect(() => {
+27
View File
@@ -0,0 +1,27 @@
import { A } from "@solidjs/router"
import type { Relay } from "@/lib/api"
type RelayListItemProps = {
relay: Relay
href: string
showTenant?: boolean
}
export default function RelayListItem(props: RelayListItemProps) {
return (
<li>
<A href={props.href} class="block rounded-lg border border-gray-200 bg-white p-4 hover:border-gray-300">
<div class="flex items-center justify-between gap-3">
<div>
<p class="font-medium text-gray-900">{props.relay.info_name || props.relay.subdomain}</p>
<p class="text-xs text-gray-500">{props.relay.subdomain}.spaces.coracle.social</p>
{props.showTenant && (
<p class="text-xs text-gray-500 break-all mt-1">Tenant: {props.relay.tenant}</p>
)}
</div>
<p class="text-xs uppercase tracking-wide text-gray-500">{props.relay.status}</p>
</div>
</A>
</li>
)
}
+25
View File
@@ -0,0 +1,25 @@
type SearchInputProps = {
value: string
onInput: (value: string) => void
placeholder?: string
}
export default function SearchInput(props: SearchInputProps) {
return (
<div class="relative">
<span class="pointer-events-none absolute inset-y-0 left-3 flex items-center text-gray-400">
<svg class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<circle cx="11" cy="11" r="8" />
<path d="M21 21l-4.3-4.3" />
</svg>
</span>
<input
type="search"
value={props.value}
onInput={(e) => props.onInput(e.currentTarget.value)}
placeholder={props.placeholder ?? "Search..."}
class="w-full border border-gray-300 rounded-lg py-2 pl-10 pr-3 focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
</div>
)
}
+12 -2
View File
@@ -1,6 +1,14 @@
import { Show, createEffect, createSignal, onCleanup } from "solid-js"
export const [toastMessage, setToastMessage] = createSignal("")
type ToastVariant = "error" | "success"
const [toastVariant, setToastVariant] = createSignal<ToastVariant>("error")
export const [toastMessage, setRawToastMessage] = createSignal("")
export function setToastMessage(message: string, variant: ToastVariant = "error") {
setToastVariant(variant)
setRawToastMessage(message)
}
export default function Toast() {
const [visible, setVisible] = createSignal(false)
@@ -57,10 +65,12 @@ export default function Toast() {
<Show when={toastMessage()}>
<div
role="alert"
class="fixed bottom-4 right-4 z-50 max-w-md rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-base text-red-700 shadow-lg transition-all duration-400 ease-out"
class="fixed bottom-4 right-4 z-50 max-w-md rounded-xl border px-4 py-3 text-base shadow-lg transition-all duration-400 ease-out"
classList={{
"translate-y-0 opacity-100 scale-100": visible(),
"translate-y-3 opacity-0 scale-95": !visible(),
"border-red-200 bg-red-50 text-red-700": toastVariant() === "error",
"border-green-200 bg-green-50 text-green-700": toastVariant() === "success",
}}
>
{toastMessage()}