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 ConfirmDialog from "@/components/ConfirmDialog" import Field from "@/components/Field" import PricingTable from "@/components/PricingTable" import ToggleButton from "@/components/ToggleButton" import ToggleField from "@/components/ToggleField" import { setToastMessage } from "@/components/Toast" import { plans } from "@/lib/state" const STATUS_STYLES: Record = { active: "bg-green-50 text-green-700 border-green-200", inactive: "bg-gray-100 text-gray-500 border-gray-200", } function StatusBadge(props: { status: string }) { const styles = () => STATUS_STYLES[props.status] ?? "bg-gray-100 text-gray-500 border-gray-200" const label = () => props.status.replace(/_/g, " ") return ( {label()} ) } function DetailSection(props: { title: string; children: any }) { return (

{props.title}

{props.children}
) } function MembershipSection(props: { title: string; children: any }) { return (

{props.title}

{props.children}
) } type RelayDetailCardProps = { relay: Relay currentMembers?: number showTenant?: boolean editHref?: string onDeactivate?: () => void | Promise onReactivate?: () => void | Promise deactivating?: boolean reactivating?: boolean onTogglePublicJoin?: () => void onToggleStripSignatures?: () => void onToggleGroups?: () => void onToggleManagement?: () => void onToggleMediaStorage?: () => void onToggleLivekitSupport?: () => void onTogglePushNotifications?: () => void onUpdatePlan?: (plan: PlanId) => Promise enforcePlanLimits?: boolean showPlanActions?: boolean } export default function RelayDetailCard(props: RelayDetailCardProps) { const r = () => props.relay const flag = (value: number, fallback: boolean) => { if (value === 0) return false if (value === 1) return true return fallback } const [menuOpen, setMenuOpen] = createSignal(false) const [plan, setPlan] = createSignal(props.relay.plan) const [pendingAction, setPendingAction] = createSignal<"deactivate" | "reactivate" | null>(null) let menuContainerRef: HTMLDivElement | undefined const memberLimitLabel = () => { const p = plans().find(p => p.id === r().plan) if (!p) return "?" return p.members === null ? "∞" : String(p.members) } const planLimited = () => (props.enforcePlanLimits ?? true) && r().plan === "free" const showPlanActions = () => props.showPlanActions ?? true const actionBusy = () => pendingAction() === "deactivate" ? !!props.deactivating : pendingAction() === "reactivate" ? !!props.reactivating : false const relayLabel = () => r().info_name || r().subdomain const confirmTitle = () => pendingAction() === "deactivate" ? "Deactivate relay?" : "Reactivate relay?" const confirmDescription = () => pendingAction() === "deactivate" ? `${relayLabel()} will be taken offline immediately.` : `${relayLabel()} will come back online and start accepting connections.` const confirmDetails = () => pendingAction() === "deactivate" ? [ "All client connections will be dropped immediately.", "Members will be unable to read from or publish to the relay.", "Scheduled and automated tasks (billing, syncing) will be paused.", "All relay data, settings, and members are preserved, nothing is deleted.", "You can reactivate at any time from this page.", ] : undefined const confirmLabel = () => pendingAction() === "deactivate" ? "Yes, deactivate" : "Yes, reactivate" const confirmBusyLabel = () => pendingAction() === "deactivate" ? "Deactivating..." : "Reactivating..." const confirmTone = () => pendingAction() === "deactivate" ? "danger" : "primary" async function changePlan(plan: PlanId) { setPlan(plan) try { await props.onUpdatePlan?.(plan) setToastMessage(`Plan updated to ${plan}`, "success") } catch { // error is handled by the caller } } function openActionDialog(action: "deactivate" | "reactivate") { setMenuOpen(false) setPendingAction(action) } function closeActionDialog() { if (actionBusy()) return setPendingAction(null) } async function confirmAction() { const action = pendingAction() if (!action) return if (action === "deactivate") { await props.onDeactivate?.() } else { await props.onReactivate?.() } setPendingAction(null) } createEffect(() => { if (!menuOpen()) return const handleClickOutside = (event: MouseEvent) => { const target = event.target as Node | null if (target && !menuContainerRef?.contains(target)) { setMenuOpen(false) } } const handleEscape = (event: KeyboardEvent) => { if (event.key === "Escape") { setMenuOpen(false) } } document.addEventListener("mousedown", handleClickOutside) document.addEventListener("keydown", handleEscape) onCleanup(() => { document.removeEventListener("mousedown", handleClickOutside) document.removeEventListener("keydown", handleEscape) }) }) return (
{/* Header */}

{r().info_name || r().subdomain}

wss://{r().subdomain}.spaces.coracle.social

{r().info_description}

setMenuOpen(false)} > Edit Details

Provisioning error

{r().sync_error}



}> Upgrade Plan } > Update Plan } > }> Upgrade Plan } > Update Plan } >
{props.currentMembers ?? "—"} {memberLimitLabel()} {r().tenant}
{r().plan} } >
) }