diff --git a/client/src/App.jsx b/client/src/App.jsx
index 16ce59d..72744b4 100644
--- a/client/src/App.jsx
+++ b/client/src/App.jsx
@@ -1,6 +1,7 @@
import { BrowserRouter as Router, Routes, Route, useLocation } from 'react-router-dom';
import Navbar from './components/Navbar';
import Footer from './components/Footer';
+import Toast from './components/Toast';
import LocationBanner from './components/LocationBanner';
import Home from './pages/Home';
import Login from './pages/Login';
@@ -57,6 +58,25 @@ function AppContent() {
function App() {
return (
+
+
+
+
+
+
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ {/* TODO: Add more routes here */}
+
+
+
+
);
diff --git a/client/src/components/LocationBanner.jsx b/client/src/components/LocationBanner.jsx
index 977cf26..2efa9af 100644
--- a/client/src/components/LocationBanner.jsx
+++ b/client/src/components/LocationBanner.jsx
@@ -1,5 +1,6 @@
import { useLocation } from '../context/LocationContext';
import { useState } from 'react';
+import useToast from '../hooks/useToast';
/**
* LocationBanner
@@ -12,11 +13,16 @@ import { useState } from 'react';
const LocationBanner = () => {
const { coords, loading, error, permissionDenied, retry } = useLocation();
const [retryLoading, setRetryLoading] = useState(false);
+ const { showToast } = useToast();
const handleRetry = async () => {
setRetryLoading(true);
try {
await retry();
+ showToast('Location detected successfully!', 'success');
+ } catch (error) {
+ console.error('Retry failed:', error);
+ showToast('Failed to detect location. Please try again.', 'error');
} catch (error) {
console.error('Retry failed:', error);
} finally {
diff --git a/client/src/components/Toast.jsx b/client/src/components/Toast.jsx
new file mode 100644
index 0000000..e92c8c3
--- /dev/null
+++ b/client/src/components/Toast.jsx
@@ -0,0 +1,20 @@
+import useToast from '../hooks/useToast';
+
+const Toast = () => {
+ const { toasts } = useToast();
+
+ return (
+
+ {toasts.map(toast => (
+
+ {toast.message}
+
+ ))}
+
+ );
+};
+
+export default Toast;
diff --git a/client/src/context/ToastContext.jsx b/client/src/context/ToastContext.jsx
new file mode 100644
index 0000000..5634443
--- /dev/null
+++ b/client/src/context/ToastContext.jsx
@@ -0,0 +1,33 @@
+import { createContext, useState, useCallback } from 'react';
+
+const ToastContext = createContext(null);
+
+export const ToastProvider = ({ children }) => {
+ const [toasts, setToasts] = useState([]);
+
+ const showToast = useCallback((message, type = 'success') => {
+ const id = Math.random().toString(36).substr(2, 9);
+ const toast = { id, message, type };
+
+ setToasts(prev => [...prev, toast]);
+
+ // Auto-hide after 2.5 seconds
+ setTimeout(() => {
+ setToasts(prev => prev.filter(t => t.id !== id));
+ }, 2500);
+
+ return id;
+ }, []);
+
+ const removeToast = useCallback((id) => {
+ setToasts(prev => prev.filter(t => t.id !== id));
+ }, []);
+
+ return (
+
+ {children}
+
+ );
+};
+
+export default ToastContext;
diff --git a/client/src/hooks/useToast.js b/client/src/hooks/useToast.js
new file mode 100644
index 0000000..3e88a53
--- /dev/null
+++ b/client/src/hooks/useToast.js
@@ -0,0 +1,12 @@
+import { useContext } from 'react';
+import ToastContext from '../context/ToastContext';
+
+const useToast = () => {
+ const ctx = useContext(ToastContext);
+ if (!ctx) {
+ throw new Error('useToast must be used inside ');
+ }
+ return ctx;
+};
+
+export default useToast;
diff --git a/client/src/index.css b/client/src/index.css
index d9a5583..3f42b9e 100644
--- a/client/src/index.css
+++ b/client/src/index.css
@@ -31,6 +31,50 @@
animation: spin 1.5s linear infinite;
}
+/* Toast notifications */
+@keyframes slide-in {
+ from {
+ transform: translateX(400px);
+ opacity: 0;
+ }
+ to {
+ transform: translateX(0);
+ opacity: 1;
+ }
+}
+
+@keyframes slide-out {
+ from {
+ transform: translateX(0);
+ opacity: 1;
+ }
+ to {
+ transform: translateX(400px);
+ opacity: 0;
+ }
+}
+
+.animate-slide-in {
+ animation: slide-in 0.3s ease-out forwards;
+}
+
+.toast {
+ font-weight: 500;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
+.toast-success {
+ background-color: #10b981;
+ border-left: 4px solid #059669;
+}
+
+.toast-error {
+ background-color: #ef4444;
+ border-left: 4px solid #dc2626;
+}
+
/* Mobile responsiveness */
@media (max-width: 640px) {
.grid-cols-1 {
diff --git a/client/src/main.jsx b/client/src/main.jsx
index 86a24e8..0f7efed 100644
--- a/client/src/main.jsx
+++ b/client/src/main.jsx
@@ -2,11 +2,17 @@ import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.jsx'
import { LocationProvider } from './context/LocationContext.jsx'
+import { ToastProvider } from './context/ToastContext.jsx'
import { AuthProvider } from './context/AuthContext.jsx'
import './index.css'
ReactDOM.createRoot(document.getElementById('root')).render(
+
+
+
+
+
diff --git a/client/src/pages/Bookings.jsx b/client/src/pages/Bookings.jsx
index efb3dc5..dba0a2e 100644
--- a/client/src/pages/Bookings.jsx
+++ b/client/src/pages/Bookings.jsx
@@ -1,5 +1,6 @@
import { useEffect, useMemo, useState } from "react";
import { Link } from "react-router-dom";
+import useToast from "../hooks/useToast";
import EmptyState from "../components/EmptyState";
const Bookings = () => {
@@ -9,6 +10,7 @@ const Bookings = () => {
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [actionLoading, setActionLoading] = useState({}); // For individual button loading
+ const { showToast } = useToast();
import { useState } from "react";
const Bookings = () => {
@@ -116,6 +118,10 @@ const StarRating = ({ rating, onRatingChange, size = "md" }) => {
b.id === id ? { ...b, status: "Cancelled" } : b
);
setBookings(updated);
+ showToast('Booking cancelled successfully.', 'success');
+ } catch (error) {
+ console.error('Cancel failed:', error);
+ showToast('Failed to cancel booking. Please try again.', 'error');
} catch (error) {
console.error('Cancel failed:', error);
} finally {
@@ -138,6 +144,10 @@ const StarRating = ({ rating, onRatingChange, size = "md" }) => {
setActiveReview(null);
setRating(0);
setComment("");
+ showToast('Review submitted successfully!', 'success');
+ } catch (error) {
+ console.error('Review submit failed:', error);
+ showToast('Failed to submit review. Please try again.', 'error');
} catch (error) {
console.error('Review submit failed:', error);
} finally {
diff --git a/client/src/pages/Login.jsx b/client/src/pages/Login.jsx
index 3fe67d5..9cf2e3c 100644
--- a/client/src/pages/Login.jsx
+++ b/client/src/pages/Login.jsx
@@ -1,4 +1,9 @@
import { useState } from "react";
+import useToast from "../hooks/useToast";
+
+const Login = () => {
+ const [loading, setLoading] = useState(false);
+ const { showToast } = useToast();
const Login = () => {
const [loading, setLoading] = useState(false);
@@ -9,6 +14,10 @@ const Login = () => {
try {
// TODO: Add authentication logic and API connection
await new Promise(resolve => setTimeout(resolve, 1000)); // Simulate API call
+ showToast('Login successful! Welcome back.', 'success');
+ } catch (error) {
+ console.error('Login failed:', error);
+ showToast('Login failed. Please try again.', 'error');
} catch (error) {
console.error('Login failed:', error);
import { useState } from 'react';
diff --git a/client/src/pages/Profile.jsx b/client/src/pages/Profile.jsx
index ef41fb4..336a761 100644
--- a/client/src/pages/Profile.jsx
+++ b/client/src/pages/Profile.jsx
@@ -1,4 +1,9 @@
import { useState } from "react";
+import useToast from "../hooks/useToast";
+
+const Profile = () => {
+ const [loading, setLoading] = useState(false);
+ const { showToast } = useToast();
const Profile = () => {
const [loading, setLoading] = useState(false);
@@ -8,6 +13,10 @@ const Profile = () => {
try {
// TODO: Handle API update logic
await new Promise(resolve => setTimeout(resolve, 1000)); // Simulate API call
+ showToast('Profile updated successfully!', 'success');
+ } catch (error) {
+ console.error('Save failed:', error);
+ showToast('Failed to save changes. Please try again.', 'error');
} catch (error) {
console.error('Save failed:', error);
} finally {
diff --git a/client/src/pages/Register.jsx b/client/src/pages/Register.jsx
index e07c87f..842c6ee 100644
--- a/client/src/pages/Register.jsx
+++ b/client/src/pages/Register.jsx
@@ -1,4 +1,20 @@
import { useState } from "react";
+import useToast from "../hooks/useToast";
+
+const Register = () => {
+ const [loading, setLoading] = useState(false);
+ const { showToast } = useToast();
+
+ const handleSubmit = async (e) => {
+ e.preventDefault();
+ setLoading(true);
+ try {
+ // TODO: Add registration logic and API connection
+ await new Promise(resolve => setTimeout(resolve, 1000)); // Simulate API call
+ showToast('Account created successfully!', 'success');
+ } catch (error) {
+ console.error('Registration failed:', error);
+ showToast('Registration failed. Please try again.', 'error');
import { useNavigate, Link } from "react-router-dom";
import { useAuth } from "../context/AuthContext";
import axios from "axios";
@@ -179,6 +195,9 @@ const Register = () => {
Join FixNearby and get started
+ {/* TODO: Add authentication logic and API connection */}
+