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"); } } };