# Project export: Budgie

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: UC Berkeley AI Hackathon 2025
- Tagline: Scan. Track. Save.
- Devpost: https://devpost.com/software/budgie-gtbj82
- GitHub: https://github.com/altran03/hackathon_receipt
- Video: https://www.youtube.com/embed/xTAWALC_0yw?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 2 GitHub contributor(s) — alvintran (32 commits), lotuseana (14 commits)

## Devpost submission (written by the team)

### Inspiration

Managing personal finances can be overwhelming, especially when it comes to tracking spending, sticking to budgets, and making sense of receipts. We wanted to create a budgeting app that not only makes these tasks easier, but also feels friendly, supportive, and even a little fun. That’s why we built Budgie—a smart budgeting app with a cheerful mascot that guides, encourages, and educates users every step of the way.

### What it does

Budgie is a web app that helps users: -Track spending by category: Upload receipts or enter expenses manually, and Budgie automatically categorizes and logs your spending. -Set and manage budgets: Assign monthly budgets to categories and see real-time progress with intuitive visualizations. -Get smart, actionable tips: Budgie’s mascot uses AI to analyze your spending and provide personalized, context-aware budgeting advice. -Make adjustments easily: If you need to adjust a category’s spending, you can do so with a simple, interactive popup—no more clunky forms. -Stay motivated: Budgie’s mascot is always present, offering encouragement, tips, and even cute idle lines when you’re not actively adding items.

### How we built it

-Frontend: React.js with modern hooks and component structure for a responsive, interactive UI. -Backend: Supabase for authentication, data storage, and real-time updates. -OCR: Google Cloud Vision API extracts itemized data from uploaded receipts, making it easy to log purchases. -AI Integration: Anthropic Claude API parses the output from the Vision API and generates Budgie's spending tips. -UI/UX: Custom CSS for a playful, modern look, including a floating Budgie mascot with a speech bubble that always feels present and helpful.

### Challenges we ran into

-Context-aware tips: Generating truly helpful, non-repetitive tips required careful prompt engineering and robust error handling for the AI API. -Receipt parsing: Extracting structured data from a wide variety of receipt formats was tricky and we faced many problems where the OCR generates gibberish text -User experience: We iterated on the UI to make editing budgets and adjusting categories as seamless and intuitive as possible, including in-place editing and contextual popups. -Database: Creating unique data row entries for individual users upon log in, as opposed to only having a single global database. -Switching agents: We initially built our OCR functionality with Tesseract.js, before eventually switching to Google Cloud Vision API with much trial and error.

### Accomplishments we're proud of

-It's our first time using AI integration with a project, database, and live collaboration. -We completed a full functioning web app!

### What we learned

-The importance of keeping API Keys safe, and how to do it. -How crucial it is to frequently commit to git to prevent conflicts in the codebase.

### What's next

-Mobile app integration for Budgie. -Hosting on a live server. -Give Budgie the ability to generate acceptable budget suggestions.

## README (from the GitHub repository)

# Receipt Budget Assistant

[Live Demo on Vercel](https://hackathon-receipt.vercel.app/)

A smart, AI-powered web app to help you track spending, manage budgets, and get actionable insights from your receipts. Upload or photograph your receipts, and let Budgie (your mascot assistant) extract, categorize, and analyze your spending with the help of Google Cloud Vision, Anthropic AI, and Supabase.

---

## Features

- **Receipt OCR**: Upload images or take photos of receipts. Extracts text and itemizes purchases using Google Cloud Vision API.
- **Manual Entry**: Add spending items manually if you don't have a receipt.
- **Automatic Categorization**: Items are categorized for you, and you can adjust them as needed.
- **Budget Management**: Set, update, and track budgets for each category. Visualize your progress.
- **Spending Dashboard**: See your spending breakdown by category, with interactive charts and details.
- **AI-Powered Insights**: Get personalized, actionable budgeting tips from Budgie, powered by Anthropic AI.
- **Authentication**: Sign up or sign in with email/password or GitHub (via Supabase).

---

## Getting Started

### 1. Clone the repository

```bash
git clone https://github.com/yourusername/receipt-budget-app.git
cd receipt-budget-app
```

### 2. Install dependencies

```bash
npm install
```

### 3. Set up environment variables

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

```env
# Google Cloud Vision API Key
GOOGLE_CLOUD_VISION_API_KEY=your_google_cloud_vision_api_key_here

# Anthropic API Key (for AI analysis)
ANTHROPIC_API_KEY=your_anthropic_api_key_here

# Supabase Configuration
REACT_APP_SUPABASE_URL=your_supabase_url_here
REACT_APP_SUPABASE_ANON_KEY=your_supabase_anon_key_here
```

See [SETUP.md](SETUP.md) for detailed instructions on obtaining these keys.

### 4. Start the development server

```bash
npm start
```

Open [http://localhost:3000](http://localhost:3000) in your browser.

---

## Usage

1. **Sign up or sign in** (email/password or GitHub).
2. **Upload a receipt image** or take a photo. The app will extract and categorize items.
3. **Review and adjust** the extracted data as needed.
4. **Set budgets** for your categories and track your progress.
5. **View your dashboard** for a breakdown of spending and budgets.
6. **Get AI-powered tips** from Budgie to help you save and spend smarter.

---

## Tech Stack

- React
- Supabase (auth & database)
- Google Cloud Vision API (OCR)
- Anthropic AI (Claude)
- Chart.js (visualizations)

---

## Security Notes

- **Never commit your `.env` file** to version control.
- Restrict your API keys to specific domains/IPs when possible.
- Monitor your API usage to avoid unexpected charges.

---

## Troubleshooting

- See [SETUP.md](SETUP.md) for common issues and solutions.
- Check your environment variables and API key permissions.
- Review the browser console for errors.

---

## License

MIT


## Detected evidence (automated analysis)

Indexed codebase: 35 recognized source files, 136 KB.
- Anthropic (technology) — detected in the code
- CSS (language) — detected in the code
- HTML (language) — detected in the code
- JavaScript (language) — detected in the code
- React (technology) — detected in the code
- SQL (language) — detected in the code
- Supabase (technology) — detected in the code

## Codebase structure (from repository index)

### Files (40 of 40)

```
.gitignore
api/anthropic.js
api/supabase-admin.js
api/vision.js
database-update-v2.sql
database-update.sql
package.json
public/index.html
public/manifest.json
public/robots.txt
README.md
SETUP.md
src/App.js
src/components/auth/Auth.css
src/components/auth/Auth.js
src/components/BudgetManagement.js
src/components/camera/CameraCapture.js
src/components/charts/PieChart.js
src/components/forms/ManualEntry.js
src/components/Header.js
src/components/Mascot.css
src/components/Mascot.js
src/components/ReceiptUpload.js
src/components/SideMenu.js
src/components/SpendingDashboard.js
src/components/tips/SpendingTips.js
src/hooks/useAuth.js
src/hooks/useBudgets.js
src/hooks/useCategories.js
src/hooks/useReceiptProcessing.js
src/hooks/useSpendingItems.js
src/index.js
src/services/googleCloudVision.js
src/services/supabaseClient.js
src/styles/App.css
src/styles/index.css
src/tests/App.test.js
src/utils/reportWebVitals.js
src/utils/setupTests.js
vercel.json
```

### Dependencies

- package.json: @anthropic-ai/sdk@^0.54.0, @google-cloud/vision@^5.2.0, @supabase/supabase-js@^2.50.0, @testing-library/dom@^10.4.0, @testing-library/jest-dom@^6.6.3, @testing-library/react@^16.3.0, @testing-library/user-event@^13.5.0, chart.js@^4.5.0, react@^19.1.0, react-chartjs-2@^5.3.0, react-dom@^19.1.0, react-scripts@5.0.1, web-vitals@^2.1.4

### Recent commits (newest first)

- update readme2
- logging
- add vercel to readme
- tip improvemetn
- fix sstuff
- tone agaain
- ficx bird again
- fix bird
- tone
- debugging tips
- fix prompt for bird
- fix loaduing items
- fixed prompt
- fix json parsing
- anthropic header added
- compress images
- logging
- change verceljson
- api fixes
- change supabase keys

## Key source files (fetched from GitHub, selected and truncated for size)

### SETUP.md

```markdown
# Setup Guide for Receipt Budget Assistant

This guide will help you set up all the required API keys and environment variables for the Receipt Budget Assistant.

## Required API Keys

### 1. Google Cloud Vision API Key

The app now uses Google Cloud Vision API for OCR processing instead of Tesseract. This provides better accuracy and faster processing.

#### Steps to get your Google Cloud Vision API key:

1. Go to the [Google Cloud Console](https://console.cloud.google.com/)
2. Create a new project or select an existing one
3. Enable the Cloud Vision API:
   - Go to "APIs & Services" > "Library"
   - Search for "Cloud Vision API"
   - Click on it and press "Enable"
4. Create credentials:
   - Go to "APIs & Services" > "Credentials"
   - Click "Create Credentials" > "API Key"
   - Copy the generated API key
5. (Optional) Restrict the API key to Cloud Vision API for security

### 2. Anthropic API Key

Used for AI-powered receipt analysis and spending tips.

#### Steps to get your Anthropic API key:

1. Go to [Anthropic Console](https://console.anthropic.com/)
2. Sign up or log in
3. Go to "API Keys" section
4. Create a new API key
5. Copy the generated key

### 3. Supabase Configuration

Used for data storage and user authentication.

#### Steps to get your Supabase credentials:

1. Go to [Supabase](https://supabase.com/)
2. Create a new project or select an existing one
3. Go to "Settings" > "API"
4. Copy the "Project URL" and "anon public" key

## Environment Variables Setup

1. Create a `.env` file in the root directory of your project
2. Add the following variables:

```env
# Google Cloud Vision API Key
REACT_APP_GOOGLE_CLOUD_VISION_API_KEY=your_google_cloud_vision_api_key_here

# Anthropic API Key (for AI analysis)
REACT_APP_ANTHROPIC_API_KEY=your_anthropic_api_key_here

# Supabase Configuration
REACT_APP_SUPABASE_URL=your_supabase_url_here
REACT_APP_SUPABASE_ANON_KEY=your_supabase_anon_key_here
```

## Installation Steps

1. Install dependencies:
   ```bash
   npm install
   ```

2. Set up your environment variables (see above)

3. Start the development server:
   ```bash
   npm start
   ```

## Testing the Setup

1. Open the app in your browser
2. Sign in with your Supabase account
3. Try uploading a receipt image or taking a photo
4. The app should now use Google Cloud Vision API for OCR processing

## Troubleshooting

### Google Cloud Vision API Issues

- **"API key not valid"**: Make sure you've enabled the Cloud Vision API in your Google Cloud project
- **"Quota exceeded"**: Check your Google Cloud billing and quotas
- **"No text detected"**: Try with a clearer image or better lighting

### General Issues

- Make sure all environment variables are set correctly
- Check the browser console for any error messages
- Ensure your API keys have the necessary permissions

## Security Notes

- Never commit your `.env` file to version control
- Consider restricting your API keys to specific domains/IPs
- Monitor your API usage to avoid unexpecte
[truncated — 10 more characters]
```

### package.json

```
{
  "name": "receipt-budget-app",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@anthropic-ai/sdk": "^0.54.0",
    "@google-cloud/vision": "^5.2.0",
    "@supabase/supabase-js": "^2.50.0",
    "@testing-library/dom": "^10.4.0",
    "@testing-library/jest-dom": "^6.6.3",
    "@testing-library/react": "^16.3.0",
    "@testing-library/user-event": "^13.5.0",
    "chart.js": "^4.5.0",
    "react": "^19.1.0",
    "react-chartjs-2": "^5.3.0",
    "react-dom": "^19.1.0",
    "react-scripts": "5.0.1",
    "web-vitals": "^2.1.4"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "overrides": {
    "react": "^19.1.0"
  },
  "eslintConfig": {
    "extends": [
      "react-app",
      "react-app/jest"
    ]
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}

```

### src/index.js

```javascript
import React from 'react';
import ReactDOM from 'react-dom/client';
import './styles/index.css';
import App from './App';
import reportWebVitals from './utils/reportWebVitals';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

```

### src/App.js

```javascript
import React, { useState } from 'react';
import { useAuth } from './hooks/useAuth';
import { useCategories } from './hooks/useCategories';
import { useBudgets } from './hooks/useBudgets';
import { useReceiptProcessing } from './hooks/useReceiptProcessing';
import { useSpendingItems } from './hooks/useSpendingItems';
import Header from './components/Header';
import ReceiptUpload from './components/ReceiptUpload';
import SpendingDashboard from './components/SpendingDashboard';
import BudgetManagement from './components/BudgetManagement';
import Auth from './components/auth/Auth';
import SideMenu from './components/SideMenu';
import Mascot from './components/Mascot';
import './styles/App.css';

function App() {
  const { user, isLoading, signOut, isAuthenticated } = useAuth();
  const { 
    categories, 
    isLoading: categoriesLoading, 
    error: categoriesError, 
    addSpendingItem,
    updateCategoryAmount,
    resetAllCategories
  } = useCategories(user);
  
  const {
    budgets,
    budgetProgress,
    isLoading: budgetsLoading,
    error: budgetsError,
    updateBudget,
    createBudget,
    deleteBudget,
    fetchBudgets
  } = useBudgets(user);
  
  const { fetchAllSpendingItems, fetchSpendingItems } = useSpendingItems(user);

  const handleReceiptProcessed = async (newReceiptItems) => {
    setMascotLoading(true);
    try {
      const allEntries = await fetchAllSpendingItems();
      const tip = await getSmartTip(allEntries, newReceiptItems);
      setMascotTip(tip);
    } catch (err) {
      setMascotTip('Budgie could not fetch a tip this time.');
    } finally {
      setMascotLoading(false);
    }
  };

  const {
    selectedFile,
    previewUrl,
    ocrText,
    structuredData,
    isExtracting,
    error: receiptError,
    handleFileSelect,
    handleCapture,
    handleCancel,
    processReceipt,
    setError: setReceiptError
  } = useReceiptProcessing(addSpendingItem, fetchBudgets, categories, handleReceiptProcessed);

  const [isSubmitting, setIsSubmitting] = useState(false);
  const [isBudgetMenuOpen, setBudgetMenuOpen] = useState(false);
  const [mascotTip, setMascotTip] = useState('');
  const [mascotLoading, setMascotLoading] = useState(false);

  const handleSignOut = async () => {
    try {
      handleCancel();
      await signOut();
      setBudgetMenuOpen(false);
    } catch (error) {
      console.error('Sign out error:', error);
    }
  };

  const getSmartTip = async (allEntries, newEntryOrEntries) => {
    let prompt;
    if (Array.isArray(newEntryOrEntries)) {
      prompt = `You are Budgie, a cute, smart, friendly bird mascot for a budgeting app. Give concise, friendly, and actionable budgeting tips in a warm but succinct tone. Use at most one emoji per tip. Given the user's full spending history (as an array of entries) and the most recent receipt upload (as an array of new items), provide a positive budgeting tip or insight. Focus on helping the user spend smarter, spot trends, or stay motivated. Do not repeat previous tips. Return only the tip, no extra text or formatting.\n\nAll Entries: ${JSON.stringify(allEntries)}\nNew Receipt Items: ${JSON.stringify(newEntryOrEntries)}`;
    } else {
      prompt = `You are Budgie, a cute, smart, friendly bird mascot for a budgeting app. Give concise, friendly, and actionable budgeting tips in a warm but succinct tone. Use at most one emoji per tip. Given the user's full spending history (as an array of entries) and the most recent new entry, provide a positive budgeting tip or insight. 
      If the entry was manually entered, prioritize giving advice based on the specific entry. If the entry was uploaded from a receipt, prioritize giving advice based on the receipt's items.Focus on helping the user spend smarter, spot trends, or stay motivated. Do not repeat previous tips. Return only the tip, no extra text or formatting.\n\nAll Entries: ${JSON.stringify(allEntries)}\nNew Entry: ${JSON.stringify(newEntryOrEntries)}`;
    }
    const response = await fetch('/api/anthropic', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ prompt })
    });
    const data = await response.json();
    console.log('Anthropic API response:', data);
    console.log('Extracted tip:', data.tip);
    return data.tip || '';
  };

  const handleAddManualEntry = async ({ category: categoryName, total: amount, name: itemName }) => {
    if (!user) return;
    setIsSubmitting(true);
    setMascotLoading(true);
    try {
      if (!categoryName || !amount || !itemName) {
        throw new Error("Category, amount, and item name are required.");
      }
      if (isNaN(amount) || amount <= 0) {
        throw new Error(`Invalid amount: ${amount}`);
      }
      await addSpendingItem({ categoryName, amount, itemName });
      await fetchBudgets();
      const allEntries = await fetchAllSpendingItems();
      const newEntry = { item_name: itemName, amount, categoryName };
      const tip = await getSmartTip(allEntries, newEntry);
      setMascotTip(tip);
    } catch (err) {
      console.error(err);
      setReceiptError(err.message);
    } finally {
      setIsSubmitting(false);
      setMascotLoading(false);
    }
  };

  const handleUpdateCategory = async (categoryId, newAmount, adjustment) => {
    try {
      const category = categories.find(cat => cat.id === categoryId);
      const currentAmount = category ? category.total_spent || 0 : 0;
      const adj = adjustment !== undefined ? adjustment : newAmount - currentAmount;
      await updateCategoryAmount(categoryId, newAmount, adj);
      await fetchBudgets();
      if (adj !== 0) {
        setMascotLoading(true);
        const allEntries = await fetchAllSpendingItems();
        const newEntry = { item_name: 'Adjustment', amount: adj, categoryName: category?.name };
        const tip = await getSmartTip(allEntries, newEntry);
        setMascotTip(tip);
        setMascotLoading(false);
      }
    } catch (err) {
      console.error(e
[truncated — 2607 more characters]
```

### database-update-v2.sql

```sql
-- Create spending_items table to log individual spending entries
CREATE TABLE IF NOT EXISTS spending_items (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE NOT NULL,
  category_id BIGINT REFERENCES categories(id) ON DELETE CASCADE NOT NULL,
  item_name VARCHAR(255) NOT NULL,
  amount DECIMAL(10,2) NOT NULL,
  created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- Enable Row Level Security on spending_items table
ALTER TABLE spending_items ENABLE ROW LEVEL SECURITY;

-- Create policies for spending_items table
CREATE POLICY "Users can view their own spending items" ON spending_items
  FOR SELECT USING (auth.uid() = user_id);

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

CREATE POLICY "Users can update their own spending items" ON spending_items
  FOR UPDATE USING (auth.uid() = user_id);

CREATE POLICY "Users can delete their own spending items" ON spending_items
  FOR DELETE USING (auth.uid() = user_id);

-- Create indexes for spending_items table
CREATE INDEX IF NOT EXISTS idx_spending_items_user_id ON spending_items(user_id);
CREATE INDEX IF NOT EXISTS idx_spending_items_category_id ON spending_items(category_id); 
```

### database-update.sql

```sql
-- Update existing database to support user authentication and personalized data

-- Add user_id column to existing categories table
ALTER TABLE categories ADD COLUMN IF NOT EXISTS user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE;

-- Add unique constraint to prevent duplicate categories per user (with error handling)
DO $$
BEGIN
    IF NOT EXISTS (
        SELECT 1 FROM pg_constraint 
        WHERE conname = 'unique_user_category'
    ) THEN
        ALTER TABLE categories ADD CONSTRAINT unique_user_category UNIQUE(user_id, name);
    END IF;
END $$;

-- Enable Row Level Security on categories table
ALTER TABLE categories ENABLE ROW LEVEL SECURITY;

-- Drop existing policies if they exist (to avoid conflicts)
DROP POLICY IF EXISTS "Users can view their own categories" ON categories;
DROP POLICY IF EXISTS "Users can insert their own categories" ON categories;
DROP POLICY IF EXISTS "Users can update their own categories" ON categories;
DROP POLICY IF EXISTS "Users can delete their own categories" ON categories;

-- Create policies to allow users to only access their own categories
CREATE POLICY "Users can view their own categories" ON categories
  FOR SELECT USING (auth.uid() = user_id);

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

CREATE POLICY "Users can update their own categories" ON categories
  FOR UPDATE USING (auth.uid() = user_id);

CREATE POLICY "Users can delete their own categories" ON categories
  FOR DELETE USING (auth.uid() = user_id);

-- Create budgets table for smart budget tracking
-- Note: Using BIGINT for category_id to match the categories table id type
CREATE TABLE IF NOT EXISTS budgets (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE NOT NULL,
  category_id BIGINT REFERENCES categories(id) ON DELETE CASCADE NOT NULL,
  budget_amount DECIMAL(10,2) NOT NULL DEFAULT 0.00,
  budget_type VARCHAR(10) NOT NULL DEFAULT 'monthly' CHECK (budget_type IN ('weekly', 'monthly')),
  start_date DATE NOT NULL DEFAULT CURRENT_DATE,
  end_date DATE,
  is_active BOOLEAN DEFAULT true,
  created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
  updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
  UNIQUE(user_id, category_id, budget_type)
);

-- Enable Row Level Security on budgets table
ALTER TABLE budgets ENABLE ROW LEVEL SECURITY;

-- Drop existing policies if they exist (to avoid conflicts)
DROP POLICY IF EXISTS "Users can view their own budgets" ON budgets;
DROP POLICY IF EXISTS "Users can insert their own budgets" ON budgets;
DROP POLICY IF EXISTS "Users can update their own budgets" ON budgets;
DROP POLICY IF EXISTS "Users can delete their own budgets" ON budgets;

-- Create policies for budgets table
CREATE POLICY "Users can view their own budgets" ON budgets
  FOR SELECT USING (auth.uid() = user_id);

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

CREATE POLICY "Users can update their own budgets" ON budgets
  FOR UPDATE USING (auth.uid() = user_id);

CREATE POLICY "Users can delete their own budgets" ON budgets
  FOR DELETE USING (auth.uid() = user_id);

-- Create indexes for budgets table
CREATE INDEX IF NOT EXISTS idx_budgets_user_id ON budgets(user_id);
CREATE INDEX IF NOT EXISTS idx_budgets_category_id ON budgets(category_id);
CREATE INDEX IF NOT EXISTS idx_budgets_active ON budgets(is_active);

-- Create function to automatically create default categories for new users
CREATE OR REPLACE FUNCTION public.handle_new_user()
RETURNS TRIGGER AS $$
BEGIN
  INSERT INTO public.categories (user_id, name, total_spent)
  VALUES 
    (NEW.id, 'Food and Drink', 0.00),
    (NEW.id, 'Clothing', 0.00),
    (NEW.id, 'Utilities', 0.00),
    (NEW.id, 'Entertainment', 0.00),
    (NEW.id, 'Tax', 0.00),
    (NEW.id, 'Other', 0.00);
  RETURN NEW;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;

-- Create function to automatically create default budgets for new users
CREATE OR REPLACE FUNCTION public.handle_new_user_budgets()
RETURNS TRIGGER AS $$
BEGIN
  -- Insert default monthly budgets for each category
  INSERT INTO public.budgets (user_id, category_id, budget_amount, budget_type, start_date)
  SELECT 
    NEW.id,
    c.id,
    CASE 
      WHEN c.name = 'Food and Drink' THEN 500.00
      WHEN c.name = 'Clothing' THEN 200.00
      WHEN c.name = 'Utilities' THEN 300.00
      WHEN c.name = 'Entertainment' THEN 150.00
      WHEN c.name = 'Tax' THEN 100.00
      ELSE 100.00
    END,
    'monthly',
    CURRENT_DATE
  FROM public.categories c
  WHERE c.user_id = NEW.id;
  
  RETURN NEW;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;

-- Drop existing trigger if it exists
DROP TRIGGER IF EXISTS on_auth_user_created ON auth.users;

-- Create trigger to automatically create default categories when a user signs up
CREATE TRIGGER on_auth_user_created
  AFTER INSERT ON auth.users
  FOR EACH ROW EXECUTE FUNCTION public.handle_new_user();

-- Create trigger for budgets (runs after categories are created)
DROP TRIGGER IF EXISTS on_auth_user_created_budgets ON auth.users;
CREATE TRIGGER on_auth_user_created_budgets
  AFTER INSERT ON auth.users
  FOR EACH ROW EXECUTE FUNCTION public.handle_new_user_budgets();

-- Create function to calculate budget progress
CREATE OR REPLACE FUNCTION public.get_budget_progress(
  p_user_id UUID,
  p_category_id BIGINT,
  p_budget_type VARCHAR(10) DEFAULT 'monthly'
)
RETURNS TABLE (
  category_name VARCHAR(255),
  budget_amount DECIMAL(10,2),
  spent_amount DECIMAL(10,2),
  remaining_amount DECIMAL(10,2),
  progress_percentage DECIMAL(5,2),
  is_over_budget BOOLEAN,
  alert_level VARCHAR(20)
) AS $$
BEGIN
  RETURN QUERY
  SELECT 
    c.name as category_name,
    COALESCE(b.budget_amount, 0) as budget_amount,
    COALESCE(c.total_spent, 0) as spent_amount,
    GREATEST(COALESCE(b.budget_amount, 0) - COALESCE(c.total_spent, 0), 0) as remaining_amount,
    CASE 
      WHEN COALESCE(b.budget_amo
[truncated — 2004 more characters]
```

### api/supabase-admin.js

```javascript
import { createClient } from '@supabase/supabase-js';

export default async function handler(req, res) {
  const supabaseUrl = process.env.SUPABASE_URL;
  const supabaseServiceKey = process.env.SUPABASE_SERVICE_KEY; // Use service key, not anon key

  const supabase = createClient(supabaseUrl, supabaseServiceKey);

  // Example: List all users (admin action)
  const { data, error } = await supabase.auth.admin.listUsers();
  if (error) return res.status(500).json({ error: error.message });
  res.status(200).json(data);
}

```

### api/vision.js

```javascript
export default async function handler(req, res) {
  if (req.method !== 'POST') {
    return res.status(405).json({ error: 'Method not allowed' });
  }

  try {
    const { imageBase64 } = req.body;
    const apiKey = process.env.GOOGLE_CLOUD_VISION_API_KEY; // No REACT_APP_ prefix

    const response = await fetch(
      `https://vision.googleapis.com/v1/images:annotate?key=${apiKey}`,
      {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          requests: [
            {
              image: { content: imageBase64 },
              features: [{ type: 'DOCUMENT_TEXT_DETECTION', maxResults: 1 }]
            }
          ]
        })
      }
    );

    if (!response.ok) {
      const errorText = await response.text();
      return res.status(response.status).json({ error: 'Vision API error', details: errorText });
    }

    const data = await response.json();
    return res.status(200).json(data);
  } catch (err) {
    return res.status(500).json({ error: 'Internal server error', details: err.message });
  }
}

```

### public/index.html

```html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="icon" href="budgie.png" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="theme-color" content="#000000" />
    <meta
      name="description"
      content="Web site created using create-react-app"
    />
    <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
    <!--
      manifest.json provides metadata used when your web app is installed on a
      user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
    -->
    <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
    <!--
      Notice the use of %PUBLIC_URL% in the tags above.
      It will be replaced with the URL of the `public` folder during the build.
      Only files inside the `public` folder can be referenced from the HTML.

      Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
      work correctly both with client-side routing and a non-root public URL.
      Learn how to configure a non-root public URL by running `npm run build`.
    -->
    <title>Budgie</title>
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
    <!--
      This HTML file is a template.
      If you open it directly in the browser, you will see an empty page.

      You can add webfonts, meta tags, or analytics to this file.
      The build step will place the bundled scripts into the <body> tag.

      To begin the development, run `npm start` or `yarn start`.
      To create a production bundle, use `npm run build` or `yarn build`.
    -->
  </body>
</html>

```

### api/anthropic.js

```javascript
export default async function handler(req, res) {
  if (req.method !== 'POST') {
    return res.status(405).json({ error: 'Method not allowed' });
  }

  const apiKey = process.env.ANTHROPIC_API_KEY; // No REACT_APP_ prefix
  const anthropicUrl = 'https://api.anthropic.com/v1/messages';

  // Log the incoming prompt for debugging
  console.log('Received prompt:', req.body?.prompt);

  // Support both prompt (simple) and full payload (advanced)
  let payload;
  if (req.body && typeof req.body.prompt === 'string') {
    payload = {
      model: 'claude-3-haiku-20240307',
      max_tokens: 256,
      messages: [
        { role: 'user', content: req.body.prompt }
      ]
    };
  } else {
    // Assume the frontend sent a full Anthropic payload (model, max_tokens, messages, etc.)
    payload = req.body;
  }

  const response = await fetch(anthropicUrl, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'x-api-key': apiKey,
      'anthropic-version': '2023-06-01'
    },
    body: JSON.stringify(payload)
  });

  const data = await response.json();
  // Log the Anthropic API response for debugging
  console.log('Anthropic API raw response:', data);
  // Extract tip from the response (Anthropic Claude returns it in content[0].text)
  let tip = '';
  if (data && Array.isArray(data.content) && data.content.length > 0 && data.content[0].text) {
    tip = data.content[0].text.trim();
    // If the tip is a JSON string, parse it
    try {
      const parsed = JSON.parse(tip);
      if (typeof parsed === 'string') {
        tip = parsed;
      }
    } catch (e) {
      // Not JSON, keep as is
    }
  }
  res.status(response.status).json({ ...data, tip });
}

```

[25 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]