Examples
CRUD Form Example
Complete CRUD form example with dialog, validation, and error handling.
Complete Example
Dialog Component
// src/app/(secure)/entities/components/entity-dialog.tsx
'use client';
import { useCallback } from 'react';
import { Popup, useCurrentRowSync, useIsStoreDirty, useIsStorePosting, showSuccess, showError } from '@wayvo-ai/core/ui';
import { TextInput, TextareaInput } from '@wayvo-ai/core/ui';
import type { Store } from '@wayvo-ai/core/common';
import type { Entity } from '@/lib/common/ds/types/Entity';
interface Props {
open: boolean;
onOpenChange: (open: boolean) => void;
store: Store<Entity>;
editingRow?: Entity | null;
}
export function EntityDialog({ open, onOpenChange, store, editingRow }: Props) {
const row = useCurrentRowSync(store);
const isDirty = useIsStoreDirty(store);
const isPosting = useIsStorePosting(store);
const handleSave = useCallback(async () => {
try {
if (!row?.name?.trim()) {
showError('Name is required');
return;
}
const success = await store.save();
if (success) {
showSuccess(editingRow ? 'Entity updated' : 'Entity created');
onOpenChange(false);
}
} catch (error) {
showError('Failed to save entity');
}
}, [store, row, editingRow, onOpenChange]);
const handleClose = useCallback(() => {
store.resetStore();
onOpenChange(false);
}, [store, onOpenChange]);
if (!open) return null;
return (
<Popup
title={editingRow ? 'Edit Entity' : 'Add Entity'}
onClose={handleClose}
width={520}
height={430}
footer={
<>
<Button variant="outline" onClick={handleClose} disabled={isPosting}>
Cancel
</Button>
<Button onClick={handleSave} disabled={isPosting || !isDirty || !row}>
{isPosting && <Loader2 className="mr-2 size-4 animate-spin" />}
Save
</Button>
</>
}
>
{row && (
<div className="grid gap-4">
<TextInput
label="Name"
value={row.name || ''}
onChange={(value) => store.setValue('name', value)}
required
/>
<TextareaInput
label="Description"
value={row.description || ''}
onChange={(value) => store.setValue('description', value)}
rows={4}
/>
</div>
)}
</Popup>
);
}
Usage in Page Content
// src/app/(secure)/entities/page-content.tsx
'use client';
import { useState, useCallback } from 'react';
import { PageLayoutTemplate } from '@wayvo-ai/core/ui';
import { useEntityStore } from './hooks/use-store';
import useTableColumns from './hooks/use-table-columns';
import useSmartSearchColumns from './hooks/smart-search-columns';
import { EntityDialog } from './components/entity-dialog';
import type { Entity } from '@/lib/common/ds/types/Entity';
export default function EntityPageContent() {
const store = useEntityStore();
const tableColumns = useTableColumns(store);
const smartSearchColumns = useSmartSearchColumns();
const [isDialogOpen, setIsDialogOpen] = useState(false);
const [editingRow, setEditingRow] = useState<Entity | null>(null);
const handleAdd = useCallback(() => {
store.createNew({ partialRecord: { status: 'draft' } });
setEditingRow(null);
setIsDialogOpen(true);
}, [store]);
const handleEdit = useCallback((row: Entity) => {
store.setCurrentRow(row);
setEditingRow(row);
setIsDialogOpen(true);
}, [store]);
return (
<>
<PageLayoutTemplate
title="Entities"
store={store}
smartSearchColumns={smartSearchColumns}
tableColumns={tableColumns}
pageId="entity-page"
itemId="entity"
onRowClick={handleEdit}
addNewButtonText="Add Entity"
onAddNew={handleAdd}
/>
<EntityDialog
open={isDialogOpen}
onOpenChange={setIsDialogOpen}
store={store}
editingRow={editingRow}
/>
</>
);
}
Key Features
- ✅ Form validation
- ✅ Loading states
- ✅ Error handling
- ✅ Success feedback
- ✅ Proper dialog initialization
Next Steps
- Filtered Table Example - Advanced filtering
- Complex Workflow Example - Multiple stores