import React, { useState, useEffect } from 'react'; import { Users, Plus, TrendingUp, DollarSign, Settings, LogOut, Home, Clock, Tag, Check, X, Menu, ChevronRight, Receipt, Search } from 'lucide-react'; // API Configuration - UPDATE THIS with your actual domain const API_URL = 'https://https://justintrend.in/.com/api.php'; // // API helper functions const api = { async call(action, data = null, method = 'GET') { try { const options = { method, headers: { 'Content-Type': 'application/json' } }; if (data && method !== 'GET') { options.body = JSON.stringify(data); } const url = method === 'GET' && data ? `${API_URL}?action=${action}&${new URLSearchParams(data).toString()}` : `${API_URL}?action=${action}`; const response = await fetch(url, options); const result = await response.json(); if (!result.success) { throw new Error(result.error || 'API request failed'); } return result; } catch (error) { console.error('API Error:', error); throw error; } }, // Auth register: (name, email, password) => api.call('register', { name, email, password }, 'POST'), login: (email, password) => api.call('login', { email, password }, 'POST'), // Household getHousehold: (userId) => api.call('get_household', { user_id: userId }), addMember: (userId, name) => api.call('add_member', { user_id: userId, name }, 'POST'), removeMember: (memberId) => api.call('remove_member', { member_id: memberId }, 'POST'), // Expenses getExpenses: (userId, settled = null) => { const params = { user_id: userId }; if (settled !== null) params.settled = settled ? '1' : '0'; return api.call('get_expenses', params); }, addExpense: (userId, expense) => api.call('add_expense', { user_id: userId, ...expense }, 'POST'), settleExpense: (expenseId) => api.call('settle_expense', { expense_id: expenseId }, 'POST'), settleAll: (userId) => api.call('settle_all', { user_id: userId }, 'POST'), // Merchants getMerchants: (category = null) => { const params = {}; if (category) params.category = category; return api.call('get_merchants', params); }, searchMerchants: (query) => api.call('search_merchants', { query }), }; const CATEGORIES = [ { name: 'Groceries', icon: '๐Ÿ›’', color: '#10b981' }, { name: 'Utilities', icon: 'โšก', color: '#f59e0b' }, { name: 'Rent', icon: '๐Ÿ ', color: '#8b5cf6' }, { name: 'Entertainment', icon: '๐ŸŽฌ', color: '#ec4899' }, { name: 'Transport', icon: '๐Ÿš—', color: '#3b82f6' }, { name: 'Health', icon: '๐Ÿ’Š', color: '#ef4444' }, { name: 'Shopping', icon: '๐Ÿ›๏ธ', color: '#14b8a6' }, { name: 'Food & Dining', icon: '๐Ÿฝ๏ธ', color: '#f97316' }, { name: 'Other', icon: '๐Ÿ“Œ', color: '#6366f1' }, ]; export default function HomeExpenseApp() { const [currentUser, setCurrentUser] = useState(null); const [view, setView] = useState('login'); const [showMenu, setShowMenu] = useState(false); const [loading, setLoading] = useState(false); // Auth states const [authForm, setAuthForm] = useState({ email: '', password: '', name: '' }); const [authError, setAuthError] = useState(''); // App states const [household, setHousehold] = useState([]); const [expenses, setExpenses] = useState([]); const [merchants, setMerchants] = useState([]); const [newMember, setNewMember] = useState(''); const [showAddExpense, setShowAddExpense] = useState(false); const [showAddMember, setShowAddMember] = useState(false); const [showSettleUp, setShowSettleUp] = useState(false); // Expense form const [expenseForm, setExpenseForm] = useState({ description: '', merchant: '', amount: '', category: 'Groceries', paid_by: '', split_among: [], }); // Merchant search const [merchantSearch, setMerchantSearch] = useState(''); const [merchantSuggestions, setMerchantSuggestions] = useState([]); const [showMerchantDropdown, setShowMerchantDropdown] = useState(false); // Load user from session storage useEffect(() => { const savedUser = sessionStorage.getItem('currentUser'); if (savedUser) { const user = JSON.parse(savedUser); setCurrentUser(user); loadUserData(user.id); } }, []); const loadUserData = async (userId) => { try { setLoading(true); const [householdRes, expensesRes, merchantsRes] = await Promise.all([ api.getHousehold(userId), api.getExpenses(userId, false), api.getMerchants() ]); setHousehold(householdRes.members || []); setExpenses(expensesRes.expenses || []); setMerchants(merchantsRes.merchants || []); setView('dashboard'); } catch (error) { console.error('Error loading data:', error); alert('Error loading data: ' + error.message); } finally { setLoading(false); } }; // Search merchants const handleMerchantSearch = async (query) => { setMerchantSearch(query); setExpenseForm({ ...expenseForm, merchant: query }); if (query.length < 2) { setMerchantSuggestions([]); setShowMerchantDropdown(false); return; } try { const result = await api.searchMerchants(query); setMerchantSuggestions(result.merchants || []); setShowMerchantDropdown(true); } catch (error) { console.error('Error searching merchants:', error); } }; const selectMerchant = (merchant) => { setMerchantSearch(merchant.name); setExpenseForm({ ...expenseForm, merchant: merchant.name }); setShowMerchantDropdown(false); }; // Auth functions const handleRegister = async () => { if (!authForm.name || !authForm.email || !authForm.password) { setAuthError('Please fill all fields'); return; } try { setLoading(true); const result = await api.register(authForm.name, authForm.email, authForm.password); sessionStorage.setItem('currentUser', JSON.stringify(result.user)); setCurrentUser(result.user); setAuthError(''); await loadUserData(result.user.id); } catch (error) { setAuthError(error.message); } finally { setLoading(false); } }; const handleLogin = async () => { if (!authForm.email || !authForm.password) { setAuthError('Please fill all fields'); return; } try { setLoading(true); const result = await api.login(authForm.email, authForm.password); sessionStorage.setItem('currentUser', JSON.stringify(result.user)); setCurrentUser(result.user); setAuthError(''); await loadUserData(result.user.id); } catch (error) { setAuthError(error.message); } finally { setLoading(false); } }; const handleLogout = () => { sessionStorage.removeItem('currentUser'); setCurrentUser(null); setView('login'); setAuthForm({ email: '', password: '', name: '' }); setHousehold([]); setExpenses([]); }; // Household functions const addHouseholdMember = async () => { if (!newMember.trim()) return; try { setLoading(true); const result = await api.addMember(currentUser.id, newMember.trim()); setHousehold([...household, result.member]); setNewMember(''); setShowAddMember(false); } catch (error) { alert('Error adding member: ' + error.message); } finally { setLoading(false); } }; const removeMember = async (memberId) => { if (!confirm('Are you sure you want to remove this member?')) return; try { setLoading(true); await api.removeMember(memberId); setHousehold(household.filter(m => m.id !== memberId)); } catch (error) { alert('Error removing member: ' + error.message); } finally { setLoading(false); } }; // Expense functions const addExpense = async () => { if (!expenseForm.description || !expenseForm.amount || !expenseForm.paid_by || expenseForm.split_among.length === 0) { alert('Please fill all required fields'); return; } try { setLoading(true); const expenseData = { description: expenseForm.description, merchant: expenseForm.merchant || '', amount: parseFloat(expenseForm.amount), category: expenseForm.category, paid_by: parseInt(expenseForm.paid_by), split_among: expenseForm.split_among.map(id => parseInt(id)), }; await api.addExpense(currentUser.id, expenseData); // Reload expenses const result = await api.getExpenses(currentUser.id, false); setExpenses(result.expenses || []); setExpenseForm({ description: '', merchant: '', amount: '', category: 'Groceries', paid_by: '', split_among: [], }); setMerchantSearch(''); setShowAddExpense(false); } catch (error) { alert('Error adding expense: ' + error.message); } finally { setLoading(false); } }; const toggleSplitMember = (memberId) => { const current = expenseForm.split_among; if (current.includes(memberId)) { setExpenseForm({ ...expenseForm, split_among: current.filter(id => id !== memberId) }); } else { setExpenseForm({ ...expenseForm, split_among: [...current, memberId] }); } }; const settleAllExpenses = async () => { try { setLoading(true); await api.settleAll(currentUser.id); // Reload expenses const result = await api.getExpenses(currentUser.id, false); setExpenses(result.expenses || []); setShowSettleUp(false); } catch (error) { alert('Error settling expenses: ' + error.message); } finally { setLoading(false); } }; // Calculate balances const calculateBalances = () => { const balances = {}; household.forEach(member => { balances[member.id] = { name: member.name, balance: 0 }; }); expenses.filter(e => !e.settled).forEach(expense => { const shareAmount = expense.amount / expense.split_among.length; expense.split_among.forEach(memberId => { if (memberId !== expense.paid_by) { balances[memberId].balance -= shareAmount; balances[expense.paid_by].balance += shareAmount; } }); }); return Object.values(balances).filter(b => Math.abs(b.balance) > 0.01); }; const getTotalExpenses = () => expenses.filter(e => !e.settled).reduce((sum, e) => sum + e.amount, 0); const getYourShare = () => { const userMember = household.find(m => m.name === currentUser?.name); if (!userMember) return 0; return expenses.filter(e => !e.settled && e.split_among.includes(userMember.id)) .reduce((sum, e) => sum + (e.amount / e.split_among.length), 0); }; const balances = calculateBalances(); if (loading && !currentUser) { return (
Loading...
); } if (!currentUser) { return (

Home Expenses

Track and split household costs

{authError && (
{authError}
)}
{view === 'register' && ( setAuthForm({ ...authForm, name: e.target.value })} style={{ width: '100%', padding: '14px', border: '2px solid #e2e8f0', borderRadius: '12px', fontSize: '16px', marginBottom: '12px', boxSizing: 'border-box', outline: 'none', }} onFocus={(e) => e.target.style.borderColor = '#667eea'} onBlur={(e) => e.target.style.borderColor = '#e2e8f0'} /> )} setAuthForm({ ...authForm, email: e.target.value })} style={{ width: '100%', padding: '14px', border: '2px solid #e2e8f0', borderRadius: '12px', fontSize: '16px', marginBottom: '12px', boxSizing: 'border-box', outline: 'none', }} onFocus={(e) => e.target.style.borderColor = '#667eea'} onBlur={(e) => e.target.style.borderColor = '#e2e8f0'} /> setAuthForm({ ...authForm, password: e.target.value })} style={{ width: '100%', padding: '14px', border: '2px solid #e2e8f0', borderRadius: '12px', fontSize: '16px', boxSizing: 'border-box', outline: 'none', }} onFocus={(e) => e.target.style.borderColor = '#667eea'} onBlur={(e) => e.target.style.borderColor = '#e2e8f0'} />
); } return (
{/* Header */}

Home Expenses

Welcome, {currentUser.name}

{/* Menu Dropdown */} {showMenu && (
)}
{view === 'dashboard' && ( <> {/* Stats Cards */}
TOTAL

ยฃ{getTotalExpenses().toFixed(2)}

YOUR SHARE

ยฃ{getYourShare().toFixed(2)}

EXPENSES

{expenses.filter(e => !e.settled).length}

{/* Balances */} {balances.length > 0 && (

Balances

{balances.map((balance, idx) => (
{balance.name} 0 ? '#10b981' : '#e53e3e', }}> {balance.balance > 0 ? '+' : ''}ยฃ{balance.balance.toFixed(2)}
))}
)} {/* Recent Expenses */}

Recent Expenses

{expenses.filter(e => !e.settled).slice(0, 5).length === 0 ? (

No expenses yet. Add one to get started!

) : ( expenses.filter(e => !e.settled).slice(0, 5).map(expense => { const category = CATEGORIES.find(c => c.name === expense.category); const paidByMember = household.find(m => m.id === expense.paid_by); return (
{category?.icon}
{expense.description}
{expense.merchant && `${expense.merchant} ยท `}Paid by {paidByMember?.name}
ยฃ{expense.amount.toFixed(2)}
Split {expense.split_among.length} ways
); }) )} {expenses.filter(e => !e.settled).length > 5 && ( )}
)} {view === 'household' && (

Household Members

{household.map(member => (
{member.name.charAt(0).toUpperCase()}
{member.name}
{household.length > 1 && ( )}
))}
)} {view === 'history' && (

Expense History

{expenses.map(expense => { const category = CATEGORIES.find(c => c.name === expense.category); const paidByMember = household.find(m => m.id === expense.paid_by); const date = new Date(expense.date); return (
{category?.icon}
{expense.description}
{expense.merchant && (
{expense.merchant}
)}
{date.toLocaleDateString('en-GB')} ยท Paid by {paidByMember?.name}
Split among: {expense.split_among.map(id => household.find(m => m.id === id)?.name ).join(', ')}
ยฃ{expense.amount.toFixed(2)}
{expense.settled && ( โœ“ Settled )}
); })}
)}
{/* Add Expense Modal */} {showAddExpense && (
setShowAddExpense(false)}>
e.stopPropagation()}>

Add Expense

setExpenseForm({ ...expenseForm, description: e.target.value })} placeholder="e.g., Weekly shopping" style={{ width: '100%', padding: '12px', border: '2px solid #e2e8f0', borderRadius: '10px', fontSize: '16px', boxSizing: 'border-box', }} />
handleMerchantSearch(e.target.value)} onFocus={() => merchantSearch.length >= 2 && setShowMerchantDropdown(true)} placeholder="e.g., Tesco, Sainsbury's" style={{ width: '100%', padding: '12px 12px 12px 40px', border: '2px solid #e2e8f0', borderRadius: '10px', fontSize: '16px', boxSizing: 'border-box', }} />
{showMerchantDropdown && merchantSuggestions.length > 0 && (
{merchantSuggestions.map(merchant => ( ))}
)}
setExpenseForm({ ...expenseForm, amount: e.target.value })} placeholder="0.00" style={{ width: '100%', padding: '12px', border: '2px solid #e2e8f0', borderRadius: '10px', fontSize: '16px', boxSizing: 'border-box', }} />
{CATEGORIES.map(cat => ( ))}
{household.map(member => ( ))}
)} {/* Add Member Modal */} {showAddMember && (
setShowAddMember(false)}>
e.stopPropagation()}>

Add Household Member

setNewMember(e.target.value)} placeholder="Member name" style={{ width: '100%', padding: '12px', border: '2px solid #e2e8f0', borderRadius: '10px', fontSize: '16px', marginBottom: '16px', boxSizing: 'border-box', }} />
)} {/* Settle Up Modal */} {showSettleUp && (
setShowSettleUp(false)}>
e.stopPropagation()}>

Settle All Expenses

This will mark all current expenses as settled. Are you sure?

)} {/* Floating Action Button */} {view === 'dashboard' && household.length > 0 && ( )}
); }