Insolitum Developers
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

src/app/page.tsx
'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

On this page