import { designToken } from "@ekklesia/design-tokens"; import { Button, Dialog, Field, TextInput } from "@ekklesia/ui"; import { type FormEvent, type ReactElement, useCallback, useEffect, useState } from "react"; import { approveDevicePairing, type EkklesiaHostConfig, type HostDeviceSummary, listHostDevices, lookupDevicePairing, revokeHostDevice, } from "../host-protocol.js"; /** * The administrator's whole device surface: type the code from a screen, name it, approve it, and * later take it away again. * * Deliberately small. Device identity belongs to the platform rather than to any module, so it has * to live in the host — but this is infrastructure plumbing, not a fleet-management product, and * there is no grouping, filtering, bulk action, targeting, health, or remote control here on * purpose (ADR 0036). It renders only for an administrator who actually holds `core.devices.manage`: * a viewer without it is told nothing about which devices exist. */ const panelStyles = { meta: { color: designToken("text.muted"), fontSize: designToken("typography.size-xs"), margin: 0, }, row: { alignItems: "center", borderTop: `${designToken("border.width")} solid ${designToken("border.divider")}`, display: "flex", gap: designToken("spacing.sm"), justifyContent: "space-between", padding: `${designToken("spacing.sm")} 0`, }, section: { display: "grid", gap: designToken("spacing.sm") }, title: { color: designToken("text.secondary"), fontSize: designToken("typography.size-xs"), fontWeight: designToken("typography.weight-bold"), letterSpacing: "0.05em", margin: 0, }, } as const; type PairingStep = | { readonly step: "idle" } | { readonly step: "busy" } | { readonly step: "found"; readonly moduleId: string; readonly permissionId: string } | { readonly step: "error"; readonly message: string }; export interface DevicePanelProps { readonly config: EkklesiaHostConfig; } export function DevicePanel(props: DevicePanelProps): ReactElement | undefined { const { config } = props; const [devices, setDevices] = useState(undefined); const [permitted, setPermitted] = useState(true); const [code, setCode] = useState(""); const [name, setName] = useState(""); const [pairing, setPairing] = useState({ step: "idle" }); /** The device an administrator asked to revoke and has not yet confirmed. */ const [confirming, setConfirming] = useState(undefined); const [revoking, setRevoking] = useState(false); const dismissConfirmation = useCallback(() => setConfirming(undefined), []); const refresh = useCallback(async (): Promise => { try { setDevices(await listHostDevices(config)); } catch { // The panel simply does not render for someone who may not manage devices; it never explains // what it would have shown. setPermitted(false); } }, [config]); useEffect(() => { void refresh(); }, [refresh]); if (!permitted) { return undefined; } const lookUp = async (event: FormEvent): Promise => { event.preventDefault(); setPairing({ step: "busy" }); try { const found = await lookupDevicePairing(config, code.trim()); setPairing({ moduleId: found.moduleId, permissionId: found.permissionId, step: "found" }); } catch { // One message for expired, consumed, mistyped, and never-existed alike. setPairing({ message: "That code is not waiting for approval.", step: "error" }); } }; const approve = async (): Promise => { setPairing({ step: "busy" }); try { await approveDevicePairing(config, code.trim(), name.trim()); setCode(""); setName(""); setPairing({ step: "idle" }); await refresh(); } catch { setPairing({ message: "That code could not be approved.", step: "error" }); } }; const revoke = async (deviceId: string): Promise => { setRevoking(true); try { await revokeHostDevice(config, deviceId); } catch { // Left to the next refresh rather than asserting a failure the operator cannot act on. } await refresh(); setRevoking(false); setConfirming(undefined); }; return (

DISPLAY DEVICES

void lookUp(event)} style={{ display: "grid", gap: designToken("spacing.sm") }} > {(control) => ( setCode(event.target.value)} value={code} /> )}
{pairing.step === "found" ? (

A display is asking to show {pairing.moduleId}.

{(control) => ( setName(event.target.value)} placeholder="Foyer TV" value={name} /> )}
) : undefined} {pairing.step === "error" ? (

{pairing.message}

) : undefined} {devices !== undefined && devices.length === 0 ? (

No displays are paired.

) : undefined} {devices?.map((device) => (
{device.name} {device.revoked ? " (revoked)" : ""} {device.revoked ? undefined : ( )}
))} {/* Revoking strands the screen until someone re-pairs it in person, so the consequence is spelled out before it happens — never a browser-native confirm. */} } onDismiss={dismissConfirmation} open={confirming !== undefined} title={`Revoke ${confirming?.name ?? "this display"}?`} >

The screen stops showing content within a few seconds and returns to its pairing code. This cannot be undone: to use it again, pair it from the screen with a new code.

); }