import { BadgeCheck, ShieldAlert, ShieldQuestion } from "lucide-react"; import { type FC, useEffect, useState } from "react"; import type { StoreEntry } from "@/api/store"; import { Button } from "@/components/ui/button"; import { Checkbox } from "@/components/ui/checkbox"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { m } from "@/paraglide/messages"; // Friction proportional to trust. The three install paths deliberately do NOT share a confirm // button: a verified install is one click on a plain dialog, an external one makes you read that // unom didn't review the code and names who did curate it, and a raw package spec makes you retype // the spec and tick a box. The last one is never reachable by accident — no card, no button on a // listing, only the small footer link on Browse. /** * Tiers 1 and 2: install a curated catalog entry. Verified entries get an ordinary confirm; an * entry from an operator-added source gets the warning treatment and names its source, because * "pinned and integrity-checked" is not the same claim as "reviewed". */ export const InstallDialog: FC<{ /** The entry being confirmed, or null when the dialog is closed. */ entry: StoreEntry | null; onCancel: () => void; onConfirm: (entry: StoreEntry) => void; isPending: boolean; }> = ({ entry, onCancel, onConfirm, isPending }) => { const external = entry?.tier === "external"; return ( !open && onCancel()}> {entry && ( {external ? ( ) : ( )} {external ? m.store_install_external_title({ title: entry.title }) : m.store_install_title({ title: entry.title })} {external ? m.store_install_external_body({ version: entry.version, source: entry.source, }) : m.store_install_verified_body({ version: entry.version })}

{entry.pkg}@{entry.version}

{external && (

{m.store_install_external_note()}

)} {/* Red is reserved for the raw-spec install; an external one escalates through its copy and its amber panel, not by borrowing the danger colour. */}
)}
); }; /** * Tier 3: install a raw package spec. No catalog, no review, no pinning — so the dialog spells out * exactly what that means and asks for two independent confirmations (retype the spec, tick the * box) before it will even enable its button. */ export const SpecInstallDialog: FC<{ open: boolean; onCancel: () => void; onConfirm: (spec: string, password: string) => void; isPending: boolean; /** Set when the BFF rejected the password (401), so the dialog can say so and stay open. */ wrongPassword?: boolean; }> = ({ open, onCancel, onConfirm, isPending, wrongPassword }) => { const [spec, setSpec] = useState(""); const [echo, setEcho] = useState(""); const [accepted, setAccepted] = useState(false); const [password, setPassword] = useState(""); // Every confirmation is cleared on exit, cancel AND confirm alike: reopening this dialog must // never find it pre-armed with the last spec, a ticked box, or a typed password. // // Clearing hangs off `open` rather than off the two exit paths, because only one of them runs in // this component: cancel goes through `onCancel`, but SUCCESS is the parent flipping `open`, and // the dialog stays mounted either way. Setter identities are stable, so the effect needs no other // dependency — a `reset()` helper in the list would be a new function every render. useEffect(() => { if (open) return; setSpec(""); setEcho(""); setAccepted(false); setPassword(""); }, [open]); const wanted = spec.trim(); // Every gate must pass: the retyped spec matches exactly, the box is ticked, and the console // password is re-entered (the BFF verifies it — a session cookie alone must not run new code). const ready = wanted.length > 0 && echo.trim() === wanted && accepted && password.length > 0; return ( !next && onCancel()}> {m.store_spec_title()} {m.store_spec_lead()}

{m.store_spec_permanent()}

setSpec(e.target.value)} />

{m.store_spec_field_help()}

setEcho(e.target.value)} />
setPassword(e.target.value)} />

{m.store_spec_password_help()}

{wrongPassword && (

{m.update_apply_wrong_password()}

)}
); };