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.
What you're building
Sales teams need a clear view of their pipeline — which deals are in which stage, what the conversion rates are between stages, and where the revenue forecast stands. A CRM-lite funnel view gives teams the visibility they need without the bloat of enterprise CRM software.
V0 generates the kanban board, deal detail page, and analytics dashboard from prompts. The key architectural challenge is accurate conversion rate calculation between stages. A Supabase database function queries the deal_activities table where activity_type = 'stage_change', groups by from-stage and to-stage, and computes conversion percentages — exposed as an RPC endpoint that the analytics page calls.
The architecture uses Next.js App Router with Server Components for the pipeline list and analytics, a client component for the interactive kanban board, Server Actions for all deal mutations (which also log activities automatically), and Recharts for funnel and forecast visualizations.
Final result
A sales funnel app with a drag-and-drop kanban pipeline, deal detail pages with activity timelines, stage-to-stage conversion analytics, revenue forecasting, and a contact directory.
Tech stack
Prerequisites
- 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
Build steps
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.
1// Paste this prompt into V0's AI chat:2// Build a sales funnel app. Create a Supabase schema with:3// 1. pipelines: id (uuid PK), owner_id (uuid FK), name (text), created_at (timestamptz)4// 2. stages: id (uuid PK), pipeline_id (uuid FK), name (text), position (integer), color (text), created_at (timestamptz)5// 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)6// 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)7// 5. contacts: id (uuid PK), owner_id (uuid FK), name (text), email (text), company (text), phone (text), created_at (timestamptz)8// RLS: users see only deals where owner_id = auth.uid().9// Create a default pipeline with stages: Lead, Qualified, Proposal, Negotiation, Closed Won, Closed Lost.10// 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.
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.
1// Paste this prompt into V0's AI chat:2// Build a sales pipeline page at app/pipeline/page.tsx.3// Requirements:4// - Kanban board with columns for each stage, ordered by position5// - Each column header shows stage name with colored Badge, deal count, and total value6// - Deal Cards showing: contact_name, company, value (formatted as currency), probability Badge, expected_close_date7// - Drag-and-drop deals between columns using @dnd-kit/core8// - On drop: call Server Action moveDealToStage() which:9// 1. Updates deal's stage_id10// 2. Inserts a deal_activities record with activity_type 'stage_change' and metadata {from_stage, to_stage}11// - Deal Card DropdownMenu: View Details, Mark Won, Mark Lost (opens Dialog for lost_reason)12// - "Add Deal" Button per column opens Sheet with deal form (contact_name, company, value, probability, expected_close_date)13// - Tabs for switching between Pipeline (kanban), List (Table view), and Analytics14// - 'use client' for the kanban board15// - Use shadcn/ui Card, Badge, Avatar, Sheet, DropdownMenu, Tabs, Table, Button, DialogExpected 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.
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.
1// Paste this prompt into V0's AI chat:2// Build a deal detail page at app/deals/[id]/page.tsx.3// Requirements:4// - Header: contact_name, company, value (large), stage Badge (colored), probability5// - Quick actions: DropdownMenu with Move to Stage (nested menu of all stages), Mark Won, Mark Lost, Edit6// - Two-column layout:7// - Left: Activity timeline (deal_activities ordered by created_at DESC)8// - Each activity: Avatar, activity_type Badge (note=blue, call=green, email=purple, meeting=orange, stage_change=gray), description, timestamp9// - Stage change activities show "Moved from {from_stage} to {to_stage}" from metadata10// - "Add Activity" form at top with activity_type Select, description Textarea, "Log" Button11// - Right: Deal details Card (contact info, value, probability, dates) + Contact Card12// - Server Actions: logActivity(), updateDeal()13// - Server Components for data fetching14// - Use shadcn/ui Card, Badge, Avatar, Select, Textarea, Button, DropdownMenu, SeparatorExpected result: A deal detail page with all deal information, a chronological activity timeline with type-specific Badges, and a form for logging new activities.
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.
1// Paste this prompt into V0's AI chat:2// Build a pipeline analytics page at app/pipeline/analytics/page.tsx.3// Requirements:4// - Top row: metric Cards showing total pipeline value, average deal size, win rate %, average time to close5// - Conversion funnel: horizontal bar chart (Recharts BarChart) showing deal count at each stage6// - Labels between bars showing conversion percentage from one stage to the next7// - Data comes from a Supabase RPC function 'get_conversion_stats' that:8// - Queries deal_activities WHERE activity_type = 'stage_change'9// - Groups by from_stage and to_stage10// - Computes conversion percentage for each transition11// - Revenue forecast: Recharts LineChart showing expected revenue by month12// - Based on deals' expected_close_date and value * (probability / 100)13// - Stage breakdown: Table showing each stage's deal count, total value, average time in stage14// - Date range filter with Calendar + Popover DatePicker15// - Server Components for data fetching — call the RPC function directly16// - Use shadcn/ui Card, Table, Badge, Calendar, Popover, ButtonPro 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
1'use server'23import { createClient } from '@/lib/supabase/server'4import { revalidatePath } from 'next/cache'56export async function createDeal(formData: FormData) {7 const supabase = await createClient()8 const { data: { user } } = await supabase.auth.getUser()9 if (!user) throw new Error('Must be logged in')1011 const { data: deal } = await supabase12 .from('deals')13 .insert({14 pipeline_id: formData.get('pipeline_id') as string,15 stage_id: formData.get('stage_id') as string,16 owner_id: user.id,17 contact_name: formData.get('contact_name') as string,18 contact_email: formData.get('contact_email') as string,19 company: formData.get('company') as string,20 value: parseInt(formData.get('value') as string),21 probability: parseInt(formData.get('probability') as string),22 expected_close_date: formData.get('expected_close_date') as string,23 status: 'open',24 })25 .select()26 .single()2728 if (!deal) throw new Error('Failed to create deal')2930 await supabase.from('deal_activities').insert({31 deal_id: deal.id,32 user_id: user.id,33 activity_type: 'note',34 description: 'Deal created',35 })3637 revalidatePath('/pipeline')38 return deal39}4041export async function moveDealToStage(42 dealId: string,43 fromStageId: string,44 toStageId: string45) {46 const supabase = await createClient()47 const { data: { user } } = await supabase.auth.getUser()48 if (!user) throw new Error('Must be logged in')4950 await supabase51 .from('deals')52 .update({ stage_id: toStageId, updated_at: new Date().toISOString() })53 .eq('id', dealId)5455 await supabase.from('deal_activities').insert({56 deal_id: dealId,57 user_id: user.id,58 activity_type: 'stage_change',59 description: 'Deal moved to new stage',60 metadata: { from_stage: fromStageId, to_stage: toStageId },61 })6263 revalidatePath('/pipeline')64}6566export async function logActivity(formData: FormData) {67 const supabase = await createClient()68 const { data: { user } } = await supabase.auth.getUser()69 if (!user) throw new Error('Must be logged in')7071 const dealId = formData.get('deal_id') as string7273 await supabase.from('deal_activities').insert({74 deal_id: dealId,75 user_id: user.id,76 activity_type: formData.get('activity_type') as string,77 description: formData.get('description') as string,78 })7980 revalidatePath(`/deals/${dealId}`)81}Customization ideas
Add deal automation rules
Create automation triggers like 'when a deal stays in Proposal for 7 days, send a follow-up reminder' using Supabase cron jobs and edge functions.
Build email integration
Connect Gmail or Outlook to automatically log sent/received emails as deal activities when the contact email matches a deal's contact_email.
Add team collaboration
Extend deals with multiple assignees, @mention notifications in activity notes, and team-level pipeline views with shared visibility.
Build lead scoring
Use deal activity frequency, email engagement, and meeting count to auto-calculate a lead score that updates the deal's probability field.
Common pitfalls
Pitfall: Not logging stage changes in deal_activities when moving deals
How to avoid: 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.
Pitfall: Computing conversion rates on the client side from deal counts per stage
How to avoid: 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.
Pitfall: Using client-side drag-and-drop without optimistic updates
How to avoid: 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
AI prompts to try
Copy these prompts to build this project faster.
I'm building a sales funnel app with Next.js and Supabase. Write a PostgreSQL function called get_conversion_stats that takes a pipeline_id as input. It should query the deal_activities table where activity_type = 'stage_change', join with stages to get stage names, group by from_stage and to_stage, and return each transition with the count and conversion percentage. Include the CREATE FUNCTION statement.
Create a pipeline conversion funnel component. Accept an array of stage conversion data (stage_name, deal_count, conversion_to_next_percent). Render a Recharts horizontal BarChart where each bar represents a stage's deal count. Between bars, show the conversion percentage with an arrow. Color bars using each stage's color. Use Server Components for data fetching from the Supabase RPC function.
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.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation