Skip to main content
RapidDev - Software Development Agency

How to Build Productivity app with V0

Build a unified productivity app with V0 using Next.js, Supabase, and Clerk that combines tasks, notes, a Pomodoro timer, and daily planning in one workspace. Features drag-and-drop kanban, global search via Command palette, and optimistic UI updates — all in about 1-2 hours.

What you'll build

  • Today dashboard combining active tasks, Pomodoro timer, and pinned notes in one view
  • Task management with kanban and list views using shadcn/ui Card and Badge components
  • Rich notes editor with tagging and search functionality
  • Pomodoro timer with session logging using shadcn/ui Progress for the timer ring
  • Daily planning page with goals and reflection journaling
  • Global search using shadcn/ui Command (Cmd+K) across tasks and notes
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate11 min read1-2 hoursV0 Premium or higherApril 2026RapidDev Engineering Team
TL;DR

Build a unified productivity app with V0 using Next.js, Supabase, and Clerk that combines tasks, notes, a Pomodoro timer, and daily planning in one workspace. Features drag-and-drop kanban, global search via Command palette, and optimistic UI updates — all in about 1-2 hours.

What you're building

Switching between separate task managers, note apps, and timers wastes time and breaks focus. A unified productivity workspace puts everything you need in one place — tasks with priorities, quick-capture notes, a Pomodoro timer for focused work, and daily planning to set intentions.

V0 generates the entire workspace from prompts — the dashboard layout, task kanban, note editor, and timer component. All mutations use Server Actions so you don't need to build API routes. Clerk via Vercel Marketplace gives you instant authentication.

The architecture uses Next.js App Router with Server Components for data-heavy pages, 'use client' components for interactive elements (kanban drag-and-drop, timer, Cmd+K search), Server Actions for all mutations, and Supabase for persistent storage.

Final result

A full-featured productivity workspace with a today dashboard, kanban task board, searchable notes, Pomodoro timer with session tracking, and daily planning — all synced to your Supabase database.

Tech stack

V0AI Code Generator
Next.jsFull-Stack Framework
Tailwind CSSStyling
shadcn/uiComponent Library
SupabaseDatabase
ClerkAuth

Prerequisites

  • A V0 account (Premium recommended for multiple pages)
  • A Supabase project (free tier works — connect via V0's Connect panel)
  • A Clerk account (free tier works — enables instant auth via Vercel Marketplace)
  • A sense of what your daily productivity workflow looks like

Build steps

1

Set up the project with auth and database schema

Open V0 and create a new project. Use the Connect panel to add Supabase and enable Clerk via Vercel Marketplace. Then prompt V0 to create the full schema for tasks, notes, time entries, and daily plans.

prompt.txt
1// Paste this prompt into V0's AI chat:
2// Build a productivity workspace app. Set up Clerk auth and create a Supabase schema with:
3// 1. tasks: id (uuid PK), user_id (uuid FK), title (text), description (text), status (text check todo/in_progress/done), priority (text check low/medium/high/urgent), due_date (date), completed_at (timestamptz), created_at (timestamptz)
4// 2. notes: id (uuid PK), user_id (uuid FK), title (text), content (text), tags (text[]), is_pinned (boolean default false), created_at (timestamptz), updated_at (timestamptz)
5// 3. time_entries: id (uuid PK), user_id (uuid FK), task_id (uuid FK nullable), duration_minutes (integer), started_at (timestamptz), ended_at (timestamptz)
6// 4. daily_plans: id (uuid PK), user_id (uuid FK), plan_date (date unique per user), goals (jsonb), reflection (text), created_at (timestamptz)
7// Add RLS so users only see their own data.
8// Generate SQL migration and TypeScript types.

Pro tip: Set CLERK_SECRET_KEY in the Vars tab (no NEXT_PUBLIC_ prefix for the secret). NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY is fine with the prefix since it is a publishable key.

Expected result: Supabase is connected with all four tables created. Clerk is enabled via Vercel Marketplace with sign-in and sign-up working out of the box.

2

Build the today dashboard with tasks, timer, and notes

Create the main dashboard that shows today's tasks, the active Pomodoro timer, and pinned notes. This is the home screen users see when they open the app.

prompt.txt
1// Paste this prompt into V0's AI chat:
2// Build a productivity dashboard at app/page.tsx.
3// Requirements:
4// - Protected by Clerk auth (redirect to sign-in if not authenticated)
5// - Three-column layout on desktop, stacked on mobile:
6// - Left: Today's tasks (due_date = today OR status = in_progress), each in a shadcn/ui Card with Checkbox for completion, Badge for priority (urgent=red, high=orange, medium=blue, low=gray)
7// - Center: Pomodoro timer showing a circular Progress indicator, 25-minute countdown, Start/Pause/Reset Buttons, session count for the day from time_entries
8// - Right: Pinned notes (is_pinned = true) in compact Cards with title and first 100 chars
9// - Quick-add task Dialog triggered by a floating "+" Button
10// - shadcn/ui Command (Cmd+K) for global search across tasks and notes
11// - Use Server Components for data fetching, 'use client' for timer and interactive elements
12// - Server Actions: createTask(), toggleTaskComplete(), logTimeEntry()

Expected result: A three-column dashboard with today's tasks (with completion checkboxes), a Pomodoro timer with progress ring, and pinned notes. Cmd+K opens global search.

3

Create the task management views with kanban

Build a tasks page with both kanban and list views. The kanban board uses drag-and-drop to move tasks between columns with optimistic UI updates via Server Actions.

components/kanban-board.tsx
1'use client'
2
3import { useOptimistic, useTransition } from 'react'
4import { Card } from '@/components/ui/card'
5import { Badge } from '@/components/ui/badge'
6import { Checkbox } from '@/components/ui/checkbox'
7import { updateTaskStatus } from '@/app/actions/tasks'
8
9interface Task {
10 id: string
11 title: string
12 status: 'todo' | 'in_progress' | 'done'
13 priority: 'low' | 'medium' | 'high' | 'urgent'
14 due_date: string | null
15}
16
17const columns = ['todo', 'in_progress', 'done'] as const
18const priorityColors = {
19 urgent: 'destructive',
20 high: 'default',
21 medium: 'secondary',
22 low: 'outline',
23} as const
24
25export function KanbanBoard({ tasks }: { tasks: Task[] }) {
26 const [isPending, startTransition] = useTransition()
27 const [optimisticTasks, setOptimisticTasks] = useOptimistic(
28 tasks,
29 (state, update: { id: string; status: string }) =>
30 state.map((t) => (t.id === update.id ? { ...t, status: update.status as Task['status'] } : t))
31 )
32
33 function handleDrop(taskId: string, newStatus: string) {
34 startTransition(async () => {
35 setOptimisticTasks({ id: taskId, status: newStatus })
36 await updateTaskStatus(taskId, newStatus)
37 })
38 }
39
40 return (
41 <div className="grid grid-cols-3 gap-4">
42 {columns.map((col) => (
43 <div
44 key={col}
45 className="bg-muted/50 rounded-lg p-4 min-h-[400px]"
46 onDragOver={(e) => e.preventDefault()}
47 onDrop={(e) => handleDrop(e.dataTransfer.getData('taskId'), col)}
48 >
49 <h3 className="font-semibold mb-3 capitalize">{col.replace('_', ' ')}</h3>
50 {optimisticTasks
51 .filter((t) => t.status === col)
52 .map((task) => (
53 <Card
54 key={task.id}
55 className="p-3 mb-2 cursor-grab"
56 draggable
57 onDragStart={(e) => e.dataTransfer.setData('taskId', task.id)}
58 >
59 <div className="flex items-center gap-2">
60 <Checkbox />
61 <span className="text-sm flex-1">{task.title}</span>
62 <Badge variant={priorityColors[task.priority]}>{task.priority}</Badge>
63 </div>
64 </Card>
65 ))}
66 </div>
67 ))}
68 </div>
69 )
70}

Pro tip: Use V0's Design Mode (Option+D) to visually adjust the kanban column widths, card spacing, and Badge colors for free without consuming credits.

Expected result: A kanban board with three columns (Todo, In Progress, Done). Tasks can be dragged between columns with optimistic UI updates — the UI moves the card instantly while the Server Action persists in the background.

4

Build the Pomodoro timer component

Create a client-side timer component that counts down 25 minutes, plays a notification sound, and logs completed sessions as time entries linked to the active task.

components/pomodoro-timer.tsx
1'use client'
2
3import { useState, useEffect, useCallback } from 'react'
4import { Button } from '@/components/ui/button'
5import { Progress } from '@/components/ui/progress'
6import { Card } from '@/components/ui/card'
7import { logTimeEntry } from '@/app/actions/tasks'
8
9const WORK_MINUTES = 25
10const BREAK_MINUTES = 5
11
12export function PomodoroTimer({ activeTaskId }: { activeTaskId?: string }) {
13 const [seconds, setSeconds] = useState(WORK_MINUTES * 60)
14 const [isRunning, setIsRunning] = useState(false)
15 const [isBreak, setIsBreak] = useState(false)
16 const [sessions, setSessions] = useState(0)
17
18 const totalSeconds = (isBreak ? BREAK_MINUTES : WORK_MINUTES) * 60
19 const progress = ((totalSeconds - seconds) / totalSeconds) * 100
20 const minutes = Math.floor(seconds / 60)
21 const secs = seconds % 60
22
23 const completeSession = useCallback(async () => {
24 if (!isBreak) {
25 setSessions((s) => s + 1)
26 await logTimeEntry(WORK_MINUTES, activeTaskId)
27 }
28 setIsBreak(!isBreak)
29 setSeconds((!isBreak ? BREAK_MINUTES : WORK_MINUTES) * 60)
30 setIsRunning(false)
31 }, [isBreak, activeTaskId])
32
33 useEffect(() => {
34 if (!isRunning) return
35 const interval = setInterval(() => {
36 setSeconds((s) => {
37 if (s <= 1) {
38 completeSession()
39 return 0
40 }
41 return s - 1
42 })
43 }, 1000)
44 return () => clearInterval(interval)
45 }, [isRunning, completeSession])
46
47 return (
48 <Card className="p-6 text-center">
49 <p className="text-sm text-muted-foreground mb-2">
50 {isBreak ? 'Break Time' : 'Focus Time'}
51 </p>
52 <div className="text-4xl font-mono font-bold mb-4">
53 {String(minutes).padStart(2, '0')}:{String(secs).padStart(2, '0')}
54 </div>
55 <Progress value={progress} className="mb-4" />
56 <div className="flex gap-2 justify-center">
57 <Button onClick={() => setIsRunning(!isRunning)}>
58 {isRunning ? 'Pause' : 'Start'}
59 </Button>
60 <Button
61 variant="outline"
62 onClick={() => {
63 setIsRunning(false)
64 setSeconds(WORK_MINUTES * 60)
65 setIsBreak(false)
66 }}
67 >
68 Reset
69 </Button>
70 </div>
71 <p className="text-sm text-muted-foreground mt-3">
72 Sessions today: {sessions}
73 </p>
74 </Card>
75 )
76}

Expected result: A Pomodoro timer with 25-minute work / 5-minute break cycles, a visual progress bar, Start/Pause/Reset buttons, and automatic session logging to Supabase via Server Action.

5

Add the notes editor and global search

Build a notes page with a searchable grid of note Cards and the Cmd+K command palette for global search across both tasks and notes.

prompt.txt
1// Paste this prompt into V0's AI chat:
2// Build two features:
3// 1. Notes page at app/notes/page.tsx:
4// - Grid of note Cards showing title, first 150 chars of content, tags as Badges, pin icon for is_pinned
5// - Search Input at top that filters notes by title and content
6// - "New Note" Button opens a Dialog with Input for title, Textarea for content, ToggleGroup for tags (work/personal/ideas/reference)
7// - Server Actions: saveNote(), togglePin(), deleteNote()
8// 2. Global search Command component (Cmd+K):
9// - shadcn/ui Command component with CommandInput, CommandList, CommandGroup
10// - Two groups: Tasks and Notes
11// - Search across task titles and note titles/content
12// - Clicking a result navigates to the task or note
13// - Add this to the root layout so it's available on every page
14// - 'use client' component with useEffect for keyboard shortcut listener

Expected result: A notes page with searchable grid layout and tag filtering. Pressing Cmd+K anywhere opens a global search modal that finds tasks and notes by title and content.

Complete code

app/actions/tasks.ts
1'use server'
2
3import { createClient } from '@/lib/supabase/server'
4import { auth } from '@clerk/nextjs/server'
5import { revalidatePath } from 'next/cache'
6
7export async function createTask(formData: FormData) {
8 const { userId } = await auth()
9 const supabase = await createClient()
10
11 await supabase.from('tasks').insert({
12 user_id: userId,
13 title: formData.get('title') as string,
14 description: formData.get('description') as string,
15 priority: formData.get('priority') as string,
16 due_date: formData.get('due_date') as string || null,
17 status: 'todo',
18 })
19
20 revalidatePath('/')
21 revalidatePath('/tasks')
22}
23
24export async function updateTaskStatus(taskId: string, status: string) {
25 const supabase = await createClient()
26
27 await supabase
28 .from('tasks')
29 .update({
30 status,
31 completed_at: status === 'done' ? new Date().toISOString() : null,
32 })
33 .eq('id', taskId)
34
35 revalidatePath('/')
36 revalidatePath('/tasks')
37}
38
39export async function logTimeEntry(
40 durationMinutes: number,
41 taskId?: string
42) {
43 const { userId } = await auth()
44 const supabase = await createClient()
45
46 await supabase.from('time_entries').insert({
47 user_id: userId,
48 task_id: taskId ?? null,
49 duration_minutes: durationMinutes,
50 started_at: new Date(
51 Date.now() - durationMinutes * 60000
52 ).toISOString(),
53 ended_at: new Date().toISOString(),
54 })
55
56 revalidatePath('/')
57}

Customization ideas

Add calendar integration

Connect Google Calendar via OAuth to display tasks with due dates alongside calendar events, and auto-create calendar blocks for Pomodoro focus sessions.

Add recurring tasks

Implement recurring task templates that automatically create new tasks on a daily, weekly, or custom schedule using Supabase pg_cron.

Build a weekly review page

Create a weekly summary page showing completed tasks, total focus time, and reflection prompts based on daily plan data.

Add collaborative workspaces

Enable shared task boards where multiple users can collaborate on projects, assign tasks to each other, and see real-time updates via Supabase Realtime.

Common pitfalls

Pitfall: Putting the Pomodoro timer in a Server Component

How to avoid: Mark the timer component with 'use client' at the top. All interactive components (timer, kanban drag-and-drop, Command palette) must be client components.

Pitfall: Not using optimistic updates for kanban drag-and-drop

How to avoid: Use React's useOptimistic hook to immediately move the card in local state while the Server Action persists the change in the background.

Pitfall: Fetching all tasks and notes in the Command palette on every keystroke

How to avoid: Pre-fetch task and note titles on Command open, then filter client-side. For large datasets, add a 300ms debounce before making search queries.

Best practices

  • Use Server Actions for all mutations (task CRUD, note saving, time logging) — no API routes needed for this project
  • Use V0's Design Mode (Option+D) to visually arrange the dashboard layout for free without consuming credits
  • Keep the Pomodoro timer as a separate 'use client' component to avoid re-rendering the entire dashboard on each tick
  • Use useOptimistic for kanban drag-and-drop to provide instant visual feedback while Server Actions persist in the background
  • Set up the shadcn/ui Command component in the root layout so global search is available on every page
  • Use revalidatePath after each Server Action to refresh the affected pages with fresh data from Supabase

AI prompts to try

Copy these prompts to build this project faster.

ChatGPT Prompt

I'm building a productivity app with Next.js App Router, Supabase, and Clerk. I need help with the daily planning feature. Design a daily_plans table with goals (as a JSONB array of objects with text and is_completed boolean) and reflection (text). Write a Server Action that upserts the plan for today's date (insert if new, update if exists) and a Server Component that fetches and displays the plan with editable goal checkboxes.

Build Prompt

Build a Pomodoro timer component using shadcn/ui. It should count down from 25:00, show a circular Progress indicator, have Start/Pause/Reset buttons, automatically switch to a 5-minute break after work completes, track session count, and call a logTimeEntry Server Action when each work session finishes. Mark it as 'use client' and use useEffect for the interval.

Frequently asked questions

What V0 plan do I need for this productivity app?

V0 Free works for basic features, but Premium ($20/month) is recommended because the app has multiple pages (dashboard, tasks, notes, planning) that benefit from prompt queuing to generate them efficiently.

Can the Pomodoro timer track time against specific tasks?

Yes. The timer component accepts an activeTaskId prop. When a Pomodoro session completes, the logTimeEntry Server Action links the time entry to that task via the task_id foreign key.

Does the app work offline?

Not in this base build since it relies on Supabase for data storage. You could add offline support by implementing local storage caching with sync-on-reconnect logic.

How do I deploy the productivity app?

Click Share then Publish to Production in V0. Set CLERK_SECRET_KEY in the Vars tab without NEXT_PUBLIC_ prefix. Supabase credentials are auto-configured from the Connect panel.

Can I use this with a team instead of just personal use?

The base build is single-user. To add team features, extend the schema with a teams table and shared tasks, then update RLS policies to allow team members to view each other's tasks.

Can RapidDev help build a custom productivity platform?

Yes. RapidDev has built 600+ apps including team productivity platforms with real-time collaboration, calendar integrations, and analytics dashboards. Book a free consultation to discuss your specific needs.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help building your app?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.