diff --git a/app/src/App.tsx b/app/src/App.tsx index f0dc5942d..3315d95f7 100644 --- a/app/src/App.tsx +++ b/app/src/App.tsx @@ -16,6 +16,8 @@ import NotFound from "./pages/NotFound"; import { Landing } from "./pages/Landing"; import ProtectedRoute from "./components/auth/ProtectedRoute"; import Account from "./pages/Account"; +import { Savings } from "./pages/Savings"; + const queryClient = new QueryClient({ defaultOptions: { @@ -91,6 +93,15 @@ const App = () => ( } /> + + + + } + /> + } /> } /> diff --git a/app/src/components/layout/Navbar.tsx b/app/src/components/layout/Navbar.tsx index c7593b701..ad9b29594 100644 --- a/app/src/components/layout/Navbar.tsx +++ b/app/src/components/layout/Navbar.tsx @@ -1,7 +1,8 @@ import { useEffect, useState } from 'react'; import { Link, useLocation, useNavigate } from 'react-router-dom'; import { Button } from '@/components/ui/button'; -import { Menu, X, TrendingUp, ShieldCheck } from 'lucide-react'; +import { Menu, X, TrendingUp, ShieldCheck, Target } from 'lucide-react'; + import { getToken, getRefreshToken, clearToken, clearRefreshToken } from '@/lib/auth'; import { useToast } from '@/components/ui/use-toast'; import { logout as logoutApi } from '@/api/auth'; @@ -9,12 +10,14 @@ import { logout as logoutApi } from '@/api/auth'; const navigation = [ { name: 'Dashboard', href: '/dashboard' }, { name: 'Budgets', href: '/budgets' }, + { name: 'Savings', href: '/savings', icon: Target }, { name: 'Bills', href: '/bills' }, { name: 'Reminders', href: '/reminders' }, { name: 'Expenses', href: '/expenses' }, { name: 'Analytics', href: '/analytics' }, ]; + export function Navbar() { const [isOpen, setIsOpen] = useState(false); const [isAuthed, setIsAuthed] = useState(!!getToken()); @@ -69,12 +72,14 @@ export function Navbar() { to={item.href} className={ active - ? 'rounded-full bg-secondary px-4 py-2 text-xs font-semibold text-secondary-foreground shadow-sm' - : 'rounded-full px-4 py-2 text-xs font-semibold text-muted-foreground transition hover:bg-muted hover:text-foreground' + ? 'rounded-full bg-secondary px-4 py-2 text-xs font-semibold text-secondary-foreground shadow-sm flex items-center' + : 'rounded-full px-4 py-2 text-xs font-semibold text-muted-foreground transition hover:bg-muted hover:text-foreground flex items-center' } > + {item.icon && } {item.name} + ); })} @@ -124,12 +129,14 @@ export function Navbar() { onClick={() => setIsOpen(false)} className={ active - ? 'block rounded-lg bg-secondary px-3 py-2 text-sm font-medium text-secondary-foreground' - : 'block rounded-lg px-3 py-2 text-sm font-medium text-foreground hover:bg-muted' + ? 'flex items-center rounded-lg bg-secondary px-3 py-2 text-sm font-medium text-secondary-foreground' + : 'flex items-center rounded-lg px-3 py-2 text-sm font-medium text-foreground hover:bg-muted' } > + {item.icon && } {item.name} + ); })}
diff --git a/app/src/pages/Dashboard.tsx b/app/src/pages/Dashboard.tsx index b2d4e7aa8..e38a8171d 100644 --- a/app/src/pages/Dashboard.tsx +++ b/app/src/pages/Dashboard.tsx @@ -18,7 +18,9 @@ import { AlertTriangle, Calendar, Plus, + Target, } from 'lucide-react'; + import { getDashboardSummary, type DashboardSummary } from '@/api/dashboard'; import { useNavigate } from 'react-router-dom'; import { formatMoney } from '@/lib/currency'; @@ -95,8 +97,17 @@ export function Dashboard() { icon: CreditCard, description: 'Due soon', }, + { + title: 'Savings Progress', + amount: currency(13100), + change: '65%', + trend: 'up', + icon: Target, + description: 'Goal: $20,000', + }, ] as const; + const transactions = data?.recent_transactions ?? []; const upcomingBills = data?.upcoming_bills ?? []; const categoryBreakdown = data?.category_breakdown ?? []; @@ -141,7 +152,8 @@ export function Dashboard() {
)} -
+
+ {summaryCards.map((card, index) => ( diff --git a/app/src/pages/Savings.tsx b/app/src/pages/Savings.tsx new file mode 100644 index 000000000..9181d38ba --- /dev/null +++ b/app/src/pages/Savings.tsx @@ -0,0 +1,113 @@ +import { useState } from 'react'; +import { FinancialCard, FinancialCardContent, FinancialCardDescription, FinancialCardHeader, FinancialCardTitle } from '@/components/ui/financial-card'; +import { Button } from '@/components/ui/button'; +import { Target, Plus, Award, Clock } from 'lucide-react'; +import { formatMoney } from '@/lib/currency'; + +const mockGoals = [ + { + id: 1, + title: 'Emergency Fund', + target: 10000, + current: 8500, + deadline: 'Dec 2025', + }, + { + id: 2, + title: 'New Laptop', + target: 2000, + current: 400, + deadline: 'Aug 2025', + }, + { + id: 3, + title: 'Travel Fund', + target: 5000, + current: 4200, + deadline: 'Oct 2025', + } +]; + +export function Savings() { + const [goals] = useState(mockGoals); + + const getProgressClass = (percentage: number) => { + return percentage >= 80 ? 'chart-fill-success' : 'chart-fill-primary'; + }; + + return ( +
+
+
+
+

Savings Goals

+

+ Plan your future and track your progress towards financial freedom +

+
+
+ +
+
+
+ +
+ {goals.map((goal) => { + const percentage = (goal.current / goal.target) * 100; + const isHighProgress = percentage >= 80; + + return ( + + +
+ + + {goal.title} + + {isHighProgress && ( + + )} +
+ + Target date: {goal.deadline} + +
+ +
+
+
+
+ {formatMoney(goal.current)} +
+
+ of {formatMoney(goal.target)} +
+
+
+ {percentage.toFixed(0)}% +
+
+ +
+
+
+ +
+ + {isHighProgress ? 'Almost there!' : 'On your way'} +
+
+ + + ); + })} +
+
+ ); +} diff --git a/app/vite.config.ts b/app/vite.config.ts index 329b7953f..d648f8794 100644 --- a/app/vite.config.ts +++ b/app/vite.config.ts @@ -6,7 +6,7 @@ import path from "path"; export default defineConfig(({ mode }) => ({ server: { host: "::", - port: 5173, + port: 3000, }, plugins: [ react(),