diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 2d1995a..f3e0605 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -14,6 +14,8 @@
"react": "^19.1.1",
"react-chartjs-2": "^5.3.0",
"react-dom": "^19.1.1",
+ "react-hot-toast": "^2.6.0",
+ "react-router-dom": "^7.9.1"
"react-icons": "^5.5.0",
"react-router-dom": "^7.9.1",
"react-toastify": "^11.0.5"
@@ -2784,7 +2786,6 @@
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
- "dev": true,
"license": "MIT"
},
"node_modules/data-urls": {
@@ -3604,6 +3605,14 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/goober": {
+ "version": "2.1.16",
+ "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.16.tgz",
+ "integrity": "sha512-erjk19y1U33+XAMe1VTvIONHYoSqE4iS7BYUZfHaqeohLmnC0FdxEh7rQU+6MZ4OajItzjZFSRtVANrQwNq6/g==",
+ "peerDependencies": {
+ "csstype": "^3.0.10"
+ }
+ },
"node_modules/gopd": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
@@ -5020,6 +5029,20 @@
"react": "^19.1.1"
}
},
+ "node_modules/react-hot-toast": {
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.6.0.tgz",
+ "integrity": "sha512-bH+2EBMZ4sdyou/DPrfgIouFpcRLCJ+HoCA32UoAYHn6T3Ur5yfcDCeSr5mwldl6pFOsiocmrXMuoCJ1vV8bWg==",
+ "dependencies": {
+ "csstype": "^3.1.3",
+ "goober": "^2.1.16"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "react": ">=16",
+ "react-dom": ">=16"
"node_modules/react-icons": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz",
diff --git a/frontend/package.json b/frontend/package.json
index 18e062f..eb0f719 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -17,6 +17,8 @@
"react": "^19.1.1",
"react-chartjs-2": "^5.3.0",
"react-dom": "^19.1.1",
+ "react-hot-toast": "^2.6.0",
+ "react-router-dom": "^7.9.1"
"react-icons": "^5.5.0",
"react-router-dom": "^7.9.1",
"react-toastify": "^11.0.5"
diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx
index a60f3cd..facf56e 100644
--- a/frontend/src/App.jsx
+++ b/frontend/src/App.jsx
@@ -11,6 +11,7 @@ import WelcomePage from './pages/WelcomePage';
import ProtectedRoute from './components/ProtectedRoute';
import SetupProtectedRoute from './components/SetupProtectedRoute';
import Layout from './components/Layout';
+import { Toaster } from 'react-hot-toast'
import SettingsPage from './pages/SettingsPage';
import RecurringTransactions from './pages/RecurringTransactions';
import ContactUs from './pages/ContactUs';
@@ -23,6 +24,13 @@ function App() {
} />
} />
} />
+
+ {/* Protected Routes Wrapper */}
+
+
+
} />
{/* Protected Routes */}
+
>
);
}
diff --git a/frontend/src/contexts/AuthContext.jsx b/frontend/src/contexts/AuthContext.jsx
index a82e76b..cd16875 100644
--- a/frontend/src/contexts/AuthContext.jsx
+++ b/frontend/src/contexts/AuthContext.jsx
@@ -2,6 +2,7 @@ import React, { createContext, useState, useEffect } from 'react';
import { toast } from 'react-toastify';
import { useNavigate } from 'react-router-dom';
import api from '../api/axios';
+import toast from 'react-hot-toast';
const AuthContext = createContext();
@@ -24,7 +25,7 @@ export const AuthProvider = ({ children }) => {
setUser(response.data);
} catch (error) {
// Clear invalid token
- console.error("Token verification failed", error);
+ toast.error("Token verification failed");
localStorage.removeItem('token');
setUser(null);
setToken(null);
@@ -60,6 +61,7 @@ export const AuthProvider = ({ children }) => {
navigate('/dashboard');
}
} catch (error) {
+ toast.error('Login failed');
console.error('Login failed', error.response?.data);
setPendingToast({ type: 'error', message: error.response?.data?.message || 'Login failed. Please try again.' });
throw new Error(error.response?.data?.message || 'Login failed. Please try again.');
@@ -79,6 +81,7 @@ export const AuthProvider = ({ children }) => {
setPendingToast({ type: 'success', message: 'Signup successful!' });
navigate('/setup');
} catch (error) {
+ toast.error('Signup failed');
console.error('Signup failed', error.response?.data);
setPendingToast({ type: 'error', message: error.response?.data?.message || 'Signup failed. Please try again.' });
throw new Error(error.response?.data?.message || 'Signup failed. Please try again.');
diff --git a/frontend/src/pages/DashboardPage.jsx b/frontend/src/pages/DashboardPage.jsx
index 0fd78f1..b445c18 100644
--- a/frontend/src/pages/DashboardPage.jsx
+++ b/frontend/src/pages/DashboardPage.jsx
@@ -6,6 +6,7 @@ import TransactionModal from '../components/TransactionModal';
import useCurrency from '../hooks/useCurrency';
import useTheme from '../hooks/useTheme';
import Spinner from '../components/Spinner';
+import toast from 'react-hot-toast';
import EmptyState from '../components/EmptyState';
import { IoWarning } from "react-icons/io5";
@@ -62,7 +63,7 @@ const DashboardPage = () => {
setIncomeCategories(incomeCategoriesRes.data);
setRecentTransactions(summaryRes.data.recentTransactions || []);
} catch (error) {
- console.error("Failed to fetch dashboard data", error);
+ toast.error("Failed to fetch dashboard data");
} finally {
setLoading(false);
}
@@ -94,7 +95,7 @@ const DashboardPage = () => {
fetchData();
handleCloseModal();
} catch (error) {
- console.error("Failed to save transaction", error);
+ toast.error("Failed to save transaction");
}
};
diff --git a/frontend/src/pages/ReceiptsPage.jsx b/frontend/src/pages/ReceiptsPage.jsx
index 5b7e8b2..b2ecc9f 100644
--- a/frontend/src/pages/ReceiptsPage.jsx
+++ b/frontend/src/pages/ReceiptsPage.jsx
@@ -1,6 +1,7 @@
import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import api from '../api/axios';
+import toast from 'react-hot-toast';
const ReceiptsPage = () => {
const [file, setFile] = useState(null);
@@ -38,7 +39,7 @@ const ReceiptsPage = () => {
navigate('/dashboard');
} catch (err) {
setError('Upload failed. Please try again.');
- console.error(err);
+ toast.error('Upload failed. Please try again.');
} finally {
setUploading(false);
}
diff --git a/frontend/src/pages/TransactionsPage.jsx b/frontend/src/pages/TransactionsPage.jsx
index 248e76b..af8dcea 100644
--- a/frontend/src/pages/TransactionsPage.jsx
+++ b/frontend/src/pages/TransactionsPage.jsx
@@ -6,6 +6,7 @@ import TransactionDetailModal from '../components/TransactionDetailModal'
import ManageCategoriesModal from '../components/ManageCategoriesModal';
import Spinner from '../components/Spinner';
import useCurrency from '../hooks/useCurrency';
+import toast from 'react-hot-toast';
import EmptyState from '../components/EmptyState';
const handleExportCSV = async () => {
@@ -23,7 +24,7 @@ const handleExportCSV = async () => {
a.remove();
window.URL.revokeObjectURL(url);
} catch (error) {
- console.error("Failed to export CSV", error);
+ toast.error("Failed to export CSV");
alert("Failed to export CSV. Please try again.");
}
};
@@ -106,7 +107,7 @@ const TransactionsPage = () => {
setSelectedTransactionIds([]); // Clear selection on data change
} catch (error) {
- console.error("Failed to fetch transactions data", error);
+ toast.error("Failed to fetch transactions data");
} finally {
setLoading(false);
setIsFiltering(false);
@@ -187,7 +188,7 @@ const TransactionsPage = () => {
fetchData();
handleCloseTransactionModal();
} catch (error) {
- console.error("Failed to save transaction", error);
+ toast.error("Failed to save transaction");
}
};
@@ -206,7 +207,7 @@ const TransactionsPage = () => {
return updatedTransactions;
});
} catch (error) {
- console.error("Failed to delete transaction", error);
+ toast.error("Failed to delete transaction");
}
}
};
@@ -249,7 +250,7 @@ const TransactionsPage = () => {
await api.delete('/transactions/category', { data: { categoryToDelete } });
fetchData();
} catch (error) {
- console.error("Failed to delete category", error);
+ toast.error("Failed to delete category");
}
}
};