CashGap Features
CashGap provides a complete suite of financial management tools to help users take control of their money.
Dashboard Overview
The dashboard is the central hub that displays all your financial data at a glance.
Financial Metrics
Total Balance Real-time calculation of income minus expenses and subscriptions
Income Tracking Total income with source count and breakdown by frequency
Expense Summary Total expenses with item count and category breakdown
Active Subscriptions Monthly and yearly subscription costs with renewal tracking
Dashboard Implementation
The dashboard aggregates data from multiple sources:
app/(dashboard)/dashboard/page.tsx
import { useDashboardData } from "@/hooks" ;
export default function DashboardPage () {
const { data , isLoading } = useDashboardData ();
const incomes = data ?. incomes ?? [];
const expenses = data ?. expenses ?? [];
const subscriptions = data ?. subscriptions ?? [];
// Calculate totals
const stats = useMemo (() => {
const totalIncome = incomes . reduce (( sum , i ) => sum + i . amount , 0 );
const totalExpenses = expenses . reduce (( sum , e ) => sum + e . amount , 0 );
const totalSubscriptions = subscriptions
. filter ( s => s . active )
. reduce (( sum , s ) => sum + s . amount , 0 );
const totalBalance = totalIncome - totalExpenses - totalSubscriptions ;
return { totalBalance , totalIncome , totalExpenses , totalSubscriptions };
}, [ incomes , expenses , subscriptions ]);
return (
< DashboardWrapper >
{ /* Display financial metrics */ }
</ DashboardWrapper >
);
}
Recent Transactions
The dashboard displays the 5 most recent transactions combining income and expenses:
const recentTransactions = useMemo (() => {
const allTransactions = [
... incomes . map ( i => ({
id: i . id ,
name: i . name ,
date: i . date ,
amount: i . amount ,
type: "income" as const ,
})),
... expenses . map ( e => ({
id: e . id ,
name: e . name ,
date: e . date ,
amount: - e . amount ,
type: "expense" as const ,
})),
];
return allTransactions
. sort (( a , b ) => new Date ( b . date ). getTime () - new Date ( a . date ). getTime ())
. slice ( 0 , 5 );
}, [ incomes , expenses ]);
Income Management
Income Features
Income tracking supports one-time, monthly, and yearly frequencies to accommodate various income sources like salaries, bonuses, freelance work, and investments.
Key Capabilities:
Create income entries with name, amount, and date
Categorize by frequency (once, monthly, yearly)
Add optional notes and categories
Edit and delete existing entries
Filter by date range and category
Income Statistics
The income page displays three key metrics:
const totalIncome = incomes . reduce (( sum , i ) => sum + i . amount , 0 );
const monthlyIncome = incomes
. filter ( i => i . frequency === "monthly" )
. reduce (( sum , i ) => sum + i . amount , 0 );
const yearlyIncome = incomes
. filter ( i => i . frequency === "yearly" )
. reduce (( sum , i ) => sum + i . amount , 0 );
Expense Management
Expense Categories
Each expense is assigned to one of eight categories:
Food & Dining
Transportation
Shopping
Housing
Utilities
Entertainment
Health
Other
Restaurants, groceries, coffee shops, food delivery
Icon: Utensils
Color: Orange
Gas, public transit, parking, car maintenance, rideshare Clothing, electronics, home goods, gifts
Icon: Shopping Bag
Color: Pink
Rent, mortgage, property taxes, home insurance Electricity, water, gas, internet, phone Movies, games, hobbies, events, concerts
Icon: Gamepad
Color: Indigo
Medical, dental, pharmacy, gym, wellness Miscellaneous expenses that don’t fit other categories
Icon: More Horizontal
Color: Gray
Category Configuration
const categoryConfig : Record < ExpenseCategory , {
label : string ;
icon : React . ComponentType <{ className ?: string }>;
color : string ;
bg : string ;
}> = {
food: {
label: "Food & Dining" ,
icon: Utensils ,
color: "text-orange-500" ,
bg: "bg-orange-500/10" ,
},
transport: {
label: "Transport" ,
icon: Car ,
color: "text-blue-500" ,
bg: "bg-blue-500/10" ,
},
// ... other categories
};
Spending Analysis
The expenses page includes a category breakdown that shows:
Total spending per category
Percentage of total expenses
Visual progress bars
Color-coded category cards
const spendingByCategory = useMemo (() => {
const categoryTotals : Record < string , number > = {};
expenses . forEach ( e => {
categoryTotals [ e . category ] = ( categoryTotals [ e . category ] || 0 ) + e . amount ;
});
const totalSpending = Object . values ( categoryTotals ). reduce (( sum , v ) => sum + v , 0 );
return Object . entries ( categoryTotals )
. map (([ category , amount ]) => ({
category ,
amount ,
percentage: totalSpending > 0 ? ( amount / totalSpending ) * 100 : 0 ,
}))
. sort (( a , b ) => b . amount - a . amount )
. slice ( 0 , 5 );
}, [ expenses ]);
Subscription Tracking
Subscription Features
Active/Inactive Status Pause subscriptions without deleting them to track seasonal services
Billing Reminders Get alerts for subscriptions renewing in the next 7 days
Cost Calculations Automatic monthly and yearly cost calculations regardless of billing frequency
Next Billing Date Track when each subscription will charge next
Subscription Management
app/subscriptions/page.tsx
// Calculate monthly total (normalize yearly to monthly)
const monthlyTotal = activeSubscriptions . reduce (( sum , s ) => {
return sum + ( s . frequency === "yearly" ? s . amount / 12 : s . amount );
}, 0 );
const yearlyTotal = monthlyTotal * 12 ;
// Find upcoming renewals (next 7 days)
const today = new Date ();
const nextWeek = new Date ( today . getTime () + 7 * 24 * 60 * 60 * 1000 );
const upcomingRenewals = activeSubscriptions . filter ( s => {
const billingDate = new Date ( s . nextBillingDate );
return billingDate >= today && billingDate <= nextWeek ;
});
Subscription Actions
Users can perform these actions on subscriptions:
Toggle Active/Paused
const handleToggle = async () => {
await toggleSubscriptionActive ( subscription . id );
await queryClient . invalidateQueries ({ queryKey: queryKeys . dashboard });
};
Edit Subscription
Navigate to edit page: /subscriptions/${id}/edit
Delete Subscription
const handleDelete = async () => {
await deleteSubscription ( subscription . id );
await queryClient . invalidateQueries ({ queryKey: queryKeys . dashboard });
};
User Interface Components
Collapsible Sections
Dashboard sections use Radix UI’s Collapsible component for better organization:
< Collapsible open = { transactionsOpen } onOpenChange = { setTransactionsOpen } >
< CollapsibleTrigger asChild >
< button className = "flex items-center gap-3 w-full group" >
< div className = "flex items-center gap-2" >
< ArrowUpDown className = "h-5 w-5 text-primary" />
< h3 className = "text-lg font-semibold" > Recent Transactions </ h3 >
</ div >
< ChevronDown className = { cn (
"h-5 w-5 transition-transform" ,
transactionsOpen && "rotate-180"
)} />
</ button >
</ CollapsibleTrigger >
< CollapsibleContent >
{ /* Transaction list */ }
</ CollapsibleContent >
</ Collapsible >
Contextual actions use dropdown menus:
< DropdownMenu >
< DropdownMenuTrigger asChild >
< button className = "p-2.5 rounded-2xl hover:bg-muted" >
< MoreVertical className = "h-4 w-4" />
</ button >
</ DropdownMenuTrigger >
< DropdownMenuContent align = "end" >
< DropdownMenuItem asChild >
< Link href = { `/income/ ${ income . id } /edit` } >
< Edit className = "h-4 w-4 mr-2" />
Edit
</ Link >
</ DropdownMenuItem >
< DropdownMenuItem onClick = { handleDelete } >
< Trash2 className = "h-4 w-4 mr-2" />
Delete
</ DropdownMenuItem >
</ DropdownMenuContent >
</ DropdownMenu >
Data Fetching & Caching
TanStack Query Integration
CashGap uses TanStack Query for efficient data fetching:
export const queryKeys = {
dashboard: [ 'dashboard' ],
incomes: [ 'incomes' ],
expenses: [ 'expenses' ],
subscriptions: [ 'subscriptions' ],
};
export function useDashboardData () {
return useQuery ({
queryKey: queryKeys . dashboard ,
queryFn : async () => {
const response = await fetch ( '/api/dashboard' );
if ( ! response . ok ) throw new Error ( 'Failed to fetch dashboard data' );
return response . json ();
},
});
}
Cache Invalidation
After mutations, the cache is invalidated to refetch fresh data:
await queryClient . invalidateQueries ({ queryKey: queryKeys . dashboard });
Responsive Design
All pages are fully responsive with mobile-first design:
Grid layouts adjust from 1 column on mobile to 2-4 columns on desktop
Touch-friendly button sizes (minimum 44x44px)
Collapsible navigation on mobile
Optimized typography scales
Next Steps
Authentication Learn about authentication implementation
Income Tracking Deep dive into income tracking
Expense Management Explore expense management details
Subscriptions Manage recurring subscriptions