"use client"; import { useState } from "react"; import { Button } from "./Button"; import { Field } from "./Field"; import { Note } from "./Panel"; import { ApiError, type RenameResult } from "@/lib/api"; import { baseSlug, hostFor, slugError } from "@/lib/slug"; /* * The rename control, and only the control — the same shape as RelinkPanel: an * input that expands in place rather than a modal, because this app has no modal * and one action with one field does not need one. * * The host preview is drawn from lib/slug.ts, a mirror of the Go rules. It can * disagree with the server; the 409 that comes back is the answer that counts. * * movesHost is what separates a rename that moves a DNS host from one that only * changes a label. Self-hosted instances and unprovisioned cloud placeholders * have no address, so every word about old links breaking and signing in again * is false for them — and a preview host they will never live at is worse than * no preview at all. */ export function RenamePanel({ currentName, currentSlug, movesHost, onRename, }: { currentName: string; currentSlug: string; movesHost: boolean; onRename: (name: string) => Promise; }) { const [open, setOpen] = useState(false); const [value, setValue] = useState(currentName); const [error, setError] = useState(); const [busy, setBusy] = useState(false); const [done, setDone] = useState(); const name = value.trim(); const derived = baseSlug(name); const invalid = slugError(name); // A cosmetic edit that lands on the same slug is still a rename worth doing — // the name is what the customer reads. Only an empty or unchanged name is // nothing to submit. const unchanged = name === currentName.trim(); async function submit() { setError(undefined); setBusy(true); try { const res = await onRename(name); setDone(res); setOpen(false); // The input is prefilled with the current name, and the current name // is now this one. Leaving the old text in would make the next open // look like an edit already in progress. setValue(res.name); } catch (err) { setError(err instanceof ApiError ? err.message : "Rename failed. Try again."); } finally { setBusy(false); } } // The note sits ABOVE the control rather than replacing it. A rename is not // a one-shot action — a customer who mistypes the new name needs the panel // back, and returning early here left them with a success message and no way // to correct it short of a reload. return (
{done && (movesHost ? ( This instance is now {done.name}, at{" "} {hostFor(done.slug)}. The old address has stopped working, and your sign-in does not follow it — you will need to sign in again there. Open {hostFor(done.slug)} → ) : ( This instance is now {done.name}. ))} {open && ( setValue(e.target.value)} error={error ?? (name ? invalid : undefined)} hint={ movesHost && name && !invalid ? ( <> Moves to {hostFor(derived)} {derived === currentSlug && " — the address does not change"} ) : ( "Letters and digits; everything else becomes a hyphen." ) } /> )}
{open && movesHost && ( Anyone signed in will need to sign in again at the new address, and links to the old one stop working. )}
); }