From b88bcd841a0da063596af580a1d351673b323751 Mon Sep 17 00:00:00 2001 From: valanm22 Date: Wed, 1 Oct 2025 20:29:50 +0530 Subject: [PATCH] feat: Add toast notifications --- frontend/package-lock.json | 26 ++++++++++++++- frontend/package.json | 1 + frontend/src/App.jsx | 42 ++++++++++++++----------- frontend/src/contexts/AuthContext.jsx | 7 +++-- frontend/src/pages/DashboardPage.jsx | 5 +-- frontend/src/pages/ReceiptsPage.jsx | 3 +- frontend/src/pages/TransactionsPage.jsx | 11 ++++--- 7 files changed, 64 insertions(+), 31 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 8752e26..afd4f41 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -13,6 +13,7 @@ "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" }, "devDependencies": { @@ -2772,7 +2773,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": { @@ -3592,6 +3592,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", @@ -4999,6 +5007,22 @@ "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-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", diff --git a/frontend/package.json b/frontend/package.json index 9bada8f..744b8f8 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -16,6 +16,7 @@ "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" }, "devDependencies": { diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 403a78f..c826e75 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -3,33 +3,37 @@ import { Routes, Route } from 'react-router-dom'; import LoginPage from './pages/LoginPage'; import RegisterPage from './pages/RegisterPage'; import DashboardPage from './pages/DashboardPage'; -import {TransactionsPage} from './pages/TransactionsPage'; +import { TransactionsPage } from './pages/TransactionsPage'; import ReceiptsPage from './pages/ReceiptsPage'; import WelcomePage from './pages/WelcomePage'; import ProtectedRoute from './components/ProtectedRoute'; import Layout from './components/Layout'; +import { Toaster } from 'react-hot-toast' function App() { return ( - - {/* Public Routes */} - } /> - } /> - } /> + <> + + {/* Public Routes */} + } /> + } /> + } /> - {/* Protected Routes Wrapper */} - - - - } - > - } /> - } /> - } /> - - + {/* Protected Routes Wrapper */} + + + + } + > + } /> + } /> + } /> + + + + ); } diff --git a/frontend/src/contexts/AuthContext.jsx b/frontend/src/contexts/AuthContext.jsx index 01a83f2..83477eb 100644 --- a/frontend/src/contexts/AuthContext.jsx +++ b/frontend/src/contexts/AuthContext.jsx @@ -1,6 +1,7 @@ import React, { createContext, useState, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; import api from '../api/axios'; +import toast from 'react-hot-toast'; const AuthContext = createContext(); @@ -22,7 +23,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); @@ -46,7 +47,7 @@ export const AuthProvider = ({ children }) => { navigate('/dashboard'); } catch (error) { - console.error('Login failed', error.response?.data); + toast.error('Login failed'); } }; @@ -62,7 +63,7 @@ export const AuthProvider = ({ children }) => { navigate('/dashboard'); } catch (error) { - console.error('Signup failed', error.response?.data); + toast.error('Signup failed'); 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 f96a78c..b3bf766 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'; // A reusable card component for the dashboard summary const SummaryCard = ({ title, value, bgColor, loading }) => { @@ -54,7 +55,7 @@ const DashboardPage = () => { setCategories(categoriesRes.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); } @@ -73,7 +74,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 8e7b9ff..8834a32 100644 --- a/frontend/src/pages/TransactionsPage.jsx +++ b/frontend/src/pages/TransactionsPage.jsx @@ -4,6 +4,7 @@ import TransactionModal from '../components/TransactionModal'; import ManageCategoriesModal from '../components/ManageCategoriesModal'; import Spinner from '../components/Spinner'; import useCurrency from '../hooks/useCurrency'; +import toast from 'react-hot-toast'; const handleExportCSV = async () => { try { @@ -20,7 +21,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."); } }; @@ -48,7 +49,7 @@ const TransactionsPage = () => { setTotalPages(transactionsRes.data.totalPages); setCategories(categoriesRes.data); } catch (error) { - console.error("Failed to fetch transactions data", error); + toast.error("Failed to fetch transactions data"); } finally { setLoading(false); } @@ -75,7 +76,7 @@ const TransactionsPage = () => { fetchData(); handleCloseTransactionModal(); } catch (error) { - console.error("Failed to save transaction", error); + toast.error("Failed to save transaction"); } }; @@ -85,7 +86,7 @@ const TransactionsPage = () => { await api.delete(`/transactions/${id}`); fetchData(); } catch (error) { - console.error("Failed to delete transaction", error); + toast.error("Failed to delete transaction"); } } }; @@ -100,7 +101,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"); } } };