"use client"; // The add-an-archive control: paste an archive URL, it validates against the // federation contract, and on success the archive joins "Archives You Added". // Written as direction — validating → success, or a plain reason it couldn't // be read. Self-contained: owns its input + status and commits to the shared // registry. The button is the family's CTA — ink on ground, not a coloured // fill: foreground on background, whatever the base (tokens.css). import { useState } from "react"; import { useRegistry, validateSite, } from "yt-dlp-transcript-common/components/siteRegistry"; type AddState = | { status: "idle" } | { status: "validating" } | { status: "added"; title: string } | { status: "error"; message: string }; export default function AddArchive() { const { addSite } = useRegistry(); const [url, setUrl] = useState(""); const [state, setState] = useState({ status: "idle" }); async function submit() { if (state.status === "validating" || !url.trim()) return; setState({ status: "validating" }); const result = await validateSite(url); if (result.ok) { addSite(result.site); setUrl(""); setState({ status: "added", title: result.site.siteTitle }); } else { setState({ status: "error", message: result.reason }); } } const busy = state.status === "validating"; return (
{ e.preventDefault(); void submit(); }} noValidate className="flex flex-wrap items-center gap-2" > { setUrl(e.target.value); if (state.status !== "idle" && state.status !== "validating") { setState({ status: "idle" }); } }} className="w-full min-w-0 flex-1 rounded-md border border-border bg-background px-3 py-2 text-base text-foreground sm:w-auto sm:min-w-64 sm:text-sm placeholder:text-muted-foreground/70 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand" aria-label="Archive URL" />
{state.status === "error" && (

{state.message}

)} {state.status === "added" && (

Added {state.title}.

)}
); }