update final design
This commit is contained in:
138
SerialToSerialBT.ino
Normal file
138
SerialToSerialBT.ino
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
#include <LiquidCrystal.h>
|
||||||
|
#include <ArduinoJson.h>
|
||||||
|
|
||||||
|
// 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<float>();
|
||||||
|
financialData.totalIncome = jsonDoc["total_income"].as<float>();
|
||||||
|
financialData.totalExpenses = jsonDoc["total_expenses"].as<float>();
|
||||||
|
financialData.netAmount = jsonDoc["net_amount"].as<float>();
|
||||||
|
|
||||||
|
// Parse transaction count
|
||||||
|
JsonArray transactions = jsonDoc["recent_transactions"].as<JsonArray>();
|
||||||
|
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<DisplayState>((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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -92,19 +92,6 @@ const DashboardContent = () => {
|
|||||||
const { accounts, transactions, loading } = usePlaid(); // Use the Plaid context
|
const { accounts, transactions, loading } = usePlaid(); // Use the Plaid context
|
||||||
const [reminders, setReminders] = useState([]);
|
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
|
// Process Plaid data for charts
|
||||||
const processChartData = () => {
|
const processChartData = () => {
|
||||||
if (!transactions?.length) return [];
|
if (!transactions?.length) return [];
|
||||||
@@ -308,7 +295,7 @@ const DashboardLayout = ({ children }) => {
|
|||||||
{/* Logo and Brand */}
|
{/* Logo and Brand */}
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<DollarSign className="h-8 w-8 text-green-600" />
|
<DollarSign className="h-8 w-8 text-green-600" />
|
||||||
<span className="ml-2 text-xl font-bold text-gray-900"></span>
|
<span className="ml-2 text-xl font-bold text-gray-900">Fin Track</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* User Info and Logout */}
|
{/* User Info and Logout */}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { usePlaid } from '../PlaidProvider';
|
|||||||
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts';
|
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts';
|
||||||
import { DollarSign, ChevronLeft } from 'lucide-react';
|
import { DollarSign, ChevronLeft } from 'lucide-react';
|
||||||
|
|
||||||
|
|
||||||
const ExpenseTracker = () => {
|
const ExpenseTracker = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const {
|
const {
|
||||||
|
|||||||
@@ -1,221 +1,83 @@
|
|||||||
// QuizPage.jsx
|
// QuizPage.jsx
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
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 { supabase } from '../supabaseClient';
|
||||||
|
import { initializeQuizDatabase } from '../quizInsert';
|
||||||
|
|
||||||
const QuizPage = () => {
|
const QuizPage = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const [quizzes, setQuizzes] = useState([]);
|
||||||
const [currentQuiz, setCurrentQuiz] = useState(null);
|
const [currentQuiz, setCurrentQuiz] = useState(null);
|
||||||
const [currentQuestionIndex, setCurrentQuestionIndex] = useState(0);
|
const [currentQuestionIndex, setCurrentQuestionIndex] = useState(0);
|
||||||
const [selectedAnswer, setSelectedAnswer] = useState(null);
|
const [selectedAnswer, setSelectedAnswer] = useState(null);
|
||||||
const [score, setScore] = useState(0);
|
const [score, setScore] = useState(0);
|
||||||
const [showResult, setShowResult] = useState(false);
|
const [showResult, setShowResult] = useState(false);
|
||||||
const [quizStarted, setQuizStarted] = useState(false);
|
const [quizStarted, setQuizStarted] = useState(false);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [initializationStatus, setInitializationStatus] = useState('');
|
||||||
|
|
||||||
// Sample quiz data - Replace with Supabase data
|
useEffect(() => {
|
||||||
const quizzes = [
|
initializeAndFetchQuizzes();
|
||||||
{
|
}, []);
|
||||||
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."
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
const handleStartQuiz = () => {
|
const initializeAndFetchQuizzes = async () => {
|
||||||
setCurrentQuiz(quizzes[0]);
|
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 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);
|
setQuizStarted(true);
|
||||||
setCurrentQuestionIndex(0);
|
setCurrentQuestionIndex(0);
|
||||||
setScore(0);
|
setScore(0);
|
||||||
@@ -226,21 +88,46 @@ const QuizPage = () => {
|
|||||||
setSelectedAnswer(answerIndex);
|
setSelectedAnswer(answerIndex);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleNextQuestion = () => {
|
const handleNextQuestion = async () => {
|
||||||
// Calculate score
|
|
||||||
if (selectedAnswer === currentQuiz.questions[currentQuestionIndex].correctAnswer) {
|
if (selectedAnswer === currentQuiz.questions[currentQuestionIndex].correctAnswer) {
|
||||||
setScore(score + 1);
|
setScore(score + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Move to next question or show results
|
|
||||||
if (currentQuestionIndex + 1 < currentQuiz.questions.length) {
|
if (currentQuestionIndex + 1 < currentQuiz.questions.length) {
|
||||||
setCurrentQuestionIndex(currentQuestionIndex + 1);
|
setCurrentQuestionIndex(currentQuestionIndex + 1);
|
||||||
setSelectedAnswer(null);
|
setSelectedAnswer(null);
|
||||||
} else {
|
} 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);
|
setShowResult(true);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen flex items-center justify-center">
|
||||||
|
<div className="flex flex-col items-center gap-4">
|
||||||
|
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-gray-900"></div>
|
||||||
|
{initializationStatus && (
|
||||||
|
<p className="text-gray-600">{initializationStatus}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gray-100">
|
<div className="min-h-screen bg-gray-100">
|
||||||
{/* Navigation Bar */}
|
{/* Navigation Bar */}
|
||||||
@@ -268,29 +155,30 @@ const QuizPage = () => {
|
|||||||
// Quiz Selection Screen
|
// Quiz Selection Screen
|
||||||
<div className="bg-white rounded-lg shadow-lg p-6">
|
<div className="bg-white rounded-lg shadow-lg p-6">
|
||||||
<div className="text-center mb-8">
|
<div className="text-center mb-8">
|
||||||
<h1 className="text-2xl font-bold mb-2">Financial Independence Quiz</h1>
|
<h1 className="text-2xl font-bold mb-2">Knowledge Quiz</h1>
|
||||||
<p className="text-gray-600">Test your knowledge and learn about personal finance</p>
|
<p className="text-gray-600">Select a quiz to test your knowledge</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div className="flex items-start gap-4 p-4 bg-purple-50 rounded-lg">
|
{quizzes.map((quiz, index) => (
|
||||||
|
<div key={index} className="flex items-start gap-4 p-4 bg-purple-50 rounded-lg">
|
||||||
<div className="bg-purple-100 p-3 rounded-lg">
|
<div className="bg-purple-100 p-3 rounded-lg">
|
||||||
<Award className="h-6 w-6 text-purple-600" />
|
<Award className="h-6 w-6 text-purple-600" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div className="flex-1">
|
||||||
<h3 className="font-semibold">Budgeting Basics</h3>
|
<h3 className="font-semibold">{quiz.title}</h3>
|
||||||
<p className="text-sm text-gray-600 mt-1">Learn the fundamentals of personal budgeting and financial planning</p>
|
<p className="text-sm text-gray-600 mt-1">{quiz.description}</p>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={handleStartQuiz}
|
onClick={() => handleStartQuiz(quiz)}
|
||||||
className="w-full bg-purple-600 text-white rounded-lg py-3 font-medium hover:bg-purple-700 transition-colors"
|
className="mt-4 bg-purple-600 text-white rounded-lg px-4 py-2 text-sm font-medium hover:bg-purple-700 transition-colors"
|
||||||
>
|
>
|
||||||
Start Quiz
|
Start Quiz
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
) : showResult ? (
|
) : showResult ? (
|
||||||
// Results Screen
|
// Results Screen
|
||||||
<div className="bg-white rounded-lg shadow-lg p-6">
|
<div className="bg-white rounded-lg shadow-lg p-6">
|
||||||
|
|||||||
87
frontend/src/quizInsert.js
Normal file
87
frontend/src/quizInsert.js
Normal file
@@ -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 };
|
||||||
103
test.py
Normal file
103
test.py
Normal file
@@ -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()
|
||||||
Reference in New Issue
Block a user