import { Button, Input, Modal, ModalBody, ModalContent, ModalFooter, ModalHeader, Textarea, } from "@heroui/react"; import { useAtom } from "jotai"; import { useEffect } from "react"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; import { IconCamera, IconInfoCircle } from "@tabler/icons-react"; import { commitTargetAtom, viewAtom } from "../state/atoms"; import { useCommitMachine } from "../lib/queries"; import { useToast } from "../state/toast"; import { useSetAtom } from "jotai"; // A snapshot name doubles as a directory + flavor key, so keep it filesystem-safe. const schema = z.object({ name: z .string() .min(1, "A name is required") .max(40, "Too long") .regex(/^[a-zA-Z0-9._-]+$/, "Letters, digits, . _ - only"), description: z.string().max(120, "Keep it under 120 chars"), }); type FormValues = z.infer; /** * Snapshot a machine's current state into a reusable flavor (`bsdkrun commit`). * Opened from a machine row / detail drawer via `commitTargetAtom`. */ export default function CommitDialog() { const [target, setTarget] = useAtom(commitTargetAtom); const setView = useSetAtom(viewAtom); const commit = useCommitMachine(); const toast = useToast(); const isBsd = target ? target.kind !== "linux" : false; const { register, handleSubmit, reset, formState: { errors, isSubmitting }, } = useForm({ resolver: zodResolver(schema), defaultValues: { name: "", description: "" }, }); // Reset the form whenever a new machine is targeted. useEffect(() => { if (target) reset({ name: "", description: "" }); }, [target, reset]); const close = () => setTarget(null); const onSubmit = handleSubmit(async (v) => { if (!target) return; try { await commit.mutateAsync({ id: target.id, name: v.name, description: v.description, }); toast("success", `Snapshot “${v.name}” saved`, "Find it under Flavors → Your snapshots"); setTarget(null); setView("flavors"); } catch (e) { toast("error", "Snapshot failed", String(e)); } }); return ( { if (!isSubmitting) close(); }} size="md" backdrop="opaque" shouldBlockScroll={false} classNames={{ base: "border border-white/10 bg-content1" }} >
Snapshot machine

Save the current state of{" "} {target?.label} as a reusable flavor. Boot fresh machines from it any time — packages, files and all.