Skip to content

lazy-logic/Tasko-Project

Repository files navigation

Tasko

A modern, full-featured project management and task tracking application inspired by Trello. Built with Next.js 15, TypeScript, Supabase, and Clerk authentication.

Live Demo

View Live Application

Screenshots

Tasko Dashboard Tasko Board View Tasko Task Management

Features

Core Functionality

  • Secure authentication with Clerk (Email, Google, GitHub OAuth)
  • Multi-organization support with role-based access control
  • Create and manage unlimited boards
  • Drag-and-drop task management with smooth animations
  • Real-time collaboration and updates
  • Global search across boards, tasks, and members
  • Mobile-first responsive design

Task Management

  • Rich text descriptions with formatting toolbar
  • Custom cover images and colors
  • Labels and tags for organization
  • Due dates and priority levels
  • Multiple member assignment to tasks
  • Checklists with progress tracking
  • File attachments and links
  • Comments and activity tracking
  • Task watchers for notifications
  • Task completion status

Team Collaboration

  • Organization workspaces
  • Team member invitations via email
  • Role management (Owner, Admin, Member, Guest)
  • Shared boards and tasks
  • Activity feed and notifications
  • Member profiles and avatars

User Experience

  • Intuitive board navigation widget
  • Quick task creation and editing
  • Keyboard shortcuts support
  • Optimized loading and animations
  • Dark mode compatible UI
  • Toast notifications for actions

Tech Stack

Frontend

  • Next.js 15 (App Router)
  • React 19
  • TypeScript
  • Tailwind CSS
  • Radix UI Components
  • dnd-kit (Drag and Drop)
  • Lucide Icons

Backend

  • Next.js API Routes
  • Supabase PostgreSQL
  • Row Level Security (RLS)
  • Server-side rendering

Authentication & Authorization

  • Clerk Authentication
  • OAuth providers (Google, GitHub)
  • Protected routes and middleware
  • Role-based access control

Getting Started

Prerequisites

  • Node.js 18 or higher
  • npm or yarn package manager
  • Supabase account (free tier available)
  • Clerk account (free tier available)

Installation

  1. Clone the repository
git clone https://github.com/yourusername/tasko.git
cd tasko
  1. Install dependencies
npm install
  1. Set up environment variables

Create a .env.local file in the root directory with the following variables:

# Clerk Authentication
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=your_clerk_publishable_key
CLERK_SECRET_KEY=your_clerk_secret_key
NEXT_PUBLIC_CLERK_SIGN_IN_URL=/login
NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up
NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL=/dashboard
NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL=/dashboard

# Supabase
NEXT_PUBLIC_SUPABASE_URL=your_supabase_url
NEXT_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key
SUPABASE_SERVICE_ROLE_KEY=your_supabase_service_role_key

# App URL
NEXT_PUBLIC_APP_URL=http://localhost:3000

# Brevo Email Service (for organization invites)
BREVO_API_KEY=your_brevo_api_key
BREVO_SENDER_EMAIL=your_email@example.com
BREVO_SENDER_NAME=Your App Name
  1. Set up Supabase database

Go to your Supabase dashboard, open the SQL Editor, and run the following schema:

-- Enable UUID extension
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";

-- Organizations & memberships
CREATE TABLE IF NOT EXISTS organizations (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  name TEXT NOT NULL,
  slug TEXT NOT NULL UNIQUE,
  plan TEXT NOT NULL DEFAULT 'free',
  created_by TEXT NOT NULL,
  created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
  updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE TABLE IF NOT EXISTS organization_members (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  org_id UUID NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
  user_id TEXT NOT NULL,
  role TEXT NOT NULL DEFAULT 'member' CHECK (role IN ('owner','admin','member','guest')),
  status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active','invited','suspended')),
  joined_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
  UNIQUE(org_id, user_id)
);

CREATE TABLE IF NOT EXISTS organization_invites (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  org_id UUID NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
  invited_email TEXT NOT NULL,
  invited_by TEXT NOT NULL,
  token TEXT NOT NULL UNIQUE,
  status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending','accepted','expired')),
  expires_at TIMESTAMPTZ NOT NULL,
  redirect_url TEXT,
  metadata JSONB,
  created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

-- Boards, lists, cards, activity
CREATE TABLE IF NOT EXISTS boards (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  title TEXT NOT NULL,
  description TEXT,
  color TEXT DEFAULT '#0079BF',
  thumbnail_url TEXT,
  background_image TEXT,
  user_id TEXT,
  org_id UUID REFERENCES organizations(id) ON DELETE CASCADE,
  created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
  updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE TABLE IF NOT EXISTS columns (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  board_id UUID NOT NULL REFERENCES boards(id) ON DELETE CASCADE,
  title TEXT NOT NULL,
  sort_order INTEGER NOT NULL DEFAULT 0,
  user_id TEXT,
  created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE TABLE IF NOT EXISTS tasks (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  column_id UUID NOT NULL REFERENCES columns(id) ON DELETE CASCADE,
  title TEXT NOT NULL,
  description TEXT,
  assignee TEXT,
  due_date TIMESTAMPTZ,
  priority TEXT DEFAULT 'medium' CHECK (priority IN ('low','medium','high')),
  sort_order INTEGER NOT NULL DEFAULT 0,
  is_completed BOOLEAN DEFAULT FALSE,
  cover_image TEXT,
  cover_color TEXT,
  org_id UUID,
  created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
  updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

-- Task members (for assigning multiple members to a task)
CREATE TABLE IF NOT EXISTS task_members (
  task_id UUID NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
  user_id TEXT NOT NULL,
  created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
  PRIMARY KEY (task_id, user_id)
);

-- Labels
CREATE TABLE IF NOT EXISTS labels (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  board_id UUID NOT NULL REFERENCES boards(id) ON DELETE CASCADE,
  name TEXT NOT NULL,
  color TEXT NOT NULL,
  created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE TABLE IF NOT EXISTS task_labels (
  task_id UUID NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
  label_id UUID NOT NULL REFERENCES labels(id) ON DELETE CASCADE,
  created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
  PRIMARY KEY (task_id, label_id)
);

-- Checklists
CREATE TABLE IF NOT EXISTS checklists (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  task_id UUID NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
  title TEXT NOT NULL,
  sort_order INTEGER NOT NULL DEFAULT 0,
  created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE TABLE IF NOT EXISTS checklist_items (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  checklist_id UUID NOT NULL REFERENCES checklists(id) ON DELETE CASCADE,
  title TEXT NOT NULL,
  is_completed BOOLEAN DEFAULT FALSE,
  sort_order INTEGER NOT NULL DEFAULT 0,
  created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

-- Attachments
CREATE TABLE IF NOT EXISTS attachments (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  task_id UUID NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
  name TEXT NOT NULL,
  url TEXT NOT NULL,
  type TEXT NOT NULL CHECK (type IN ('file', 'link')),
  size INTEGER,
  created_by TEXT NOT NULL,
  created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

-- Comments
CREATE TABLE IF NOT EXISTS comments (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  task_id UUID NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
  user_id TEXT NOT NULL,
  content TEXT NOT NULL,
  created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
  updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE TABLE IF NOT EXISTS comment_reactions (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  comment_id UUID NOT NULL REFERENCES comments(id) ON DELETE CASCADE,
  user_id TEXT NOT NULL,
  emoji TEXT NOT NULL,
  created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
  UNIQUE(comment_id, user_id, emoji)
);

-- Task watchers
CREATE TABLE IF NOT EXISTS task_watchers (
  task_id UUID NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
  user_id TEXT NOT NULL,
  created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
  PRIMARY KEY (task_id, user_id)
);

-- Card activity
CREATE TABLE IF NOT EXISTS card_activity (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  card_id UUID NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
  performed_by TEXT NOT NULL,
  action_type TEXT NOT NULL,
  payload JSONB,
  created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

-- Helper functions
CREATE OR REPLACE FUNCTION requesting_user_id()
RETURNS text AS $$
  SELECT NULLIF(
    current_setting('request.jwt.claims', true)::json->>'sub',
    ''
  )::text;
$$ LANGUAGE SQL STABLE;

CREATE OR REPLACE FUNCTION is_org_member(target_org uuid)
RETURNS boolean AS $$
  SELECT EXISTS (
    SELECT 1
    FROM organization_members
    WHERE org_id = target_org
      AND user_id = requesting_user_id()
      AND status = 'active'
  );
$$ LANGUAGE SQL STABLE;

CREATE OR REPLACE FUNCTION is_org_admin(target_org uuid)
RETURNS boolean AS $$
  SELECT EXISTS (
    SELECT 1
    FROM organization_members
    WHERE org_id = target_org
      AND user_id = requesting_user_id()
      AND status = 'active'
      AND role IN ('owner', 'admin')
  );
$$ LANGUAGE SQL STABLE;

-- Indexes
CREATE INDEX IF NOT EXISTS idx_boards_org_id ON boards(org_id);
CREATE INDEX IF NOT EXISTS idx_boards_user_id ON boards(user_id);
CREATE INDEX IF NOT EXISTS idx_columns_board_id ON columns(board_id);
CREATE INDEX IF NOT EXISTS idx_tasks_column_id ON tasks(column_id);
CREATE INDEX IF NOT EXISTS idx_tasks_sort_order ON tasks(sort_order);
CREATE INDEX IF NOT EXISTS idx_task_members_task_id ON task_members(task_id);
CREATE INDEX IF NOT EXISTS idx_task_members_user_id ON task_members(user_id);
CREATE INDEX IF NOT EXISTS idx_labels_board_id ON labels(board_id);
CREATE INDEX IF NOT EXISTS idx_task_labels_task_id ON task_labels(task_id);
CREATE INDEX IF NOT EXISTS idx_task_labels_label_id ON task_labels(label_id);
CREATE INDEX IF NOT EXISTS idx_checklists_task_id ON checklists(task_id);
CREATE INDEX IF NOT EXISTS idx_checklist_items_checklist_id ON checklist_items(checklist_id);
CREATE INDEX IF NOT EXISTS idx_attachments_task_id ON attachments(task_id);
CREATE INDEX IF NOT EXISTS idx_comments_task_id ON comments(task_id);
CREATE INDEX IF NOT EXISTS idx_comment_reactions_comment_id ON comment_reactions(comment_id);
CREATE INDEX IF NOT EXISTS idx_task_watchers_task_id ON task_watchers(task_id);
CREATE INDEX IF NOT EXISTS idx_task_watchers_user_id ON task_watchers(user_id);
CREATE INDEX IF NOT EXISTS idx_card_activity_card_id ON card_activity(card_id);
CREATE INDEX IF NOT EXISTS idx_organization_invites_email ON organization_invites(invited_email);
CREATE INDEX IF NOT EXISTS idx_organization_invites_token ON organization_invites(token);
CREATE INDEX IF NOT EXISTS idx_organization_members_org_id ON organization_members(org_id);
CREATE INDEX IF NOT EXISTS idx_organization_members_user_id ON organization_members(user_id);

-- Updated_at trigger
CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
  NEW.updated_at = NOW();
  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER update_boards_updated_at
  BEFORE UPDATE ON boards
  FOR EACH ROW
  EXECUTE FUNCTION update_updated_at_column();

CREATE TRIGGER update_organizations_updated_at
  BEFORE UPDATE ON organizations
  FOR EACH ROW
  EXECUTE FUNCTION update_updated_at_column();

-- Row Level Security
ALTER TABLE organizations ENABLE ROW LEVEL SECURITY;
ALTER TABLE organization_members ENABLE ROW LEVEL SECURITY;
ALTER TABLE organization_invites ENABLE ROW LEVEL SECURITY;
ALTER TABLE boards ENABLE ROW LEVEL SECURITY;
ALTER TABLE columns ENABLE ROW LEVEL SECURITY;
ALTER TABLE tasks ENABLE ROW LEVEL SECURITY;
ALTER TABLE card_activity ENABLE ROW LEVEL SECURITY;

-- Auto-create owner membership when org is created
CREATE OR REPLACE FUNCTION create_org_owner_membership()
RETURNS TRIGGER AS $$
BEGIN
  INSERT INTO organization_members (org_id, user_id, role, status)
  VALUES (NEW.id, NEW.created_by, 'owner', 'active');
  RETURN NEW;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;

CREATE TRIGGER auto_create_org_owner
  AFTER INSERT ON organizations
  FOR EACH ROW
  EXECUTE FUNCTION create_org_owner_membership();

-- RLS Policies
CREATE POLICY "Members can view org" ON organizations
FOR SELECT USING (created_by = requesting_user_id());

CREATE POLICY "Owners manage org" ON organizations
FOR UPDATE USING (is_org_admin(id));

CREATE POLICY "Owners delete org" ON organizations
FOR DELETE USING (is_org_admin(id));

CREATE POLICY "Users create their org" ON organizations
FOR INSERT WITH CHECK (created_by = requesting_user_id());

CREATE POLICY "Members can view membership" ON organization_members
FOR SELECT USING (user_id = requesting_user_id());

CREATE POLICY "Admins invite members" ON organization_members
FOR INSERT WITH CHECK (true);

CREATE POLICY "Admins update member roles" ON organization_members
FOR UPDATE USING (is_org_admin(org_id));

CREATE POLICY "Admins remove members" ON organization_members
FOR DELETE USING (is_org_admin(org_id));

-- Organization invites RLS policy
-- Service role has full access (used by backend API routes)
CREATE POLICY "Service role full access" ON organization_invites
FOR ALL 
USING (true)
WITH CHECK (true);

CREATE POLICY "Org members view boards" ON boards
FOR SELECT USING (
  (org_id IS NOT NULL AND is_org_member(org_id))
  OR (org_id IS NULL AND user_id = requesting_user_id())
);

CREATE POLICY "Org members insert boards" ON boards
FOR INSERT WITH CHECK (
  (org_id IS NOT NULL AND is_org_member(org_id))
  OR (org_id IS NULL AND user_id = requesting_user_id())
);

CREATE POLICY "Org members update boards" ON boards
FOR UPDATE USING (
  (org_id IS NOT NULL AND is_org_member(org_id))
  OR (org_id IS NULL AND user_id = requesting_user_id())
);

CREATE POLICY "Org members delete boards" ON boards
FOR DELETE USING (
  (org_id IS NOT NULL AND is_org_member(org_id))
  OR (org_id IS NULL AND user_id = requesting_user_id())
);
  1. Run the development server
npm run dev
  1. Open your browser and navigate to http://localhost:3000

Available Scripts

npm run dev          # Start development server on port 3000
npm run build        # Build production bundle
npm start            # Start production server
npm run lint         # Run ESLint for code quality

Deployment

Deploy to Vercel (Recommended)

  1. Push your code to GitHub
git init
git add .
git commit -m "Initial commit"
git branch -M main
git remote add origin https://github.com/yourusername/tasko.git
git push -u origin main
  1. Import your project in Vercel

    • Go to https://vercel.com
    • Click "New Project"
    • Import your GitHub repository
    • Vercel will auto-detect Next.js
  2. Configure environment variables

    • Add all variables from your .env.local file
    • Update NEXT_PUBLIC_APP_URL to your Vercel URL
  3. Deploy

    • Click "Deploy"
    • Wait for build to complete

Post-Deployment: Clerk Configuration

IMPORTANT: After deployment, update your Clerk dashboard settings:

  1. Go to https://dashboard.clerk.com
  2. Select your application
  3. Navigate to "Paths" or "URLs" settings
  4. Add your production URLs:
    • Authorized redirect URLs: https://your-app.vercel.app/sso-callback
    • Sign-in URL: https://your-app.vercel.app/login
    • Sign-up URL: https://your-app.vercel.app/sign-up
    • After sign-in URL: https://your-app.vercel.app/dashboard
    • After sign-up URL: https://your-app.vercel.app/dashboard

Without these settings, OAuth authentication will fail.

Email Service Setup (Brevo)

Tasko uses Brevo (formerly Sendinblue) for sending organization invitation emails.

  1. Sign up for Brevo

  2. Get your API Key

    • Navigate to SMTP & APIAPI Keys
    • Click Create a new API key
    • Copy the API key (starts with xkeysib-)
  3. Add to environment variables

    BREVO_API_KEY=xkeysib-your_api_key_here
    BREVO_SENDER_EMAIL=your_email@example.com
    BREVO_SENDER_NAME=Tasko
  4. For production (Vercel)

    • Add the same three variables to Vercel environment variables
    • Redeploy your application

Note: The free tier allows 300 emails/day without domain verification, perfect for development and small teams.

Project Structure

tasko/
├── app/                      # Next.js 15 App Router
│   ├── api/                 # API routes (REST endpoints)
│   │   ├── boards/          # Board CRUD operations
│   │   ├── tasks/           # Task management
│   │   ├── comments/        # Comments system
│   │   ├── labels/          # Labels and tags
│   │   ├── organizations/   # Organization management
│   │   └── ...
│   ├── boards/[id]/         # Dynamic board pages
│   ├── dashboard/           # User dashboard
│   ├── login/               # Authentication pages
│   ├── sign-up/             # Registration pages
│   └── ...
├── components/
│   ├── features/            # Feature-specific components
│   │   ├── tasks/           # Task cards, modals
│   │   ├── boards/          # Board components
│   │   └── organizations/   # Org management
│   ├── layout/              # Layout components
│   │   ├── navbar/          # Navigation
│   │   └── dashboard/       # Dashboard layout
│   ├── ui/                  # Reusable UI components
│   └── shared/              # Shared utilities
├── lib/
│   ├── contexts/            # React contexts
│   ├── hooks/               # Custom React hooks
│   ├── supabase/            # Supabase client & models
│   └── utils/               # Utility functions
├── public/                  # Static assets
└── middleware.ts            # Next.js middleware (auth)

API Documentation

Boards

  • GET /api/boards - List all boards
  • POST /api/boards - Create new board
  • GET /api/boards/[id] - Get board details
  • PATCH /api/boards/[id] - Update board
  • DELETE /api/boards/[id] - Delete board

Tasks

  • GET /api/tasks - List tasks
  • POST /api/tasks - Create task
  • GET /api/tasks/[id] - Get task details
  • PATCH /api/tasks/[id] - Update task
  • DELETE /api/tasks/[id] - Delete task
  • GET /api/tasks/[id]/activity - Get task activity
  • GET /api/tasks/[id]/details - Get full task details

Organizations

  • GET /api/organizations - List user organizations
  • POST /api/organizations - Create organization
  • GET /api/organizations/[orgId]/members - List members
  • POST /api/organizations/[orgId]/invites - Send invitation
  • GET /api/organizations/[orgId]/invites - List invitations

Task Member Management

  • POST /api/tasks/[id]/members - Assign member to task
    • Body: { "user_id": "user_clerk_id" }
    • Validates user is organization member
    • Prevents duplicate assignments
  • DELETE /api/tasks/[id]/members?user_id=xxx - Remove member from task
    • Query param: user_id (required)
    • Logs activity for audit trail

Contributing

Contributions are welcome! Please follow these steps:

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

Please ensure your code:

  • Follows the existing code style
  • Includes appropriate comments
  • Passes all linting checks (npm run lint)
  • Builds successfully (npm run build)

License

This project is licensed under the MIT License.

Acknowledgments

  • Inspired by Trello's intuitive project management interface
  • Built with modern web technologies and best practices
  • Community contributions and feedback

Support

For issues, questions, or feature requests:

  • Open an issue on GitHub
  • Check existing issues for solutions
  • Provide detailed information for bug reports

Roadmap

Future enhancements planned:

  • Real-time collaborative editing
  • Advanced filtering and sorting
  • Custom fields and templates
  • Time tracking integration
  • Mobile native applications
  • Advanced analytics and reporting
  • Automation and webhooks
  • Third-party integrations

Built with care by the Tasko team

About

a web app to Capture, organize, and tackle your to-dos from anywhere.

Resources

Contributing

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors