TaskMaster

A gamified task management application built with ao-forge, demonstrating basic AO process integration with Next.js. This example shows the default project structure generated by ao-forge init.

Overview

TaskMaster is a decentralized task management system that includes:

  • Gamified Task Management - Earn points for creating and completing tasks
  • AO Process Integration - Lua-based smart contract for task storage
  • Modern UI - Next.js with TypeScript and Tailwind CSS
  • Wallet Integration - ArConnect wallet support

Project Structure

The default ao-forge project includes:

my-taskmaster/
├── README.md
├── package.json
├── tsconfig.json
├── next.config.ts
├── postcss.config.mjs
├── eslint.config.mjs
├── ao/
│   ├── README.md
│   └── task-process.lua          # Main AO process
├── app/
│   ├── components/
│   │   ├── CreateTask.tsx        # Task creation form
│   │   ├── TaskList.tsx          # Task display component
│   │   ├── PointsDisplay.tsx     # Points counter
│   │   ├── Leaderboard.tsx       # Global leaderboard
│   │   ├── Navbar.tsx            # Navigation
│   │   └── WalletConnect.tsx     # Wallet connection
│   ├── lib/
│   │   └── aoconnect.ts          # AO client library
│   ├── page.tsx                  # Main page
│   ├── layout.tsx                # Root layout
│   ├── globals.css               # Global styles
│   └── favicon.ico
└── public/

Key Features

🎮 Gamification

  • +10 points for creating tasks
  • +25 points for completing tasks
  • Global leaderboard with top 10 users
  • Priority levels (high, medium, low)

🔗 AO Integration

  • Decentralized storage on Arweave
  • Lua smart contracts for task logic
  • Message-based communication with AO processes
  • Wallet authentication via ArConnect

AO Process (task-process.lua)

The core AO process handles all task operations:

-- TaskMaster AO Process
local json = require("json")

-- Initialize state
local Tasks = {}
local Points = {}
local TaskCounter = 0

-- Helper functions
local function safeDecode(data)
  local ok, result = pcall(json.decode, data)
  if ok then return result else return nil end
end

local function getUserPoints(userId)
  return Points[userId] or 0
end

local function awardPoints(userId, amount)
  Points[userId] = getUserPoints(userId) + amount
  return Points[userId]
end

-- Create Task Handler
Handlers.add(
  "CreateTask",
  Handlers.utils.hasMatchingTag("Action", "CreateTask"),
  function(msg)
    local taskData = safeDecode(msg.Data)
    if not taskData then
      return msg.reply({
        success = false,
        message = "Invalid JSON data"
      })
    end

    TaskCounter = TaskCounter + 1
    local taskId = tostring(TaskCounter)

    local task = {
      id = taskId,
      title = taskData.title or "Untitled Task",
      description = taskData.description or "",
      completed = false,
      created = os.time(),
      owner = msg.From,
      priority = taskData.priority or "medium"
    }

    Tasks[taskId] = task
    local newPoints = awardPoints(msg.From, 10)

    msg.reply({
      success = true,
      message = "Task created successfully! +10 points",
      data = {
        task = task,
        points = newPoints
      }
    })
  end
)

-- Complete Task Handler
Handlers.add(
  "CompleteTask",
  Handlers.utils.hasMatchingTag("Action", "CompleteTask"),
  function(msg)
    local taskData = safeDecode(msg.Data)
    local taskId = taskData and taskData.taskId

    if not taskId then
      return msg.reply({
        success = false,
        message = "TaskId not provided"
      })
    end

    local task = Tasks[taskId]
    if not task or task.owner ~= msg.From then
      return msg.reply({
        success = false,
        message = "Task not found or not owned by you"
      })
    end

    if task.completed then
      return msg.reply({
        success = false,
        message = "Task already completed"
      })
    end

    task.completed = true
    task.completedAt = os.time()
    local newPoints = awardPoints(msg.From, 25)

    msg.reply({
      success = true,
      message = "Task completed! +25 points",
      data = {
        task = task,
        points = newPoints
      }
    })
  end
)

-- Get Tasks Handler
Handlers.add(
  "GetTasks",
  Handlers.utils.hasMatchingTag("Action", "GetTasks"),
  function(msg)
    local userTasks = {}
    for _, task in pairs(Tasks) do
      if task.owner == msg.From then
        table.insert(userTasks, task)
      end
    end

    table.sort(userTasks, function(a, b) return a.created > b.created end)
    msg.reply({
      success = true,
      data = {
        tasks = userTasks,
        count = #userTasks
      }
    })
  end
)

-- Get Points Handler
Handlers.add(
  "GetPoints",
  Handlers.utils.hasMatchingTag("Action", "GetPoints"),
  function(msg)
    local userPoints = getUserPoints(msg.From)
    msg.reply({
      success = true,
      data = {
        points = userPoints,
        userId = msg.From
      }
    })
  end
)

-- Get Leaderboard Handler
Handlers.add(
  "GetLeaderboard",
  Handlers.utils.hasMatchingTag("Action", "GetLeaderboard"),
  function(msg)
    local leaderboard = {}
    for userId, points in pairs(Points) do
      table.insert(leaderboard, { userId = userId, points = points })
    end

    table.sort(leaderboard, function(a, b) return a.points > b.points end)

    local top10 = {}
    for i = 1, math.min(10, #leaderboard) do
      table.insert(top10, leaderboard[i])
    end

    msg.reply({
      success = true,
      data = {
        leaderboard = top10
      }
    })
  end
)

-- Delete Task Handler
Handlers.add(
  "DeleteTask",
  Handlers.utils.hasMatchingTag("Action", "DeleteTask"),
  function(msg)
    local taskData = safeDecode(msg.Data)
    local taskId = taskData and taskData.taskId

    if not taskId then
      return msg.reply({
        success = false,
        message = "TaskId not provided"
      })
    end

    local task = Tasks[taskId]
    if not task or task.owner ~= msg.From then
      return msg.reply({
        success = false,
        message = "Task not found or not owned by you"
      })
    end

    Tasks[taskId] = nil
    msg.reply({
      success = true,
      message = "Task deleted successfully"
    })
  end
)

Frontend Integration

AO Client (app/lib/aoconnect.ts)

import { message, result, createSigner } from '@permaweb/aoconnect';

export interface Task {
  id: string;
  title: string;
  description: string;
  completed: boolean;
  created: number;
  owner: string;
  priority: 'high' | 'medium' | 'low';
  completedAt?: number;
}

export class AOClient {
  public signer: any;
  public processId: string;

  constructor(processId: string) {
    this.signer = createSigner(window.arweaveWallet);
    this.processId = processId;
  }

  async sendMessage(
    action: string,
    data: Record<string, any> = {},
    extraTags: Record<string, string> = {}
  ): Promise<AOResponse> {
    try {
      const tags = [{ name: 'Action', value: action }];
      for (const key in extraTags) {
        tags.push({ name: key, value: extraTags[key] || '' });
      }
      
      const msgTxId = await message({
        process: this.processId,
        tags,
        data: JSON.stringify(data),
        signer: this.signer
      });

      const response = await result({
        message: msgTxId,
        process: this.processId
      });

      if (response.Error) {
        return { success: false, error: response.Error };
      }

      return { success: true, data: response };
    } catch (error) {
      return {
        success: false,
        error: error instanceof Error ? error.message : 'Unknown error'
      };
    }
  }

  // Task methods
  async createTask(taskData: { title: string; description?: string; priority?: 'high' | 'medium' | 'low' }) {
    return this.sendMessage('CreateTask', taskData);
  }

  async completeTask(taskId: string) {
    return this.sendMessage('CompleteTask', { taskId: taskId });
  }

  async getTasks() {
    return this.sendMessage('GetTasks', {});
  }

  async getPoints() {
    return this.sendMessage('GetPoints', {});
  }

  async getLeaderboard() {
    return this.sendMessage('GetLeaderboard', {});
  }

  async deleteTask(taskId: string) {
    return this.sendMessage('DeleteTask', { taskId: taskId });
  }
}

Getting Started

1. Create the Project

# Create new TaskMaster project
ao-forge init my-taskmaster --framework nextjs
cd my-taskmaster

2. Install Dependencies

# Install dependencies
pnpm install
# or
npm install
# or
yarn install

3. Start Development

# Start the development server
ao-forge dev

# In another terminal, start the AO process
ao-forge process start -n taskmaster-process

4. Deploy AO Process

# Deploy the AO process using AOS CLI
aos taskmaster-process --load ./ao/task-process.lua --wallet ./wallet.json

5. Use the Application

  1. Open your browser to http://localhost:3000
  2. Connect your Arweave wallet (ArConnect)
  3. Enter your AO process ID
  4. Start creating and managing tasks!

Key Components

CreateTask Component

  • Form for creating new tasks
  • Priority selection (high, medium, low)
  • Real-time points updates

TaskList Component

  • Display all user tasks
  • Mark tasks as complete
  • Delete tasks
  • Priority-based styling

PointsDisplay Component

  • Show current user points
  • Real-time updates
  • Points history

Leaderboard Component

  • Global top 10 users
  • Points rankings
  • User identification

Configuration

The project uses standard Next.js configuration with:

  • TypeScript for type safety
  • Tailwind CSS for styling
  • ESLint for code quality
  • PostCSS for CSS processing

Dependencies

{
  "dependencies": {
    "@permaweb/aoconnect": "^0.0.90",
    "next": "15.5.4",
    "react": "19.1.0",
    "react-dom": "19.1.0"
  },
  "devDependencies": {
    "@types/node": "^20",
    "@types/react": "^19",
    "@types/react-dom": "^19",
    "eslint": "^9",
    "eslint-config-next": "15.5.4",
    "tailwindcss": "^4",
    "typescript": "^5"
  }
}

Features Demonstrated

AO Development Patterns

  • Message Handlers - Using Handlers.add() for message processing
  • State Management - Local variables for task and points storage
  • Error Handling - Proper error responses and validation
  • User Authentication - Owner-based task access control

Frontend Integration

  • Wallet Connection - ArConnect integration
  • Real-time Updates - Live task and points updates
  • Component Architecture - Modular React components
  • TypeScript Integration - Full type safety

Modern Web Development

  • Next.js App Router - Modern routing system
  • Responsive Design - Mobile-first approach
  • Dark Theme - Modern UI with glassmorphism effects
  • Interactive Elements - Smooth animations and transitions

This example demonstrates the complete integration between AO processes and modern web frameworks, showing how to build decentralized applications with ao-forge.