zebflow 0.10.4

Reactive web automation platform — pipelines, templates, and real-time collaboration in a single binary.
Documentation
import { useState } from "zeb/react";
import { requestJson } from "@/components/lib/http";
import CreateTableDialog from "@/components/db/create-table-dialog";
import { DEFAULT_ATTRIBUTE, attributesPayload } from "@/components/db/attribute-editor";
import { DEFAULT_EDGE } from "@/components/db/edge-ends-fields";

const READY = "Name the table, then add its columns.";

/**
 * Defining a new table, or a new edge table.
 *
 * Owns the form and everything it is part-way through. Whether the dialog is
 * open stays with the page, because two separate buttons open it — the one in
 * the tree and the one on the schema tab — and neither belongs to the other.
 * `catalog` is the tables an edge may reach and the reload after creating.
 */
export default function CreateTablePanel({ open, onOpenChange, tablesApi, types, caps, catalog }) {
  const [slug, setSlug] = useState("");
  const [keyDefault, setKeyDefault] = useState("");
  const [attributes, setAttributes] = useState([{ ...DEFAULT_ATTRIBUTE }]);
  const [edge, setEdge] = useState({ ...DEFAULT_EDGE });
  const [status, setStatus] = useState(READY);
  const [busy, setBusy] = useState(false);

  async function submit(event) {
    event?.preventDefault?.();
    const table = String(slug || "").trim();
    if (!table) {
      setStatus("Error · A table name is required.");
      return;
    }
    if (edge.enabled && !(edge.source_table && edge.destination_table && edge.source.trim() && edge.destination.trim())) {
      setStatus("Error · An edge table names both ends: a table and a column for each.");
      return;
    }

    setBusy(true);
    setStatus("Creating table…");
    try {
      const payload = await requestJson(tablesApi, {
        method: "POST",
        body: JSON.stringify({
          table,
          attributes: attributesPayload(attributes),
          key_default: edge.enabled ? "" : keyDefault,
          edge: edge.enabled
            ? {
                source: edge.source.trim(),
                source_table: edge.source_table,
                destination: edge.destination.trim(),
                destination_table: edge.destination_table,
                label: edge.label.trim(),
                one_per_pair: edge.one_per_pair,
              }
            : null,
        }),
      });
      const created = String(payload?.table?.table || table).trim();
      setStatus(`Created · ${created}`);
      onOpenChange(false);
      await catalog.reload(created);
    } catch (error) {
      setStatus(`Error · ${String(error?.message || error)}`);
    } finally {
      setBusy(false);
    }
  }

  return (
    <CreateTableDialog
      types={types}
      caps={caps}
      tables={catalog.tables}
      open={open}
      onOpenChange={(next) => {
        if (next) {
          // A fresh form each time it is opened; the previous attempt's name
          // and status are not an answer to this one.
          setStatus(READY);
          setSlug("");
          setKeyDefault("");
          setAttributes([{ ...DEFAULT_ATTRIBUTE }]);
          setEdge({ ...DEFAULT_EDGE });
        }
        onOpenChange(next);
      }}
      form={{ slug, setSlug, keyDefault, setKeyDefault, attributes, setAttributes, edge, setEdge, status, busy, submit }}
    />
  );
}