Examples
Example: CRUD Task Manager
Complete example of a task management module with create, read, update, and delete operations using useModuleApi.
Example: CRUD Task Manager
A complete task management module with multi-tenant data isolation.
Database Setup
CREATE TABLE taskmanager_tasks (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
organization_id UUID NOT NULL REFERENCES organizations(id),
title TEXT NOT NULL,
description TEXT,
status TEXT DEFAULT 'todo' CHECK (status IN ('todo', 'in_progress', 'done')),
priority TEXT DEFAULT 'medium' CHECK (priority IN ('low', 'medium', 'high')),
assigned_to UUID REFERENCES auth.users(id),
created_by UUID REFERENCES auth.users(id),
created_at TIMESTAMPTZ DEFAULT now(),
updated_at TIMESTAMPTZ DEFAULT now()
);
CREATE INDEX idx_taskmanager_tasks_org
ON taskmanager_tasks(organization_id);
ALTER TABLE taskmanager_tasks ENABLE ROW LEVEL SECURITY;
CREATE POLICY "tenant_isolation" ON taskmanager_tasks
FOR ALL
USING (
organization_id IN (
SELECT organization_id FROM user_organizations
WHERE user_id = auth.uid()
)
);Task List Page
'use client';
import { useEffect, useState } from 'react';
import { useShellAuth } from '@/lib/shell-auth';
import { useModuleApi } from '@/hooks/useModuleApi';
import { Plus, CheckCircle2, Circle, Clock, Trash2 } from 'lucide-react';
interface Task {
id: string;
title: string;
description: string | null;
status: 'todo' | 'in_progress' | 'done';
priority: 'low' | 'medium' | 'high';
created_at: string;
}
const statusIcons = {
todo: Circle,
in_progress: Clock,
done: CheckCircle2,
};
const priorityColors = {
low: 'text-gray-400',
medium: 'text-yellow-400',
high: 'text-red-400',
};
export default function TasksPage() {
const { isAuthenticated, isLoading } = useShellAuth();
const { query, insert, update, remove } = useModuleApi();
const [tasks, setTasks] = useState<Task[]>([]);
const [title, setTitle] = useState('');
async function loadTasks() {
const { data } = await query('taskmanager_tasks')
.select('*')
.order('created_at', { ascending: false });
if (data) setTasks(data);
}
useEffect(() => {
if (isAuthenticated) loadTasks();
}, [isAuthenticated]);
async function addTask() {
if (!title.trim()) return;
await insert('taskmanager_tasks', {
title,
status: 'todo',
priority: 'medium',
});
setTitle('');
loadTasks();
}
async function toggleStatus(task: Task) {
const next = task.status === 'todo'
? 'in_progress'
: task.status === 'in_progress'
? 'done'
: 'todo';
await update('taskmanager_tasks', { status: next }, { id: task.id });
loadTasks();
}
async function deleteTask(id: string) {
await remove('taskmanager_tasks', { id });
loadTasks();
}
if (isLoading) return <p className="p-6 text-gray-400">Loading...</p>;
if (!isAuthenticated) return null;
return (
<div className="p-6 max-w-2xl">
<h1 className="text-2xl font-bold text-white mb-6">Tasks</h1>
{/* Add task form */}
<div className="flex gap-2 mb-6">
<input
value={title}
onChange={(e) => setTitle(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && addTask()}
placeholder="New task..."
className="flex-1 rounded-lg border border-gray-700 bg-gray-800 px-4 py-2.5 text-white placeholder-gray-500 focus:border-purple-500 focus:outline-none"
/>
<button
onClick={addTask}
className="inline-flex items-center gap-2 rounded-lg bg-purple-600 px-4 py-2.5 text-white font-medium hover:bg-purple-500"
>
<Plus className="h-4 w-4" />
Add
</button>
</div>
{/* Task list */}
<div className="space-y-2">
{tasks.map((task) => {
const StatusIcon = statusIcons[task.status];
return (
<div
key={task.id}
className="flex items-center gap-3 rounded-lg border border-gray-700 bg-gray-800/50 p-4"
>
<button onClick={() => toggleStatus(task)}>
<StatusIcon
className={`h-5 w-5 ${
task.status === 'done' ? 'text-green-400' : 'text-gray-400'
}`}
/>
</button>
<span
className={`flex-1 ${
task.status === 'done' ? 'line-through text-gray-500' : 'text-white'
}`}
>
{task.title}
</span>
<span className={`text-xs ${priorityColors[task.priority]}`}>
{task.priority}
</span>
<button
onClick={() => deleteTask(task.id)}
className="text-gray-500 hover:text-red-400"
>
<Trash2 className="h-4 w-4" />
</button>
</div>
);
})}
{tasks.length === 0 && (
<p className="text-center text-gray-500 py-8">
No tasks yet. Add your first task above.
</p>
)}
</div>
</div>
);
}This example demonstrates:
- Multi-tenant CRUD with
useModuleApi() - RLS-protected database table
- Dark theme UI following Universe guidelines
- Reactive state management