A modern, responsive expense tracking application built with React, TypeScript, and Tailwind CSS. Manage your expenses with ease using features like add, edit, delete, search, filter, visualize spending patterns with interactive charts, and convert currencies in real-time.
Before starting the frontend setup, ensure you have:
- Node.js (v16+) and npm installed
- Backend Server running (see Backend Requirements section below)
- API Keys/Environment Variables configured
git clone https://github.com/varshitverma/ui-exp-tracker
cd "d:\exp tracker app\frontend"Before installing dependencies, create a .env.local file in the root directory:
# Required: Backend API URL
VITE_API_BASE_URL=http://localhost:3000/api/v1Environment Variables:
| Variable | Value | Required | Notes |
|---|---|---|---|
VITE_API_BASE_URL |
Backend URL | ✅ Yes | E.g., http://localhost:3000/api/v1 |
Important:
- The app requires a backend server to store expenses persistently
- Without
VITE_API_BASE_URL, the app uses sample data only - Update the URL based on your backend deployment (local dev, staging, production)
Your backend must provide these API endpoints:
GET /api/v1/expenses # Get all expenses
POST /api/v1/expenses # Create expense
PUT /api/v1/expenses/:id # Update expense
DELETE /api/v1/expenses/:id # Delete expense
GET /api/v1/expenses/convert/:currency # Convert to target currency
Currency Conversion:
- The frontend expects the backend to provide exchange rate conversion
- Backend should use ExchangeRate-API or similar service for live rates
- Default currency: INR (Indian Rupees)
npm installnpm run devThe app will start at http://localhost:5173
npm run buildsrc/
├── App.tsx # Main app with state management
├── main.tsx # Entry point
├── App.css # Global styles
├── index.css # Tailwind setup
│
├── components/
│ ├── app-sidebar.tsx # Navigation sidebar
│ ├── site-header.tsx # Page header
│ ├── section-cards.tsx # Metric cards (Dashboard)
│ ├── expense-form.tsx # Add/Edit expense form
│ ├── expense-table.tsx # Expenses list with CRUD
│ ├── expense-chart.tsx # Charts and analytics
│ ├── nav-main.tsx # Main navigation menu
│ ├── nav-secondary.tsx # Secondary navigation
│ ├── nav-user.tsx # User profile section
│ └── ui/ # shadcn/ui components
│ ├── button.tsx
│ ├── card.tsx
│ ├── input.tsx
│ ├── select.tsx
│ ├── drawer.tsx
│ ├── table.tsx
│ └── ... (other UI components)
│
├── pages/
│ ├── dashboard-page.tsx # Dashboard view
│ ├── expenses-page.tsx # Expenses management view
│ └── analytics-page.tsx # Analytics view
│
├── services/
│ └── api.ts # API service layer
│
├── context/
│ └── nav-context.tsx # Navigation context
│
├── hooks/
│ └── use-mobile.ts # Mobile detection hook
│
└── lib/
└── utils.ts # Utility functions
- Metric Cards: Total expenses, monthly spending, top category, average expense
- Interactive Charts: View spending trends with multiple visualization modes
- Real-time Updates: Metrics update as you add/edit expenses
- Currency Display: All amounts shown in selected currency
- Add Expenses: Quick form to create new expense entries (always in INR)
- Edit Expenses: Modify existing expenses with pre-filled data
- Delete Expenses: Remove expenses with confirmation
- Search: Find expenses by description, category, or payment method
- Filter: Filter by category
- Sort: Sort by date or amount
- Pagination: Navigate through large expense lists
- 180+ Supported Currencies: USD, EUR, GBP, JPY, INR, and many more
- Live Exchange Rates: Real-time rates powered by ExchangeRate-API
- Instant Conversion: Switch currencies from sidebar dropdown
- All Views Updated: Dashboard, charts, tables show selected currency
- Symbol Display: Each currency shows its symbol (₹, $, €, £, etc.)
- Default INR: All data stored in INR, conversion on-the-fly
- Trend Chart: Line chart showing spending over time
- Category Breakdown: Pie chart showing spending by category
- Payment Method Analysis: Bar chart showing payment method usage
- Monthly Statistics: Detailed breakdown by month
- Currency-Aware: Charts update with selected currency
- Responsive Design: Works on desktop, tablet, and mobile
- Dark Mode Support: Auto-detects system preference
- Form Validation: Real-time validation with error messages
- Toast Notifications: User feedback for all actions
- Collapsible Sidebar: Optimized for mobile viewing
- Currency Selector: Easy currency switching in sidebar
- React 19.2.0 - UI framework
- TypeScript 5.9 - Type safety
- Tailwind CSS 4.1.18 - Styling
- Recharts 2.15.4 - Data visualization
- React Table 8.21.3 - Advanced table features
- Zod 4.3.6 - Form validation
- Sonner 2.0.7 - Toast notifications
- shadcn/ui - Component library
- Tabler Icons 3.36.1 - Icons
- Vite - Build tool
- Click "Create Expense" button in the sidebar OR
- Go to Expenses page and click "Add Expense"
- Fill in the form:
- Amount (required)
- Category (required)
- Description (optional)
- Date (required)
- Payment Method (required)
- Click "Add Expense"
- Form closes and expense appears in the table
- Open Expenses page
- Find the expense in the table
- Click the three-dot menu (⋮) on the right
- Select "Edit"
- Form opens with current data
- Update any fields
- Click "Update Expense"
- Open Expenses page
- Find the expense in the table
- Click the three-dot menu (⋮) on the right
- Select "Delete"
- Expense is removed immediately
- Look at the Sidebar (bottom section, above user menu)
- Click the "Exchange Rate" dropdown
- Select any currency from 180+ options
- Search by currency code (USD, EUR, GBP, etc.)
- Or scroll to find currency name
- Conversion happens instantly:
- Exchange rates fetched from ExchangeRate-API
- All expenses instantly shown in new currency
- Charts and cards update with new amounts
- Important Notes:
- All data stored in INR in the database
- Conversion is display-only (no data change)
- Refreshing page returns to last selected currency
- Free tier limited to 500 requests/month (ExchangeRate-API)
Use the sidebar to switch between:
- Dashboard - Overview with charts
- Expenses - Manage all expenses
- Analytics - Detailed analytics views
{
id: number; // Auto-generated ID
amount: number; // Expense amount
category: string; // Food, Transport, Utilities, etc.
description: string | null; // Optional note
date: string; // YYYY-MM-DD format
payment_method: string; // Cash, Credit Card, etc.
created_at: string; // Timestamp
updated_at: string; // Timestamp
}- Food & Dining
- Transport
- Utilities
- Shopping
- Entertainment
- Health & Medical
- Education
- Other
- Cash
- Credit Card
- Debit Card
- Bank Transfer
- Mobile Wallet
This frontend requires a backend API to function properly. Follow these steps:
Ensure your backend is running before starting the frontend:
# Example: If backend is in parent directory
cd ../backend
npm install
npm run devBackend should be accessible at: http://localhost:3000 (or your configured port)
Make sure .env.local has correct backend URL:
VITE_API_BASE_URL=http://localhost:3000/api/v1Your backend must implement these endpoints:
Get All Expenses
GET /api/v1/expenses
Response:
{
"success": true,
"data": [
{
"id": 1,
"amount": 1200.5,
"category": "Food",
"description": "Lunch",
"date": "2026-01-15T12:00:00",
"payment_method": "Credit Card",
"created_at": "2026-01-15T12:00:00",
"updated_at": "2026-01-15T12:00:00"
}
]
}
Create Expense
POST /api/v1/expenses
Body:
{
"amount": 1200.5,
"category": "Food",
"description": "Lunch",
"date": "2026-01-15T12:00:00",
"payment_method": "Credit Card"
}
Response: Returns created expense with ID
Update Expense
PUT /api/v1/expenses/:id
Body: Same as create
Response: Returns updated expense
Delete Expense
DELETE /api/v1/expenses/:id
Response: Success message
Currency Conversion (IMPORTANT)
GET /api/v1/expenses/convert/:target_currency
Example: GET /api/v1/expenses/convert/USD
Response:
{
"success": true,
"data": [
{
"id": 1,
"amount": 1200.5,
"category": "Food",
"description": "Lunch",
"date": "2026-01-15T12:00:00",
"payment_method": "Credit Card",
"original_amount": 1200.5,
"original_currency": "INR",
"converted_amount": 14.42,
"target_currency": "USD",
"created_at": "2026-01-15T12:00:00",
"updated_at": "2026-01-15T12:00:00"
}
]
}
The frontend supports 180+ currencies with live exchange rates. Your backend must:
-
Use ExchangeRate-API (Recommended)
- Signup: https://www.exchangerate-api.com
- Get free tier API key
- Set environment variable:
EXCHANGE_RATE_API_KEY=your_key_here
-
Supported Currencies:
- Default: INR (Indian Rupees ₹)
- All ISO 4217 currency codes supported
- Live rates updated automatically
-
Backend Implementation Tips:
- Cache exchange rates (refresh every 24 hours)
- Always store amounts in INR in database
- Return conversion for requested currency only
- Include original_currency and target_currency in response
All API calls are managed in src/services/api.ts:
export const expenseApi = {
async fetchExpenses(): Promise<Expense[]> { ... }
async createExpense(expense: Omit<Expense, 'id'>): Promise<Expense> { ... }
async updateExpense(id: number, expense: Omit<Expense, 'id'>): Promise<Expense> { ... }
async deleteExpense(id: number): Promise<void> { ... }
async convertCurrency(targetCurrency: string): Promise<Expense[]> { ... }
}The API service automatically:
- ✅ Handles network errors gracefully
- ✅ Shows error toast notifications
- ✅ Falls back to cached data when available
- ✅ Logs errors to console for debugging
Problem: "API not available, using local data only"
- Check backend is running:
curl http://localhost:3000/api/v1/expenses - Verify
VITE_API_BASE_URLis correct in.env.local - Check browser console for CORS errors
- Ensure backend has proper CORS configuration
Problem: Currency conversion not working
- Verify backend has ExchangeRate-API key configured
- Test endpoint:
curl http://localhost:3000/api/v1/expenses/convert/USD - Check backend logs for API call failures
- Ensure backend can access external APIs
For production:
- Update
VITE_API_BASE_URLto your deployed backend URL - Ensure backend is on HTTPS
- Configure CORS properly for your domain
- Use environment secrets for sensitive data
- Enable rate limiting on backend
-
Set environment variable:
VITE_API_BASE_URL=http://{backend-url}/api/v1 -
Your backend should provide these endpoints:
GET /api/expenses # Get all expenses POST /api/expenses # Create expense PUT /api/expenses/:id # Update expense DELETE /api/expenses/:id # Delete expense -
Request/Response format:
{ "amount": 45.5, "category": "Food", "description": "Lunch", "date": "2026-02-01", "payment_method": "Credit Card" }
All API calls go through src/services/api.ts. This service:
- Handles network requests
- Manages error responses
- Shows toast notifications
- Falls back to mock data if API unavailable
This section covers deploying the Expense Tracker frontend to a production server with full HTTPS support.
Create a .env.production file in the root directory for production builds:
# Production API URL - use relative path or your domain
VITE_API_BASE_URL=/api/v1Important Notes:
- Never hardcode IP addresses - use relative paths or domain names
- The API is served under
/api/v1path on the same domain - Nginx will proxy
/api/*requests to the backend server
Prepare the frontend build locally:
# Install dependencies
npm install
# Build for production
npm run buildThis creates an optimized dist/ folder with minified code, ready for deployment.
Choose one of the following methods:
# From your local machine
scp -i your-key.pem -r dist/* [email protected]:/var/www/html/# If already on the server
sudo rm -rf /var/www/html/*
sudo cp -r dist/* /var/www/html/Permissions:
# Ensure Nginx can read the files
sudo chown -R www-data:www-data /var/www/html/
sudo chmod -R 755 /var/www/html/Update Nginx configuration to serve the frontend and proxy API requests:
server {
listen 80;
server_name yourdomain.com www.yourdomain.com;
# Serve static files from React build
root /var/www/html;
index index.html;
# Handle all requests, default to index.html for SPA routing
location / {
try_files $uri $uri/ /index.html;
}
# Proxy API requests to backend
location /api/ {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Disable caching for HTML (allow cache busting via file hash)
location ~* \.html?$ {
expires -1;
add_header Cache-Control "no-cache, no-store, must-revalidate";
}
# Cache static assets (JS, CSS, images)
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 30d;
add_header Cache-Control "public, immutable";
}
}Using Hostinger or similar DNS provider:
Add these DNS records:
| Type | Name | Value |
|---|---|---|
| A | @ | YOUR_EC2_IP |
| CNAME | www | yourdomain.com |
Steps:
- Go to your domain's DNS management
- Add the A record pointing to your server IP
- Add the CNAME record for www subdomain
- Wait for DNS propagation (can take 15-30 minutes)
Secure your domain with free SSL certificates.
sudo apt update
sudo apt install certbot python3-certbot-nginx -y# For single domain
sudo certbot --nginx -d yourdomain.com
# For multiple domains (recommended)
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.comWhen prompted:
- Enter email for important notices
- Accept terms and conditions
- Choose Option 2: Redirect traffic to HTTPS (recommended)
# Check renewal timer is active
sudo systemctl status certbot.timer
# Test automatic renewal
sudo certbot renew --dry-runCertbot automatically renews certificates 30 days before expiration.
Test all endpoints to ensure everything works:
https://yourdomain.comYou should see the Expense Tracker interface loading.
curl https://yourdomain.com/api/v1/expensesShould return your expenses (or empty array if none exist).
https://yourdomain.com/api/docsShould display the API documentation (if backend includes Swagger).
# SSH into your server
ssh -i your-key.pem [email protected]
# Navigate to backend directory
cd ~/backend
# Pull latest changes
git pull
# Install new dependencies if needed
source env/bin/activate
pip install -r requirements.txt
deactivate
# Restart the service
sudo systemctl restart fastapi
# Verify restart
sudo systemctl status fastapi# On your local machine, build the latest version
npm run build
# Upload to server
scp -i your-key.pem -r dist/* [email protected]:/var/www/html/
# Or use direct copy if on server:
# sudo cp -r dist/* /var/www/html/No restart needed for frontend - Nginx automatically serves new files.
Your deployed system has this architecture:
Internet (HTTPS)
↓
Nginx (Port 443)
├── / → React Frontend (Static Files)
│ └── /index.html for all routes (SPA routing)
│
└── /api/* → FastAPI Backend (Gunicorn)
└── Proxied to 127.0.0.1:8000
Data Flow:
- User requests
https://yourdomain.com→ Nginx serves React app - React app makes API call to
/api/v1/expenses→ Nginx proxies to FastAPI backend - Backend processes request and returns JSON
- Frontend displays data to user
- ✅ HTTPS enabled with valid SSL certificate
- ✅ DNS records properly configured
- ✅ Nginx firewall rules (if using AWS security groups)
- ✅ Backend running on localhost only (127.0.0.1:8000)
- ✅ Environment variables not hardcoded
- ✅ API rate limiting enabled (optional)
- ✅ CORS configured for your domain only
- ✅ Auto-renewal certificate timer active
Problem: Certificate renewal failing
# Check renewal logs
sudo journalctl -u certbot.timer -n 50
# Force renewal (use sparingly)
sudo certbot renew --force-renewalProblem: API returning 502 Bad Gateway
- Check backend is running:
sudo systemctl status fastapi - Verify backend listening on 127.0.0.1:8000
- Check Nginx error logs:
sudo tail -n 50 /var/log/nginx/error.log
Problem: Frontend showing blank page
- Check browser console for errors
- Verify files exist:
ls -la /var/www/html/ - Check Nginx access logs:
sudo tail -n 50 /var/log/nginx/access.log
Problem: DNS not resolving
- Wait for propagation (can take up to 48 hours)
- Check:
nslookup yourdomain.com - Or:
dig yourdomain.com
npm run dev # Start development server
npm run build # Build for production
npm run preview # Preview production build
npm run lint # Run ESLintThe app includes 6 pre-loaded sample expenses for testing. You can:
- Add more expenses
- Edit sample expenses
- Delete all of them
- Refresh page to restore sample data
- Check all required fields are filled
- Verify amount is a valid number
- Ensure date is selected
- Red error messages indicate which field is invalid
- Expected behavior - app uses local state by default
- Connect to backend for data persistence
- See Backend Integration section above
- Ensure search terms match expense data
- Search is case-insensitive
- Can search by description, category, or payment method
- Ensure you have expenses added
- Try switching between different chart types
- Wait for data to load if using API
Real-time search box filters expenses by:
- Description (e.g., "lunch", "gas")
- Category (e.g., "food", "transport")
- Payment method (e.g., "cash", "card")
Category dropdown filters the table to show only expenses from selected category.
Click column headers to sort:
- Date: Ascending/descending by date
- Amount: Ascending/descending by amount
Navigate large expense lists:
- First/Last - Jump to first/last page
- Previous/Next - Page by page navigation
- Page indicator shows current position
Click "View" button to toggle which columns appear in table.
Tab- Navigate form fieldsEnter- Submit formEscape- Close drawer/modal
- Charts are optimized for 1000+ expenses
- Search filters in real-time
- Pagination for efficient rendering
- Lazy loading ready for future optimization
- Chrome (latest)
- Firefox (latest)
- Safari (latest)
- Edge (latest)
Planned features:
- Budget goals and alerts
- Recurring expenses
- Export to CSV/PDF
- Multi-user support
- Mobile app
- Advanced analytics
- Expense categories customization
- Data persists only in browser memory (no refresh persistence without backend)
- No undo functionality
- Maximum 10,000 expenses before performance impact
- Single user only
To contribute:
- Create a feature branch
- Make your changes
- Test thoroughly
- Submit a pull request
MIT License - See LICENSE file for details
For issues or questions:
- Check the troubleshooting section above
- Review inline code comments
- Check component prop types for usage
- Open an issue with detailed description
- Components: Check
src/componentsfor implementation examples - API: See
src/services/api.tsfor backend integration - Navigation: See
src/context/nav-context.tsxfor page navigation - Types: All components are fully TypeScript typed
Ready to start? Run npm run dev and open http://localhost:5173!
Happy tracking! 💰📊