VVibeFlui

Advanced Form and Table

Generated UI previewFORM + TABLE

Order operations

0 selected
#ActionsCustomerStatusTotal
1
ORD-1001Ada Supply Co.paid$1,290.00
2
ORD-1002Grace Studiopending$840.00
Page 1 of 1

Create, edit, and delete all run against local React state here — the email validator only accepts example.test addresses, Status options load from a registry option loader, and both Edit and Delete are gated behind the granted orders.write / orders.delete permissions.

This tutorial combines an advanced table, advanced form, CRUD operations, permissions, custom messages, validation, transforms, dynamic options, delete confirmation, and backend response shapes.

Use this as the production-like CRUD example for order operations.

File structure

TXT
app/
  orders/
    page.tsx
schemas/
  orders/
    order.resource.schema.ts
components/
  vibeflui/
    renderers/
      orders/
        OrderStatusBadge.tsx
        OrderTotalCell.tsx
lib/
  vibeflui/
    examples/
      order-crud-actions.ts
    registry.tsx

Schema file

schemas/orders/order.resource.schema.ts

TS
export const orderResourceSchema = {
  resource: "orders",
  title: "Order operations",
  mode: "static",
  identity: { key: "id", param: "id" },
  table: {
    columns: [
      { key: "orderNo", label: "Order", searchable: true, sortable: true },
      { key: "customer", label: "Customer", searchable: true },
      { key: "status", label: "Status", renderer: "OrderStatusBadge" },
      { key: "total", label: "Total", renderer: "OrderTotalCell" }
    ],
    search: { enabled: true },
    filters: [{ key: "status", label: "Status", type: "select", optionLoader: "orders.statuses" }],
    pagination: { enabled: true, pageSize: 10 },
    rowSelection: { enabled: true, mode: "multiple" }
  },
  form: {
    mode: "modal",
    width: "2xl",
    grid: { columns: { base: 1, md: 2 } },
    fields: [
      { name: "orderNo", label: "Order number", required: true, transformer: "trim" },
      { name: "customer", label: "Customer", required: true, transformer: "trim" },
      { name: "email", label: "Customer email", type: "email", required: true, validator: "orders.email" },
      { name: "status", label: "Status", type: "select", optionLoader: "orders.statuses" },
      { name: "total", label: "Total", type: "number", min: 0 }
    ]
  },
  detail: {
    enabled: true,
    mode: "drawer",
    fields: ["orderNo", "customer", "email", "status", "total"]
  },
  actions: {
    create: { enabled: true, label: "Create order" },
    edit: { enabled: true, permission: "orders.write" },
    delete: {
      enabled: true,
      variant: "danger",
      permission: "orders.delete",
      confirm: {
        title: "Delete order?",
        description: "This removes the order from the local list.",
        secondConfirm: {
          title: "Confirm deletion",
          confirmLabel: "Delete order"
        }
      }
    },
    row: [
      { key: "invoice", label: "Invoice", icon: "mdi:file-document-outline", handler: "orders.invoice" }
    ],
    bulk: [
      { key: "bulk-export", label: "Export selected", handler: "orders.bulkExport" }
    ]
  },
  messages: {
    defaults: {
      createSuccess: "Order created.",
      updateSuccess: "Order updated.",
      deleteSuccess: "Order deleted."
    }
  },
  feedback: {
    mode: "modal"
  },
  registry: {
    renderers: ["OrderStatusBadge", "OrderTotalCell"],
    validators: ["orders.email"],
    transformers: ["trim"],
    optionLoaders: ["orders.statuses"],
    actionHandlers: ["orders.invoice", "orders.bulkExport"],
    permissionResolvers: ["orders.write", "orders.delete"]
  }
} as const;

Renderer components

components/vibeflui/renderers/orders/OrderStatusBadge.tsx

TSX
export function OrderStatusBadge({ value }: { value?: unknown }) {
  return <span>{String(value ?? "-")}</span>;
}

components/vibeflui/renderers/orders/OrderTotalCell.tsx

TSX
export function OrderTotalCell({ value }: { value?: unknown }) {
  return (
    <span>
      {new Intl.NumberFormat(undefined, {
        style: "currency",
        currency: "USD"
      }).format(Number(value ?? 0))}
    </span>
  );
}

Registry file

lib/vibeflui/registry.tsx

TSX
import { createRegistry } from "@vibeflui/core";
import { OrderStatusBadge } from "@/components/vibeflui/renderers/orders/OrderStatusBadge";
import { OrderTotalCell } from "@/components/vibeflui/renderers/orders/OrderTotalCell";

export const vibefluiRegistry = createRegistry({
  renderers: {
    OrderStatusBadge,
    OrderTotalCell
  },
  validators: {
    "orders.email": async (value) =>
      String(value ?? "").endsWith("@example.test") ? undefined : "Use an example.test email"
  },
  transformers: {
    trim: (value) => (typeof value === "string" ? value.trim() : value)
  },
  optionLoaders: {
    "orders.statuses": async () => [
      { label: "Paid", value: "paid" },
      { label: "Pending", value: "pending" },
      { label: "Failed", value: "failed" }
    ]
  },
  permissionResolvers: {
    "orders.write": ({ permissionContext }) => Boolean(permissionContext?.permissions?.includes("orders.write")),
    "orders.delete": ({ permissionContext }) => Boolean(permissionContext?.permissions?.includes("orders.delete"))
  },
  actionHandlers: {
    "orders.invoice": async ({ row }) => ({ status: true, code: 200, message: `Invoice opened for ${String(row?.orderNo ?? "order")}` }),
    "orders.bulkExport": async ({ values }) => ({ status: true, code: 200, message: `${Array.isArray(values?.selectedRows) ? values.selectedRows.length : 0} orders exported` })
  }
});

Reusable action helpers

lib/vibeflui/examples/order-crud-actions.ts

TS
import type { Dispatch, SetStateAction } from "react";
import type { FluiKitSubmitContext } from "@vibeflui/core";

export type OrderRow = {
  id: string;
  orderNo: string;
  customer: string;
  email: string;
  status: string;
  total: number;
};

export const initialOrders: OrderRow[] = [
  { id: "ord_1", orderNo: "ORD-1001", customer: "Ada Supply Co.", email: "ada@example.test", status: "paid", total: 1290 },
  { id: "ord_2", orderNo: "ORD-1002", customer: "Grace Studio", email: "grace@example.test", status: "pending", total: 840 }
];

export function createOrderCrudActions(setOrders: Dispatch<SetStateAction<OrderRow[]>>) {
  return {
    createOrder: async (values: Record<string, unknown>) => {
      const order = mapValuesToOrder(values);
      setOrders((current) => [...current, order]);
      return { status: true, code: 200, message: "Order created" };
    },
    updateOrder: async (values: Record<string, unknown>, context: FluiKitSubmitContext) => {
      const id = String(context.row?.id ?? "");
      const nextOrder = mapValuesToOrder(values, id);
      setOrders((current) => current.map((order) => (order.id === id ? nextOrder : order)));
      return { status: true, code: 200, message: "Order updated" };
    },
    deleteOrder: async (row: Record<string, unknown>) => {
      const id = String(row.id ?? "");
      setOrders((current) => current.filter((order) => order.id !== id));
      return { status: true, code: 200, message: "Order deleted" };
    }
  };
}

function mapValuesToOrder(values: Record<string, unknown>, id = crypto.randomUUID()): OrderRow {
  return {
    id,
    orderNo: String(values.orderNo ?? ""),
    customer: String(values.customer ?? ""),
    email: String(values.email ?? ""),
    status: String(values.status ?? "pending"),
    total: Number(values.total ?? 0)
  };
}

Page file

app/orders/page.tsx

TSX
"use client";

import { useState } from "react";
import { FluiKit, FluiKitProvider } from "@vibeflui/core";
import { orderResourceSchema } from "@/schemas/orders/order.resource.schema";
import { vibefluiRegistry } from "@/lib/vibeflui/registry";
import { createOrderCrudActions, initialOrders } from "@/lib/vibeflui/examples/order-crud-actions";

export default function OrdersPage() {
  const [rows, setRows] = useState(initialOrders);
  const orderActions = createOrderCrudActions(setRows);

  return (
    <FluiKitProvider
      registry={vibefluiRegistry}
      permissionContext={{ permissions: ["orders.write", "orders.delete"] }}
    >
      <FluiKit
        schema={orderResourceSchema}
        data={rows}
        onCreate={orderActions.createOrder}
        onUpdate={orderActions.updateOrder}
        onDelete={orderActions.deleteOrder}
      />
    </FluiKitProvider>
  );
}

Response samples

JSON
{ "status": true, "code": 200, "message": "Order updated" }
JSON
{ "status": false, "code": 409, "message": "Order is already fulfilled" }

This response maps to warning feedback because 409 is a 400..499 code.

JSON
{ "status": false, "code": 500, "message": "Unable to update order" }