fixes
Server Deploy / deploy (push) Successful in 2m24s

This commit is contained in:
2026-07-27 15:59:45 +01:00
parent fdfe8e8e46
commit 66140aaf58
8 changed files with 97 additions and 14 deletions
@@ -7,7 +7,16 @@ import { Field } from "@/components/Field";
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
export function LinkForm({ onLinked }: { onLinked: (instanceId: string) => void }) {
export function LinkForm({
onLinked,
claimId,
}: {
onLinked: (instanceId: string) => void;
// When set, this is a PAID placeholder awaiting its real install UUID: claim
// it in place rather than creating a fresh (Free) instance. The name was
// chosen at checkout, so it is not asked for again.
claimId?: string;
}) {
const [id, setId] = useState("");
const [name, setName] = useState("");
const [error, setError] = useState<string | undefined>();
@@ -28,7 +37,9 @@ export function LinkForm({ onLinked }: { onLinked: (instanceId: string) => void
setBusy(true);
setError(undefined);
try {
const inst = await api.link(value, name.trim());
const inst = claimId
? await api.claimLink(claimId, value)
: await api.link(value, name.trim());
onLinked(inst.instance_id);
} catch (err) {
setError(
@@ -58,12 +69,14 @@ export function LinkForm({ onLinked }: { onLinked: (instanceId: string) => void
</>
}
/>
<Field
label="Name it (optional)"
value={name}
onChange={(e) => setName(e.target.value)}
hint="So you can tell it apart from your other installs."
/>
{!claimId && (
<Field
label="Name it (optional)"
value={name}
onChange={(e) => setName(e.target.value)}
hint="So you can tell it apart from your other installs."
/>
)}
<Button type="submit" disabled={busy} className="justify-self-start">
{busy ? "Linking…" : "Link and issue licence"}
</Button>
@@ -1,6 +1,6 @@
"use client";
import { useRouter } from "next/navigation";
import { useRouter, useSearchParams } from "next/navigation";
import { useQueryClient } from "@tanstack/react-query";
import { LinkForm } from "./LinkForm";
import { PageHeader } from "@/components/PageHeader";
@@ -8,6 +8,9 @@ import { PageHeader } from "@/components/PageHeader";
export default function LinkPage() {
const router = useRouter();
const qc = useQueryClient();
// A paid placeholder passes its id here so its install is claimed in place
// rather than a second, Free instance being created alongside it.
const claimId = useSearchParams().get("claim") ?? undefined;
return (
<div className="grid max-w-2xl gap-6">
@@ -17,6 +20,7 @@ export default function LinkPage() {
subtitle="Every licence is tied to one install, so we need its ID before we can issue yours. Paste it below and your licence is ready on the next screen."
/>
<LinkForm
claimId={claimId}
onLinked={(instanceId) => {
qc.invalidateQueries({ queryKey: ["account"] });
// Straight to the download, not back to a list: the licence is
+11 -1
View File
@@ -221,7 +221,17 @@ export function InstanceRecord({
<div className="flex flex-wrap items-center gap-2.5">
{state === "none" ? (
<LinkButton href="/instances/link">Link an install</LinkButton>
// A paid placeholder (awaiting_link) must CLAIM its install, not
// create a second Free instance beside it — so pass its id.
<LinkButton
href={
instance.status === "awaiting_link"
? `/instances/link?claim=${instance.instance_id}`
: "/instances/link"
}
>
Link an install
</LinkButton>
) : cloud && instance.slug ? (
<>
<LinkButton