From 966d3a2232c811ec6dd6b0e5b17da46d4f29c44c Mon Sep 17 00:00:00 2001 From: Mann Patel <130435633+MannPatel0@users.noreply.github.com> Date: Sun, 16 Feb 2025 09:52:09 -0700 Subject: [PATCH] update final design --- SerialToSerialBT.ino | 138 +++++++++++ frontend/src/App.jsx | 15 +- frontend/src/pages/ExpenceTracker.jsx | 1 + frontend/src/pages/QuizPage.jsx | 332 +++++++++----------------- frontend/src/quizInsert.js | 87 +++++++ test.py | 103 ++++++++ 6 files changed, 440 insertions(+), 236 deletions(-) create mode 100644 SerialToSerialBT.ino create mode 100644 frontend/src/quizInsert.js create mode 100644 test.py diff --git a/SerialToSerialBT.ino b/SerialToSerialBT.ino new file mode 100644 index 0000000..b5372b0 --- /dev/null +++ b/SerialToSerialBT.ino @@ -0,0 +1,138 @@ +#include +#include + +// LCD Pin Configuration +LiquidCrystal lcd(14, 27, 26, 25, 33, 32); + +// JSON parsing buffer +StaticJsonDocument<2048> jsonDoc; + +// Financial tracking structures +struct FinancialSummary { + float bankBalance; + float totalIncome; + float totalExpenses; + float netAmount; + int transactionCount; +}; + +FinancialSummary financialData = {0, 0, 0, 0, 0}; + +// Display states +enum DisplayState { + BANK_BALANCE, + INCOME_EXPENSES, + TRANSACTION_COUNT +}; +DisplayState currentState = BANK_BALANCE; + +void setup() { + // Initialize serial communication + Serial.begin(115200); + + // Initialize LCD + lcd.begin(16, 2); + lcd.clear(); + + // Display startup message + lcd.setCursor(0, 0); + lcd.print("Finance Tracker"); + lcd.setCursor(0, 1); + lcd.print("Waiting for Data"); +} + +void parseFinancialData(String jsonString) { + // Reset financial data + financialData = {0, 0, 0, 0, 0}; + + // Parse JSON + DeserializationError error = deserializeJson(jsonDoc, jsonString); + + if (error) { + lcd.clear(); + lcd.setCursor(0, 0); + lcd.print("JSON Error"); + + Serial.print("JSON parsing failed: "); + Serial.println(error.c_str()); + return; + } + + // Parse financial data + financialData.bankBalance = jsonDoc["bank_balance"].as(); + financialData.totalIncome = jsonDoc["total_income"].as(); + financialData.totalExpenses = jsonDoc["total_expenses"].as(); + financialData.netAmount = jsonDoc["net_amount"].as(); + + // Parse transaction count + JsonArray transactions = jsonDoc["recent_transactions"].as(); + financialData.transactionCount = transactions.size(); + + // Display initial information + displayFinancialInfo(); +} + +void displayFinancialInfo() { + // Cycle through different display states + switch (currentState) { + case BANK_BALANCE: + displayBankBalance(); + break; + case INCOME_EXPENSES: + displayIncomeExpenses(); + break; + case TRANSACTION_COUNT: + displayTransactionCount(); + break; + } + + // Rotate to next state + currentState = static_cast((currentState + 1) % 3); +} + +void displayBankBalance() { + lcd.clear(); + lcd.setCursor(0, 0); + lcd.print("Bank Balance:"); + lcd.setCursor(0, 1); + lcd.print("$"); + lcd.print(financialData.bankBalance, 2); +} + +void displayIncomeExpenses() { + lcd.clear(); + lcd.setCursor(0, 0); + lcd.print("Income: $"); + lcd.print(financialData.totalIncome, 2); + lcd.setCursor(0, 1); + lcd.print("Expense: $"); + lcd.print(financialData.totalExpenses, 2); +} + +void displayTransactionCount() { + lcd.clear(); + lcd.setCursor(0, 0); + lcd.print("Transactions:"); + lcd.setCursor(0, 1); + lcd.print("Total: "); + lcd.print(financialData.transactionCount); +} + +void loop() { + // Check for incoming serial data + if (Serial.available()) { + String receivedData = Serial.readStringUntil('\n'); + + // Attempt to parse received JSON + parseFinancialData(receivedData); + } + + // Display rotation every 3 seconds + static unsigned long lastDisplayTime = 0; + unsigned long currentTime = millis(); + + if (currentTime - lastDisplayTime >= 3000) { + displayFinancialInfo(); + lastDisplayTime = currentTime; + } +} \ No newline at end of file diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index d69a268..82972cf 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -92,19 +92,6 @@ const DashboardContent = () => { const { accounts, transactions, loading } = usePlaid(); // Use the Plaid context const [reminders, setReminders] = useState([]); - // useEffect(() => { - // const fetchReminders = async () => { - // const { data, error } = await supabase - // .from('reminders') - // .select('*') - // .order('due_date', { ascending: true }); - - // if (data) setReminders(data); - // }; - - // fetchReminders(); - // }, []); - // Process Plaid data for charts const processChartData = () => { if (!transactions?.length) return []; @@ -308,7 +295,7 @@ const DashboardLayout = ({ children }) => { {/* Logo and Brand */}
- + Fin Track
{/* User Info and Logout */} diff --git a/frontend/src/pages/ExpenceTracker.jsx b/frontend/src/pages/ExpenceTracker.jsx index 6aefc6f..17c10e2 100644 --- a/frontend/src/pages/ExpenceTracker.jsx +++ b/frontend/src/pages/ExpenceTracker.jsx @@ -4,6 +4,7 @@ import { usePlaid } from '../PlaidProvider'; import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts'; import { DollarSign, ChevronLeft } from 'lucide-react'; + const ExpenseTracker = () => { const navigate = useNavigate(); const { diff --git a/frontend/src/pages/QuizPage.jsx b/frontend/src/pages/QuizPage.jsx index 84b1607..ff75263 100644 --- a/frontend/src/pages/QuizPage.jsx +++ b/frontend/src/pages/QuizPage.jsx @@ -1,221 +1,83 @@ // QuizPage.jsx import React, { useState, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; -import { ChevronLeft, DollarSign, BookOpen, Award, Check, X } from 'lucide-react'; +import { ChevronLeft, BookOpen, Award } from 'lucide-react'; import { supabase } from '../supabaseClient'; +import { initializeQuizDatabase } from '../quizInsert'; const QuizPage = () => { const navigate = useNavigate(); + const [quizzes, setQuizzes] = useState([]); const [currentQuiz, setCurrentQuiz] = useState(null); const [currentQuestionIndex, setCurrentQuestionIndex] = useState(0); const [selectedAnswer, setSelectedAnswer] = useState(null); const [score, setScore] = useState(0); const [showResult, setShowResult] = useState(false); const [quizStarted, setQuizStarted] = useState(false); + const [loading, setLoading] = useState(true); + const [initializationStatus, setInitializationStatus] = useState(''); - // Sample quiz data - Replace with Supabase data - const quizzes = [ - { - id: 1, - title: "Banking & Finance Essentials", - description: "Understand the key concepts of personal finance and banking.", - questions: [ - { - question: "What is compound interest?", - options: [ - "Interest earned on the initial deposit only", - "Interest earned on both the initial deposit and previously earned interest", - "A fee charged by banks for loans", - "A one-time interest payment on savings" - ], - correctAnswer: 1, - explanation: "Compound interest is calculated on both the principal amount and accumulated interest over time." - }, - { - question: "Which of the following negatively impacts your credit score?", - options: [ - "Paying credit card bills on time", - "Having multiple credit cards", - "Missing loan payments", - "Checking your credit score" - ], - correctAnswer: 2, - explanation: "Missing loan or credit payments lowers your credit score as it reflects poor financial responsibility." - }, - { - question: "What is the primary purpose of a credit score?", - options: [ - "To determine your annual tax rate", - "To assess your trustworthiness as a borrower", - "To calculate your net worth", - "To set your bank account interest rate" - ], - correctAnswer: 1, - explanation: "A credit score helps lenders assess your creditworthiness before approving loans or credit cards." - }, - { - question: "Which financial tool helps you save money on unnecessary expenses?", - options: [ - "A budgeting app", - "A credit card", - "A payday loan", - "A lottery ticket" - ], - correctAnswer: 0, - explanation: "Budgeting apps help track expenses and reduce unnecessary spending." - }, - { - question: "What is an overdraft fee?", - options: [ - "A fee for depositing money in your bank", - "A penalty for withdrawing more money than you have in your account", - "A charge for opening a new bank account", - "A bonus for using your debit card frequently" - ], - correctAnswer: 1, - explanation: "An overdraft fee is charged when you spend more than what is available in your account." - }, - { - question: "What does APR stand for?", - options: [ - "Annual Percentage Rate", - "Average Payment Ratio", - "Automated Payment Record", - "Account Processing Report" - ], - correctAnswer: 0, - explanation: "APR (Annual Percentage Rate) represents the cost of borrowing, including interest and fees." - } - ] - }, - { - id: 2, - title: "Insurance & Healthcare Knowledge", - description: "Learn about insurance and healthcare essentials.", - questions: [ - { - question: "Which type of insurance helps cover the cost of medical expenses?", - options: [ - "Home insurance", - "Health insurance", - "Auto insurance", - "Travel insurance" - ], - correctAnswer: 1, - explanation: "Health insurance covers medical expenses, including doctor visits, hospital stays, and medications." - }, - { - question: "What is a deductible in an insurance policy?", - options: [ - "A discount offered for early payments", - "The amount you pay out-of-pocket before insurance kicks in", - "The total coverage amount of an insurance policy", - "The fine print in an insurance contract" - ], - correctAnswer: 1, - explanation: "A deductible is the amount you must pay before your insurance starts covering expenses." - }, - { - question: "What does ‘premium’ mean in an insurance policy?", - options: [ - "The total amount covered by the policy", - "The amount you pay for insurance coverage, usually monthly or yearly", - "A special discount given to policyholders", - "A type of investment plan" - ], - correctAnswer: 1, - explanation: "A premium is the regular payment you make to keep your insurance policy active." - }, - { - question: "Which of the following is NOT typically covered by standard health insurance?", - options: [ - "Doctor visits", - "Prescription medication", - "Cosmetic surgery", - "Emergency room visits" - ], - correctAnswer: 2, - explanation: "Cosmetic surgery is usually not covered unless it is medically necessary." - }, - { - question: "What does life insurance primarily provide?", - options: [ - "Coverage for medical expenses", - "Financial support for dependents after the policyholder’s death", - "Investment opportunities", - "Legal protection against lawsuits" - ], - correctAnswer: 1, - explanation: "Life insurance provides financial security to the policyholder’s beneficiaries in the event of death." - } - ] - }, - { - id: 3, - title: "Food Safety & Consumer Awareness", - description: "Stay informed about food safety and smart consumer choices.", - questions: [ - { - question: "Which of the following should you NOT do to prevent food poisoning?", - options: [ - "Wash your hands before preparing food", - "Leave perishable food at room temperature for hours", - "Store raw meat separately from cooked food", - "Cook meat to the proper internal temperature" - ], - correctAnswer: 1, - explanation: "Leaving perishable food at room temperature can lead to bacterial growth and foodborne illnesses." - }, - { - question: "What does the expiration date on food packaging indicate?", - options: [ - "The last day the food should be sold", - "The date after which the food is unsafe to eat", - "The best quality before a certain date, but it may still be safe after", - "A suggestion for when to buy new food" - ], - correctAnswer: 2, - explanation: "Expiration dates often indicate peak freshness, but many foods are still safe to consume after this date." - }, - { - question: "Which of the following is the safest way to thaw frozen food?", - options: [ - "Leaving it on the kitchen counter", - "Placing it in the refrigerator overnight", - "Soaking it in hot water", - "Leaving it under direct sunlight" - ], - correctAnswer: 1, - explanation: "Refrigerator thawing is the safest method as it prevents bacterial growth." - }, - { - question: "What temperature should poultry be cooked to in order to ensure food safety?", - options: [ - "120°F (49°C)", - "145°F (63°C)", - "165°F (74°C)", - "200°F (93°C)" - ], - correctAnswer: 2, - explanation: "Poultry should be cooked to at least 165°F (74°C) to kill harmful bacteria." - }, - { - question: "Which of these common kitchen habits increases the risk of cross-contamination?", - options: [ - "Using separate cutting boards for meat and vegetables", - "Washing hands after handling raw food", - "Using the same knife for raw chicken and fresh vegetables without washing it", - "Refrigerating leftovers within two hours" - ], - correctAnswer: 2, - explanation: "Using the same knife for raw meat and vegetables without washing it can transfer harmful bacteria." - } - ] + useEffect(() => { + initializeAndFetchQuizzes(); + }, []); + + const initializeAndFetchQuizzes = async () => { + try { + setLoading(true); + setInitializationStatus('Initializing quiz database...'); + + const initResult = await initializeQuizDatabase(); + if (!initResult.success) { + throw new Error('Failed to initialize quiz database'); + } + + setInitializationStatus('Fetching quizzes...'); + await fetchQuizzes(); + + setInitializationStatus(''); + setLoading(false); + } catch (error) { + console.error('Error initializing quizzes:', error); + setInitializationStatus('Error loading quizzes. Please try again.'); + setLoading(false); } - ]; + }; - const handleStartQuiz = () => { - setCurrentQuiz(quizzes[0]); + const fetchQuizzes = async () => { + try { + const { data: quizData, error } = await supabase + .from('quiz_questions') + .select('*') + .order('id'); + + if (error) throw error; + + const groupedQuizzes = quizData.reduce((acc, question) => { + if (!acc[question.category]) { + acc[question.category] = { + title: question.category, + description: `Test your knowledge about ${question.category}`, + questions: [] + }; + } + acc[question.category].questions.push({ + question: question.question, + options: question.options, + correctAnswer: question.correct_answer, + explanation: question.explanation + }); + return acc; + }, {}); + + setQuizzes(Object.values(groupedQuizzes)); + } catch (error) { + console.error('Error fetching quizzes:', error); + throw error; + } + }; + + const handleStartQuiz = (quiz) => { + setCurrentQuiz(quiz); setQuizStarted(true); setCurrentQuestionIndex(0); setScore(0); @@ -226,21 +88,46 @@ const QuizPage = () => { setSelectedAnswer(answerIndex); }; - const handleNextQuestion = () => { - // Calculate score + const handleNextQuestion = async () => { if (selectedAnswer === currentQuiz.questions[currentQuestionIndex].correctAnswer) { setScore(score + 1); } - // Move to next question or show results if (currentQuestionIndex + 1 < currentQuiz.questions.length) { setCurrentQuestionIndex(currentQuestionIndex + 1); setSelectedAnswer(null); } else { + try { + const { data: { user } } = await supabase.auth.getUser(); + if (user) { + await supabase.from('quiz_results').insert({ + user_id: user.id, + quiz_category: currentQuiz.title, + score: score + (selectedAnswer === currentQuiz.questions[currentQuestionIndex].correctAnswer ? 1 : 0), + total_questions: currentQuiz.questions.length, + completed_at: new Date().toISOString() + }); + } + } catch (error) { + console.error('Error saving quiz result:', error); + } setShowResult(true); } }; + if (loading) { + return ( +
+
+
+ {initializationStatus && ( +

{initializationStatus}

+ )} +
+
+ ); + } + return (
{/* Navigation Bar */} @@ -268,27 +155,28 @@ const QuizPage = () => { // Quiz Selection Screen
-

Financial Independence Quiz

-

Test your knowledge and learn about personal finance

+

Knowledge Quiz

+

Select a quiz to test your knowledge

-
-
- + {quizzes.map((quiz, index) => ( +
+
+ +
+
+

{quiz.title}

+

{quiz.description}

+ +
-
-

Budgeting Basics

-

Learn the fundamentals of personal budgeting and financial planning

-
-
- - + ))}
) : showResult ? ( diff --git a/frontend/src/quizInsert.js b/frontend/src/quizInsert.js new file mode 100644 index 0000000..693c212 --- /dev/null +++ b/frontend/src/quizInsert.js @@ -0,0 +1,87 @@ +// quizInsert.js +import { supabase } from './supabaseClient'; + +const quizQuestions = [ + { + category: "Banking", + question: "What is compound interest?", + options: [ + "Interest earned on the initial deposit only", + "Interest earned on both the initial deposit and previously earned interest", + "A fee charged by banks for loans", + "A one-time interest payment on savings" + ], + correct_answer: 1, + explanation: "Compound interest is calculated on both the principal amount and accumulated interest over time." + }, + { + category: "Banking", + question: "Which of the following negatively impacts your credit score?", + options: [ + "Paying credit card bills on time", + "Having multiple credit cards", + "Missing loan payments", + "Checking your credit score" + ], + correct_answer: 2, + explanation: "Missing loan or credit payments lowers your credit score as it reflects poor financial responsibility." + }, + { + category: "Investment", + question: "What is diversification in investing?", + options: [ + "Putting all money in one stock", + "Spreading investments across different assets", + "Only investing in bonds", + "Only investing in real estate" + ], + correct_answer: 1, + explanation: "Diversification means spreading investments across different assets to reduce risk." + }, + { + category: "Budgeting", + question: "What is the 50/30/20 budgeting rule?", + options: [ + "50% needs, 30% wants, 20% savings", + "50% savings, 30% needs, 20% wants", + "50% wants, 30% savings, 20% needs", + "50% needs, 30% savings, 20% wants" + ], + correct_answer: 0, + explanation: "The 50/30/20 rule suggests spending 50% of income on needs, 30% on wants, and 20% on savings." + } +]; + +const initializeQuizDatabase = async () => { + try { + // Check if questions already exist + const { data: existing, error: checkError } = await supabase + .from('quiz_questions') + .select('id') + .limit(1); + + if (checkError) throw checkError; + + // If no questions exist, insert them + if (!existing || existing.length === 0) { + const { data, error } = await supabase + .from('quiz_questions') + .insert(quizQuestions) + .select(); + + if (error) throw error; + + console.log('Quiz questions initialized successfully'); + return { success: true, data }; + } + + console.log('Quiz questions already exist'); + return { success: true, data: existing }; + + } catch (error) { + console.error('Error initializing quiz database:', error); + return { success: false, error }; + } +}; + +export { initializeQuizDatabase }; \ No newline at end of file diff --git a/test.py b/test.py new file mode 100644 index 0000000..eacc060 --- /dev/null +++ b/test.py @@ -0,0 +1,103 @@ +import serial +import json +import requests +import time +from datetime import datetime, timedelta + +class PlaidDataSender: + def __init__(self, access_token, serial_port='/dev/tty.usbserial-0001', baud_rate=115200): + self.access_token = access_token + self.serial_port = serial_port + self.baud_rate = baud_rate + self.api_base_url = 'http://localhost:3000' + + def fetch_financial_data(self): + """Fetch real financial data from Plaid API""" + headers = {'plaid-access-token': self.access_token} + + # Fetch accounts + accounts_response = requests.get( + f'{self.api_base_url}/api/accounts', + headers=headers + ) + accounts_data = accounts_response.json() + + # Fetch transactions + transactions_response = requests.get( + f'{self.api_base_url}/api/transactions', + headers=headers + ) + transactions_data = transactions_response.json() + + # Calculate totals + total_balance = sum(account['balances']['current'] for account in accounts_data['accounts']) + transactions = transactions_data['transactions'] + total_income = sum(trans['amount'] for trans in transactions if trans['amount'] > 0) + total_expenses = sum(trans['amount'] for trans in transactions if trans['amount'] < 0) + + # Format data for ESP32 + financial_summary = { + "bank_balance": round(total_balance, 2), + "total_income": round(total_income, 2), + "total_expenses": round(abs(total_expenses), 2), + "net_amount": round(total_income + total_expenses, 2), + "recent_transactions": [ + { + "name": trans['merchant_name'] or trans['name'], + "amount": trans['amount'], + "type": "income" if trans['amount'] > 0 else "expense", + "date": trans['date'] + } + for trans in sorted(transactions, key=lambda x: x['date'], reverse=True)[:5] + ] + } + + return financial_summary + + def run(self): + """Run the data sender""" + try: + # Setup serial connection + with serial.Serial(self.serial_port, self.baud_rate, timeout=1) as ser: + print(f"Connected to ESP32 on {self.serial_port}") + time.sleep(2) # Wait for connection to establish + + while True: + try: + # Fetch latest financial data + financial_data = self.fetch_financial_data() + + # Convert to JSON and send + json_data = json.dumps(financial_data) + ser.write((json_data + '\n').encode('utf-8')) + + print(f"Sent financial data: {json_data}") + time.sleep(10) # Wait 5 seconds before next update + + except requests.RequestException as e: + print(f"API Error: {e}") + time.sleep(5) + except Exception as e: + print(f"Error sending data: {e}") + time.sleep(1) + + except serial.SerialException as e: + print(f"Serial connection error: {e}") + except KeyboardInterrupt: + print("\nStopped by user") + +def main(): + # Replace with your Plaid access token + access_token = "access-sandbox-1331c29e-2e5a-492b-ac03-623c4a7aa146" + + # Create and run the sender + sender = PlaidDataSender( + access_token=access_token, + serial_port='/dev/tty.usbserial-0001', # Hardcoded serial port + baud_rate=115200 + ) + + sender.run() + +if __name__ == "__main__": + main() \ No newline at end of file