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
- 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.`
- 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,
}
],
- 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
- 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,
}),
}
)
- 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);
- 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[];
}
- 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
- axios
- crypto
- css
- eslint
- framermotion
- git
- headlessui/react
- heroicons/react
- htmlnext.js
- javascript
- lucidereact
- node.js
- openai
- postcss
- react
- tesseract.js
- turborepo
- typescript
- uuid
- zustand
Log in or sign up for Devpost to join the conversation.