Intermediate ~4 hours

Build an AI-Powered FinTech Dashboard

Create a personal finance dashboard that connects to bank accounts, categorizes transactions using AI, and provides intelligent spending insights and recommendations.

What We're Building

In this guide, you will build a personal finance dashboard that helps users understand their spending patterns through AI-powered insights. The application connects to bank accounts (via manual CSV import or mock data for this tutorial), automatically categorizes transactions using Claude's AI, and generates actionable recommendations to improve financial health.

By the end of this guide, you will have a fully functional dashboard with:

  • User authentication with secure session management
  • Transaction import and automatic AI categorization
  • Interactive spending breakdown charts
  • AI-generated spending insights and recommendations
  • Date filtering and category management
  • Responsive design that works on desktop and mobile

This project demonstrates a practical application of AI in financial technology - using language models not just for chat, but for structured data analysis and categorization tasks that traditionally required complex rule-based systems.

Prerequisites

Before starting this guide, make sure you have the following:

  • Basic knowledge of React/Next.js - You should understand components, hooks, and basic routing
  • Understanding of REST APIs - Familiarity with making HTTP requests and handling responses
  • Node.js 18+ installed locally - Run node -v to check your version
  • A code editor - VS Code with the Cursor AI extension is recommended

You will also need free accounts for the following services:

Tech Stack Specification

Here is the technology stack we will use for this build, with reasoning for each choice:

Layer Technology Why This Choice
Frontend Next.js 14, TypeScript, Tailwind CSS Server components for performance, type safety to catch errors early, utility-first CSS for rapid styling
Backend Supabase Edge Functions Serverless architecture with Deno runtime, integrated with database, handles API keys securely
Database Supabase PostgreSQL Real-time subscriptions, built-in auth, generous free tier, Row Level Security for data protection
AI Claude API (claude-3-haiku) Fast response times (~200ms), cost-effective for categorization tasks ($0.25/1M input tokens), high accuracy
Auth Supabase Auth Seamless integration with database RLS, supports email/password and OAuth, handles sessions automatically
Charts Recharts Built specifically for React, highly customizable, good documentation, responsive out of the box
Hosting Vercel Zero-config Next.js deployment, automatic HTTPS, edge network for global performance

AI Agent Workflow

Modern AI-assisted development works best when you use the right tool for each task. Here is how to leverage different AI tools throughout this build:

Project Scaffolding with Claude Code

Use Claude Code (Anthropic's CLI tool) for the initial project setup, database schema design, and implementing the core AI integration. Claude Code excels at understanding your project structure and generating consistent code across multiple files.

# Example prompt for Claude Code:

Create a Next.js 14 app with TypeScript and Tailwind CSS for a
personal finance dashboard. Include:
- Supabase client setup with environment variables
- Authentication with Supabase Auth (email/password)
- A transactions table component with sorting and filtering
- An API route that calls Claude to categorize transactions
- Types for User, Transaction, and Category entities

Use the App Router and Server Components where appropriate.
Structure the project with /app, /components, /lib, and /types folders.

UI Generation with v0.dev

Use v0.dev (Vercel's AI UI generator) for creating polished UI components quickly. It generates shadcn/ui compatible components that integrate seamlessly with your Next.js project.

Recommended v0.dev prompts for this project:

  • "Dashboard layout with sidebar navigation and main content area, dark theme"
  • "Transaction list with amount, merchant, category badge, and date columns"
  • "Spending breakdown pie chart with legend and hover tooltips"
  • "Settings page with profile form and notification preferences"
Pro Tip

When using v0.dev, be specific about the data shape you need. Include example transaction objects in your prompt so the generated components match your TypeScript types exactly.

Development with Cursor

Use Cursor (AI-powered VS Code fork) for day-to-day development tasks. It excels at:

  • Debugging issues by selecting code and asking "why isn't this working?"
  • Refactoring components to improve performance or readability
  • Writing unit tests by selecting a function and prompting "write tests for this"
  • Adding TypeScript types to untyped code

Step-by-Step Build Guide

Phase 1: Project Setup

Start by creating a new Next.js project with the required dependencies. Open your terminal and run:

Terminal
# Create Next.js project with TypeScript
npx create-next-app@latest fintech-dashboard --typescript --tailwind --eslint --app --src-dir

# Navigate to project
cd fintech-dashboard

# Install dependencies
npm install @supabase/supabase-js @supabase/ssr recharts @anthropic-ai/sdk
npm install -D @types/node

Next, create your Supabase project at supabase.com/dashboard. Once created, grab your project URL and anon key from Settings > API.

Create a .env.local file in your project root:

.env.local
# Supabase
NEXT_PUBLIC_SUPABASE_URL=your_supabase_project_url
NEXT_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key

# Anthropic (for server-side only)
ANTHROPIC_API_KEY=your_anthropic_api_key

Phase 2: Database Schema

Create the database tables in Supabase. Go to the SQL Editor in your Supabase dashboard and run the following:

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

-- Categories table
CREATE TABLE categories (
  id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
  name TEXT NOT NULL UNIQUE,
  color TEXT NOT NULL,
  icon TEXT
);

-- Insert default categories
INSERT INTO categories (name, color, icon) VALUES
  ('Food & Dining', '#10b981', 'utensils'),
  ('Transportation', '#3b82f6', 'car'),
  ('Shopping', '#8b5cf6', 'shopping-bag'),
  ('Entertainment', '#f59e0b', 'film'),
  ('Bills & Utilities', '#ef4444', 'file-text'),
  ('Health', '#ec4899', 'heart'),
  ('Income', '#22c55e', 'dollar-sign'),
  ('Other', '#6b7280', 'more-horizontal');

-- Transactions table
CREATE TABLE transactions (
  id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
  user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE,
  amount DECIMAL(10,2) NOT NULL,
  description TEXT NOT NULL,
  merchant TEXT,
  category_id UUID REFERENCES categories(id),
  transaction_date DATE NOT NULL,
  created_at TIMESTAMPTZ DEFAULT NOW()
);

-- Enable Row Level Security
ALTER TABLE transactions ENABLE ROW LEVEL SECURITY;

-- RLS Policy: Users can only see their own transactions
CREATE POLICY "Users can view own transactions"
  ON transactions FOR SELECT
  USING (auth.uid() = user_id);

CREATE POLICY "Users can insert own transactions"
  ON transactions FOR INSERT
  WITH CHECK (auth.uid() = user_id);

-- Create index for faster queries
CREATE INDEX idx_transactions_user_date
  ON transactions(user_id, transaction_date DESC);

Phase 3: Authentication Flow

Set up the Supabase client and authentication context. Create the following files:

src/lib/supabase/client.ts
import { createBrowserClient } from '@supabase/ssr'

export function createClient() {
  return createBrowserClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
  )
}

// Export a singleton for client components
export const supabase = createClient()
src/lib/supabase/server.ts
import { createServerClient } from '@supabase/ssr'
import { cookies } from 'next/headers'

export async function createClient() {
  const cookieStore = await cookies()

  return createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    {
      cookies: {
        getAll() {
          return cookieStore.getAll()
        },
        setAll(cookiesToSet) {
          try {
            cookiesToSet.forEach(({ name, value, options }) =>
              cookieStore.set(name, value, options)
            )
          } catch {
            // Ignore - called from Server Component
          }
        },
      },
    }
  )
}

Phase 4: Core Dashboard

Build the main dashboard component that displays transactions and spending charts. Here is the transaction list component:

src/components/TransactionList.tsx
'use client'

import { useState, useEffect } from 'react'
import { supabase } from '@/lib/supabase/client'
import { Transaction } from '@/types'

export function TransactionList() {
  const [transactions, setTransactions] = useState<Transaction[]>([])
  const [loading, setLoading] = useState(true)

  useEffect(() => {
    async function fetchTransactions() {
      const { data, error } = await supabase
        .from('transactions')
        .select(`
          *,
          category:categories(name, color)
        `)
        .order('transaction_date', { ascending: false })
        .limit(50)

      if (!error && data) {
        setTransactions(data)
      }
      setLoading(false)
    }

    fetchTransactions()
  }, [])

  if (loading) {
    return <div className="animate-pulse">Loading...</div>
  }

  return (
    <div className="bg-zinc-900 rounded-xl border border-zinc-800">
      <div className="p-4 border-b border-zinc-800">
        <h2 className="text-lg font-semibold">Recent Transactions</h2>
      </div>
      <div className="divide-y divide-zinc-800">
        {transactions.map((tx) => (
          <div key={tx.id} className="p-4 flex justify-between items-center">
            <div>
              <p className="font-medium">{tx.description}</p>
              <p className="text-sm text-zinc-400">{tx.merchant}</p>
            </div>
            <div className="text-right">
              <p className={tx.amount < 0 ? 'text-red-400' : 'text-green-400'}>
                {tx.amount < 0 ? '-' : '+'}${Math.abs(tx.amount).toFixed(2)}
              </p>
              <span
                className="text-xs px-2 py-1 rounded-full"
                style={{ backgroundColor: tx.category?.color + '20', color: tx.category?.color }}
              >
                {tx.category?.name}
              </span>
            </div>
          </div>
        ))}
      </div>
    </div>
  )
}

Phase 5: AI Integration

This is where the magic happens. Create an API route that uses Claude to automatically categorize transactions:

src/app/api/categorize/route.ts
import { NextRequest, NextResponse } from 'next/server'
import Anthropic from '@anthropic-ai/sdk'

const anthropic = new Anthropic({
  apiKey: process.env.ANTHROPIC_API_KEY,
})

const CATEGORIES = [
  'Food & Dining',
  'Transportation',
  'Shopping',
  'Entertainment',
  'Bills & Utilities',
  'Health',
  'Income',
  'Other'
]

export async function POST(request: NextRequest) {
  try {
    const { transactions } = await request.json()

    const prompt = `Categorize each transaction into exactly one category.

Available categories: ${CATEGORIES.join(', ')}

Transactions to categorize:
${transactions.map((t: any, i: number) =>
  `${i + 1}. "${t.description}" - ${t.merchant || 'Unknown merchant'} - $${t.amount}`
).join('\n')}

Respond with a JSON array of category names in the same order.
Example: ["Food & Dining", "Transportation", "Shopping"]
Only respond with the JSON array, no other text.`

    const response = await anthropic.messages.create({
      model: 'claude-3-haiku-20240307',
      max_tokens: 1024,
      messages: [{ role: 'user', content: prompt }],
    })

    const content = response.content[0]
    if (content.type === 'text') {
      const categories = JSON.parse(content.text)
      return NextResponse.json({ categories })
    }

    return NextResponse.json({ error: 'Invalid response' }, { status: 500 })
  } catch (error) {
    console.error('Categorization error:', error)
    return NextResponse.json({ error: 'Failed to categorize' }, { status: 500 })
  }
}
Why claude-3-haiku?

For categorization tasks, claude-3-haiku is ideal because it is fast (~200ms response time), cost-effective ($0.25 per million input tokens), and accurate enough for structured classification. Save claude-3-opus for complex reasoning tasks.

Phase 6: Polish and Deploy

Before deploying, add proper loading states, error boundaries, and responsive design. Then deploy to Vercel:

Terminal
# Install Vercel CLI
npm install -g vercel

# Deploy (follow prompts to link project)
vercel

# Set environment variables in Vercel dashboard
# or use: vercel env add ANTHROPIC_API_KEY

Remember to add your environment variables in the Vercel dashboard under Settings > Environment Variables. Your Supabase URL and anon key can be public, but the Anthropic API key must be server-side only.

Common Issues and Solutions

Here are the most common issues developers encounter when building this project:

Rate Limiting with Claude API

If you hit rate limits, implement exponential backoff and batch your categorization requests. Process transactions in groups of 10-20 rather than one at a time.

Supabase RLS Policy Mistakes

If queries return empty results, check your Row Level Security policies. The most common mistake is forgetting to add the INSERT policy or using auth.uid() incorrectly. Test policies in the Supabase SQL editor first.

TypeScript Type Errors with Supabase

Generate types from your Supabase schema using npx supabase gen types typescript to avoid type mismatches between your code and database.

Next Steps

You now have a functional AI-powered finance dashboard. Here are ways to extend it further:

  • Add Plaid Integration - Connect to real bank accounts using Plaid API for automatic transaction imports
  • Build Spending Predictions - Use historical data to predict future spending with Claude's analysis
  • Create Budget Alerts - Set spending limits per category and get notified when approaching them
  • Add Export Features - Generate PDF reports or CSV exports of spending summaries
  • Implement Recurring Detection - Use AI to automatically identify and track recurring subscriptions