YABA & YAHA by Konrad Wilson & Jose Rodriguez

How we built it

We built YABA using a modern tech stack:

  • Frontend: Next.js 14 with TypeScript for type safety and better developer experience
    "next": "15.1.6",
    "openai": "^4.83.0",
    "react": "^19.0.0",
    "react-dom": "^19.0.0",
  • State Management: Zustand for simple but powerful state management
import { create } from 'zustand';
import { User, Transaction, UserStore, AIInsight, Achievement, UserState } from '../types';
import { generateRandomUser } from '../utils/mockDataGenerator';
import { persist, createJSONStorage } from 'zustand/middleware';
  • Styling: Tailwind CSS for rapid UI development and consistent design
      <body className={plusJakartaSans.className}>
        <div className="min-h-screen bg-gray-50 dark:bg-gray-900 text-gray-900 dark:text-gray-100">
          <Header />
          <main className="pb-16 px-4 max-w-7xl mx-auto">
            {children}
          </main>
          <Navigation />
        </div>
      </body>
  • AI Integration: OpenAI's GPT-4 for generating financial insights and personalized playlists
export async function generateFinancialInsights(transactions: Transaction[], userId: string): Promise<AIInsight> {
  const response = await openai.chat.completions.create({
    model: "gpt-4o-mini",
    messages: [
      {
        role: "system",
        content: `You are a financial advisor analyzing transaction data. You must respond with ONLY a JSON object in this exact format:
{
  "summary": "Brief summary of financial status",
  "advice": "Actionable financial advice",
  "spendingAnalysis": {
    "categories": { "category1": amount1, "category2": amount2 },
    "trends": ["trend1", "trend2"],
    "recommendations": ["recommendation1", "recommendation2"]
  },
  "playlist": [  // Must contain between 5-7 songs, inclusive. 40% of songs should be inspired by transaction categories/types (e.g., shopping, dining, travel) rather than financial amounts
    {
      "id": "unique-id",
      "title": "Song Title",
      "artist": "Artist Name",
      "mood": "happy/sad/etc",
      "reason": "Why this song matches the financial mood or transaction category"
    }
  ]
}
Note: The playlist MUST contain between 5 and 7 songs, no more and no less. Approximately 40% of the songs should be themed around transaction categories and activities rather than financial amounts.`
      },
      {
        role: "user",
        content: JSON.stringify({ transactions, userId })
      }
    ],
    temperature: 0.7,
    max_tokens: 1000,
    response_format: { type: "json_object" }
  });
  • Animation: Framer Motion for smooth UI transitions
        <motion.div
          className={`text-4xl font-medium mb-8 ${
            isAnimating ? 'text-green-400' : 'text-white'
          }`}
          animate={{
            scale: isAnimating ? 1.05 : 1,
            transition: { duration: 0.2 }
          }}
        >
          ${user.balance.toLocaleString()}
        </motion.div>

Accomplishments that we're proud of

  1. AI-Powered Insights: Successfully implemented OpenAI integration to analyze transaction patterns and generate meaningful financial insights
export async function generateFinancialInsights(transactions: Transaction[], userId: string): Promise<AIInsight> {
  const response = await openai.chat.completions.create({
    model: "gpt-4o-mini",
    messages: [
      {
        role: "system",
        content: `You are a financial advisor analyzing transaction data. You must respond with ONLY a JSON object in this exact format:
{
  "summary": "Brief summary of financial status",
  "advice": "Actionable financial advice",
  "spendingAnalysis": {
    "categories": { "category1": amount1, "category2": amount2 },
    "trends": ["trend1", "trend2"],
    "recommendations": ["recommendation1", "recommendation2"]
  },
  "playlist": [  // Must contain between 5-7 songs, inclusive. 40% of songs should be inspired by transaction categories/types (e.g., shopping, dining, travel) rather than financial amounts
    {
      "id": "unique-id",
      "title": "Song Title",
      "artist": "Artist Name",
      "mood": "happy/sad/etc",
      "reason": "Why this song matches the financial mood or transaction category"
    }
  ]
}
Note: The playlist MUST contain between 5 and 7 songs, no more and no less. Approximately 40% of the songs should be themed around transaction categories and activities rather than financial amounts.`
  1. Achievement System: Created a comprehensive achievement system that gamifies financial wellness
export const ACHIEVEMENTS = {
  SAVINGS_MILESTONES: [
    {
      id: 'savings-100',
      name: 'First $100 Saved',
      description: 'Save your first $100',
      category: 'savings',
      target: 100,
    },
    {
      id: 'savings-1000',
      name: 'Savings Master',
      description: 'Save $1,000',
      category: 'savings',
      target: 1000,
    },
    {
      id: 'savings-5000',
      name: 'Savings Expert',
      description: 'Save $5,000',
      category: 'savings',
      target: 5000,
    },
    {
      id: 'savings-10000',
      name: 'Savings Legend',
      description: 'Save $10,000',
      category: 'savings',
      target: 10000,
    }
  ],
  STREAK_MILESTONES: [
    {
      id: 'streak-7',
      name: 'Week Warrior',
      description: 'Maintain a 7-day login streak',
      category: 'streak',
      target: 7,
    },
    {
      id: 'streak-30',
      name: 'Monthly Master',
      description: 'Maintain a 30-day login streak',
      category: 'streak',
      target: 30,
    },
    {
      id: 'streak-90',
      name: 'Quarterly Champion',
      description: 'Maintain a 90-day login streak',
      category: 'streak',
      target: 90,
    },
    {
      id: 'streak-365',
      name: 'Year of Excellence',
      description: 'Maintain a 365-day login streak',
      category: 'streak',
      target: 365,
    }
  ],
  1. Dynamic Transaction Processing: Built a robust system for handling and categorizing financial transactions
export function generateRandomTransactions(userId: string, numTransactions: number = 20): Transaction[] {
  const endDate = new Date();
  const startDate = new Date();
  startDate.setDate(endDate.getDate() - 30); // Last 30 days

  const transactions: Transaction[] = [];

  for (let i = 0; i < numTransactions; i++) {
    const isIncome = Math.random() < 0.3; // 30% chance of being income
    const category = getRandomElement(isIncome ? categories.income : categories.expense);
    const amount = isIncome
      ? getRandomAmount(100, 5000)
      : getRandomAmount(10, 1000);

    const locationCategory = locations[category as keyof typeof locations];
    const location = locationCategory ? getRandomElement(locationCategory) : undefined;

    transactions.push({
      id: uuidv4(),
      userId,
      amount,
      type: isIncome ? 'credit' : 'debit',
      category,
      timestamp: getRandomDate(startDate, endDate),
      description: `${category} - ${isIncome ? 'Payment' : 'Purchase'}`,
      location,
      tags: getRandomTags(category)
    });
  }

  // Sort by timestamp in descending order
  return transactions.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());
}

What we learned

  1. State Management at Scale: Learned how to effectively manage complex application state using Zustand
        }),
    }),
    {
      name: 'app-user',
      storage: createJSONStorage(() => localStorage),
      partialize: (state) => ({
        user: state.user,
        transactions: state.transactions,
        insights: state.insights,
      }),
    }
  )
  1. AI Integration: Gained experience in integrating AI services for real-world applications
export async function processImages(files: File[]): Promise<ExtractedData> {
  console.log(`Starting OpenAI vision processing for ${files.length} files`);
  try {
    const base64Images = await Promise.all(files.map(fileToBase64));
    console.log('All images converted successfully');

    const response = await openai.chat.completions.create({
      model: "gpt-4o-mini",
      messages: [
        {
          role: "system",
          content: `You are a financial data extraction expert. Analyze the bank statement images and respond with ONLY a JSON object in this exact format:
{
  "transactions": [
    {
      "date": "YYYY-MM-DD",
      "amount": number,
      "type": "credit" | "debit",
      "description": "string",
      "category": "string"
    }
  ],
  "balance": number
}`
        },
        {
          role: "user",
          content: [
            {
              type: "text",
              text: "Extract the financial data from these bank statement images. Respond with ONLY the JSON object, no other text."
            } as const,
            ...base64Images.map(base64 => ({
              type: "image_url" as const,
              image_url: {
                url: `data:image/jpeg;base64,${base64}`,
                detail: "high" as const
              }
            }))
          ]
        }
      ],
      max_tokens: 4096,
      response_format: { type: "json_object" }
    });
    const content = response.choices[0].message.content;
    if (!content) {
      throw new Error('No content in OpenAI response');
    }

    const parsedData = JSON.parse(content);
  1. TypeScript Best Practices: Improved our TypeScript skills through comprehensive type definitions
export interface UserPreferences {
  theme: 'light' | 'dark';
  notificationsEnabled: boolean;
  musicPreferences: {
    genres: string[];
    favoriteArtists: string[];
  };
  privacySettings: {
    shareInsights: boolean;
    shareAchievements: boolean;
  };
}

export interface User {
  id: string;
  name: string;
  email: string;
  balance: number;
  streak: {
    current: number;
    lastLogin: Date;
    highestStreak: number;
    multiplier: number;
    streakProtection: number;
  };
  achievements: Achievement[];
  preferences: UserPreferences;
  transactions: Transaction[];
  insightStats: {
    consecutiveDaysViewed: number;
    lastViewed: Date;
    totalViews: number;
  };
  budgetStats: {
    goalsSet: number;
    goalsAchieved: number;
    monthlyStreak: number;
    lastUpdated: Date;
  };
  investmentStats: {
    totalInvestments: number;
    categories: Set<string>;
    returns: number;
    lastUpdated: Date;
  };
}

export interface Achievement {
  id: string;
  name: string;
  description: string;
  category: 'savings' | 'streak' | 'transaction' | 'literacy' | 'budget' | 'investment';
  earned: boolean;
  date: Date;
  target: number;
  progress?: {
    current: number;
    target: number;
  };
}
export interface Transaction {
  id: string;
  userId: string;
  amount: number;
  type: 'credit' | 'debit';
  category: string;
  timestamp: Date;
  description: string;
  location?: string;
  tags?: string[];
}

export interface ArtistImage {
  small: string;
  medium: string;
  large: string;
}

export interface Artist {
  name: string;
  images: ArtistImage;
  url?: string;
}

export interface Song {
  id: string;
  title: string;
  artist: string;
  artistImage?: string | null;
  mood: string;
  reason: string;
  url?: string;
}

export interface AIInsight {
  id: string;
  userId: string;
  date: Date;
  summary: string;
  advice: string;
  spendingAnalysis: {
    categories: Record<string, number>;
    trends: string[];
    recommendations: string[];
  };
  playlist: Song[];
}
  1. Modern React Patterns: Mastered Next.js 14 features and React patterns for building performant applications
'use client';

import { useEffect } from 'react';
import { Dashboard } from '../components/Dashboard';
import { useUserStore } from '../store/userStore';
import { generateFinancialInsights } from '../services/openai';
import { checkAchievements, updateStreak } from '../services/achievements';
import { generateRandomUser } from '../utils/mockDataGenerator';

export default function Home() {
  const { user, transactions, setUser } = useUserStore();

  useEffect(() => {
    // Create a user if one doesn't exist
    if (!user) {
      const newUser = generateRandomUser();
      setUser(newUser);
      return;
    }

    // Rest of the useEffect logic for existing users
    const updatedUser = updateStreak(user);
    setUser(updatedUser);

    // Check for new achievements
    const newAchievements = checkAchievements(updatedUser, transactions);
    if (newAchievements.length > 0) {
      setUser({
        ...updatedUser,
        achievements: [...updatedUser.achievements, ...newAchievements],
      });
    }

    // Generate new insights
    generateFinancialInsights(transactions, user.id).catch(console.error);
  }, [user?.id]);

  if (!user) {
    return (
      <div className="flex items-center justify-center min-h-screen">
        <div className="text-center">
          <h1 className="text-2xl font-bold mb-4">Welcome to Financial Wellness</h1>
          <p className="text-gray-600 dark:text-gray-300">Please sign in to continue</p>
        </div>
      </div>
    );
  }

  return <Dashboard />;
}

This project helped us grow as developers while creating something useful that combines financial technology with AI-driven insights and music recommendations.

Built With

Share this project:

Updates