import { useState } from "react"; import { useMutation, useQueryClient, type QueryClient, type UseMutationOptions } from "@tanstack/react-query"; /** Which form is open: none, the create form, or the edit form for one row. */ export type CrudFormState = { mode: "create" } | { mode: "edit"; entity: Entity }; /** The row a confirmation dialog is open for, and the message it shows. */ export interface PendingDelete { entity: Entity; message: string; } type MutationFactory = (queryClient: QueryClient) => UseMutationOptions; export interface CrudFormSpec { create: MutationFactory; update: MutationFactory<{ id: number; input: Input }>; remove: MutationFactory; /** The message the confirmation dialog shows before a delete. */ confirmDelete: (entity: Entity) => string; } /** * The create/edit/delete plumbing shared by the local DNS tabs (ruling 10): * three mutations, the open-form state, the pending-delete state and the * handlers. The field JSX, the table and the confirmation dialog stay in the * caller: `onDelete` opens the confirmation instead of deciding anything, so * the hook never reaches for `window.confirm` (ruling 5). */ export function useCrudForm(spec: CrudFormSpec) { const queryClient = useQueryClient(); const create = useMutation(spec.create(queryClient)); const update = useMutation(spec.update(queryClient)); const remove = useMutation(spec.remove(queryClient)); const [form, setForm] = useState | null>(null); const [pendingDelete, setPendingDelete] = useState | null>(null); function openForm(next: CrudFormState) { create.reset(); update.reset(); setForm(next); } function closeForm() { setForm(null); } function onSubmit(input: Input) { if (form === null) return; if (form.mode === "create") { create.mutate(input, { onSuccess: () => setForm(null) }); } else { update.mutate({ id: form.entity.id, input }, { onSuccess: () => setForm(null) }); } } function onDelete(entity: Entity) { setPendingDelete({ entity, message: spec.confirmDelete(entity) }); } function confirmPendingDelete() { if (pendingDelete === null) return; remove.mutate(pendingDelete.entity.id); setPendingDelete(null); } function cancelPendingDelete() { setPendingDelete(null); } return { create, update, remove, form, openForm, closeForm, onSubmit, onDelete, pendingDelete, confirmPendingDelete, cancelPendingDelete, }; }