# How to Build Sales funnel app with V0

- Tool: v0
- Difficulty: Intermediate
- Compatibility: V0 Premium or higher
- Last updated: April 2026

## TL;DR

Build a sales funnel app with V0 using Next.js and Supabase that gives sales teams a visual kanban pipeline for tracking deals through stages, logs all activities, and shows conversion analytics with Recharts funnel charts — all in about 1-2 hours.

## Before you start

- A V0 account (Premium recommended for the kanban and analytics complexity)
- A Supabase project (free tier works — connect via V0's Connect panel)
- Sample deal data for testing the pipeline and analytics

## Step-by-step guide

### 1. Set up the project and sales pipeline schema

Open V0 and create a new project. Use the Connect panel to add Supabase. Create the schema for pipelines, stages, deals, activities, and contacts.

```
// Paste this prompt into V0's AI chat:
// Build a sales funnel app. Create a Supabase schema with:
// 1. pipelines: id (uuid PK), owner_id (uuid FK), name (text), created_at (timestamptz)
// 2. stages: id (uuid PK), pipeline_id (uuid FK), name (text), position (integer), color (text), created_at (timestamptz)
// 3. deals: id (uuid PK), pipeline_id (uuid FK), stage_id (uuid FK), owner_id (uuid FK), contact_name (text), contact_email (text), company (text), value (integer), currency (text DEFAULT 'USD'), expected_close_date (date), probability (integer CHECK 0-100), status (text CHECK open/won/lost), lost_reason (text), created_at (timestamptz), updated_at (timestamptz)
// 4. deal_activities: id (uuid PK), deal_id (uuid FK), user_id (uuid FK), activity_type (text CHECK note/call/email/meeting/stage_change), description (text), metadata (jsonb), created_at (timestamptz)
// 5. contacts: id (uuid PK), owner_id (uuid FK), name (text), email (text), company (text), phone (text), created_at (timestamptz)
// RLS: users see only deals where owner_id = auth.uid().
// Create a default pipeline with stages: Lead, Qualified, Proposal, Negotiation, Closed Won, Closed Lost.
// Generate SQL migration and TypeScript types.
```

> Pro tip: Use V0's prompt queuing — queue the schema prompt first, then immediately queue the kanban board, analytics dashboard, and deal detail prompts while the schema generates.

**Expected result:** Supabase is connected with pipelines, stages, deals, activities, and contacts tables. A default pipeline with six stages is seeded. RLS policies restrict deals to their owners.

### 2. Build the kanban pipeline board

Create the main pipeline page with a kanban board showing deals as Cards across stage columns. Deals can be dragged between stages, which logs a stage_change activity automatically.

```
// Paste this prompt into V0's AI chat:
// Build a sales pipeline page at app/pipeline/page.tsx.
// Requirements:
// - Kanban board with columns for each stage, ordered by position
// - Each column header shows stage name with colored Badge, deal count, and total value
// - Deal Cards showing: contact_name, company, value (formatted as currency), probability Badge, expected_close_date
// - Drag-and-drop deals between columns using @dnd-kit/core
// - On drop: call Server Action moveDealToStage() which:
//   1. Updates deal's stage_id
//   2. Inserts a deal_activities record with activity_type 'stage_change' and metadata {from_stage, to_stage}
// - Deal Card DropdownMenu: View Details, Mark Won, Mark Lost (opens Dialog for lost_reason)
// - "Add Deal" Button per column opens Sheet with deal form (contact_name, company, value, probability, expected_close_date)
// - Tabs for switching between Pipeline (kanban), List (Table view), and Analytics
// - 'use client' for the kanban board
// - Use shadcn/ui Card, Badge, Avatar, Sheet, DropdownMenu, Tabs, Table, Button, Dialog
```

**Expected result:** A kanban pipeline board with draggable deal Cards across stage columns. Dropping a deal into a new stage updates the database and logs the transition.

### 3. Create the deal detail page with activity timeline

Build the deal detail page showing all information about a deal, with an activity timeline that logs notes, calls, emails, meetings, and stage changes.

```
// Paste this prompt into V0's AI chat:
// Build a deal detail page at app/deals/[id]/page.tsx.
// Requirements:
// - Header: contact_name, company, value (large), stage Badge (colored), probability
// - Quick actions: DropdownMenu with Move to Stage (nested menu of all stages), Mark Won, Mark Lost, Edit
// - Two-column layout:
//   - Left: Activity timeline (deal_activities ordered by created_at DESC)
//     - Each activity: Avatar, activity_type Badge (note=blue, call=green, email=purple, meeting=orange, stage_change=gray), description, timestamp
//     - Stage change activities show "Moved from {from_stage} to {to_stage}" from metadata
//     - "Add Activity" form at top with activity_type Select, description Textarea, "Log" Button
//   - Right: Deal details Card (contact info, value, probability, dates) + Contact Card
// - Server Actions: logActivity(), updateDeal()
// - Server Components for data fetching
// - Use shadcn/ui Card, Badge, Avatar, Select, Textarea, Button, DropdownMenu, Separator
```

**Expected result:** A deal detail page with all deal information, a chronological activity timeline with type-specific Badges, and a form for logging new activities.

### 4. Build the conversion analytics dashboard

Create the analytics page showing stage-to-stage conversion rates as a funnel chart, revenue forecast, and pipeline health metrics. Uses a Supabase RPC function for accurate conversion calculations.

```
// Paste this prompt into V0's AI chat:
// Build a pipeline analytics page at app/pipeline/analytics/page.tsx.
// Requirements:
// - Top row: metric Cards showing total pipeline value, average deal size, win rate %, average time to close
// - Conversion funnel: horizontal bar chart (Recharts BarChart) showing deal count at each stage
//   - Labels between bars showing conversion percentage from one stage to the next
//   - Data comes from a Supabase RPC function 'get_conversion_stats' that:
//     - Queries deal_activities WHERE activity_type = 'stage_change'
//     - Groups by from_stage and to_stage
//     - Computes conversion percentage for each transition
// - Revenue forecast: Recharts LineChart showing expected revenue by month
//   - Based on deals' expected_close_date and value * (probability / 100)
// - Stage breakdown: Table showing each stage's deal count, total value, average time in stage
// - Date range filter with Calendar + Popover DatePicker
// - Server Components for data fetching — call the RPC function directly
// - Use shadcn/ui Card, Table, Badge, Calendar, Popover, Button
```

> Pro tip: The Supabase RPC function for conversion stats avoids expensive client-side calculations. It runs the aggregation in PostgreSQL where it's fastest, and returns pre-computed percentages ready for Recharts.

**Expected result:** An analytics dashboard with conversion funnel bars, revenue forecast line chart, pipeline metric Cards, and stage breakdown Table.

## Complete code example

File: `app/actions/deals.ts`

```typescript
'use server'

import { createClient } from '@/lib/supabase/server'
import { revalidatePath } from 'next/cache'

export async function createDeal(formData: FormData) {
  const supabase = await createClient()
  const { data: { user } } = await supabase.auth.getUser()
  if (!user) throw new Error('Must be logged in')

  const { data: deal } = await supabase
    .from('deals')
    .insert({
      pipeline_id: formData.get('pipeline_id') as string,
      stage_id: formData.get('stage_id') as string,
      owner_id: user.id,
      contact_name: formData.get('contact_name') as string,
      contact_email: formData.get('contact_email') as string,
      company: formData.get('company') as string,
      value: parseInt(formData.get('value') as string),
      probability: parseInt(formData.get('probability') as string),
      expected_close_date: formData.get('expected_close_date') as string,
      status: 'open',
    })
    .select()
    .single()

  if (!deal) throw new Error('Failed to create deal')

  await supabase.from('deal_activities').insert({
    deal_id: deal.id,
    user_id: user.id,
    activity_type: 'note',
    description: 'Deal created',
  })

  revalidatePath('/pipeline')
  return deal
}

export async function moveDealToStage(
  dealId: string,
  fromStageId: string,
  toStageId: string
) {
  const supabase = await createClient()
  const { data: { user } } = await supabase.auth.getUser()
  if (!user) throw new Error('Must be logged in')

  await supabase
    .from('deals')
    .update({ stage_id: toStageId, updated_at: new Date().toISOString() })
    .eq('id', dealId)

  await supabase.from('deal_activities').insert({
    deal_id: dealId,
    user_id: user.id,
    activity_type: 'stage_change',
    description: 'Deal moved to new stage',
    metadata: { from_stage: fromStageId, to_stage: toStageId },
  })

  revalidatePath('/pipeline')
}

export async function logActivity(formData: FormData) {
  const supabase = await createClient()
  const { data: { user } } = await supabase.auth.getUser()
  if (!user) throw new Error('Must be logged in')

  const dealId = formData.get('deal_id') as string

  await supabase.from('deal_activities').insert({
    deal_id: dealId,
    user_id: user.id,
    activity_type: formData.get('activity_type') as string,
    description: formData.get('description') as string,
  })

  revalidatePath(`/deals/${dealId}`)
}
```

## Common mistakes

- **Not logging stage changes in deal_activities when moving deals** — Without stage change logs, the conversion analytics cannot calculate how many deals moved from one stage to another. The funnel chart shows no data. Fix: Always insert a deal_activities record with activity_type = 'stage_change' and metadata containing from_stage and to_stage whenever a deal's stage_id changes. The moveDealToStage Server Action handles this automatically.
- **Computing conversion rates on the client side from deal counts per stage** — Counting current deals per stage shows a snapshot, not actual conversion. It doesn't account for deals that skipped stages, went backwards, or were lost mid-pipeline. Fix: Use a Supabase RPC function that queries deal_activities WHERE activity_type = 'stage_change', groups by from-stage and to-stage, and computes actual transition percentages based on recorded movements.
- **Using client-side drag-and-drop without optimistic updates** — Without optimistic updates, the deal Card snaps back to its original column while waiting for the Server Action to complete, creating a jarring visual experience. Fix: Update the local state immediately on drop (optimistic), then call the Server Action. If the action fails, revert the local state and show a Toast error.

## Best practices

- Log every deal interaction as a deal_activity to build an accurate audit trail for conversion analytics
- Use a Supabase RPC function for conversion rate calculations — run aggregation in PostgreSQL, not in the browser
- Apply optimistic updates on kanban drag-and-drop for instant visual feedback while the Server Action processes
- Use V0's prompt queuing to generate the kanban board, analytics dashboard, and deal detail page sequentially
- Use V0's Design Mode (Option+D) to visually adjust stage column colors and deal Card spacing without spending credits
- Seed a default pipeline with standard sales stages (Lead, Qualified, Proposal, Negotiation, Won, Lost) for immediate usability

## Frequently asked questions

### Can I build this on V0's free tier?

V0 Free provides enough credits for the basic pipeline board. Premium ($20/month) is recommended because this project has a kanban board, analytics dashboard, and deal detail page that benefit from prompt queuing.

### How does the conversion funnel calculate accurate percentages?

A Supabase RPC function queries the deal_activities table for stage_change events, groups them by from-stage and to-stage, and computes the actual percentage of deals that transitioned between each pair of stages. This is more accurate than counting current deals per stage.

### Can I customize the pipeline stages?

Yes. Stages are stored in the stages table with name, position, and color fields. You can add, rename, reorder, or remove stages through the pipeline settings page. Deals will retain their stage assignment even if stage names change.

### How does drag-and-drop work without being slow?

The kanban uses optimistic updates — when you drop a deal Card onto a new stage column, the UI updates immediately. The Server Action runs in the background to update the database and log the stage change. If it fails, the Card reverts and shows an error Toast.

### How do I deploy the sales funnel app?

Click Share then Publish to Production in V0. Supabase credentials are auto-configured from the Connect panel. No additional env vars are needed beyond the Supabase connection.

### Can RapidDev help build a custom sales platform?

Yes. RapidDev has built 600+ apps including CRM systems with pipeline management, AI-powered lead scoring, and email integration. Book a free consultation to discuss your requirements.

---

Source: https://www.rapidevelopers.com/how-to-build-v0/sales-funnel-app
© RapidDev — https://www.rapidevelopers.com/how-to-build-v0/sales-funnel-app
