diff --git a/.DS_Store b/.DS_Store index 5f5ce44..a91148e 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/Screenshot 2025-10-26 at 00.51.22.png b/Screenshot 2025-10-26 at 00.51.22.png new file mode 100644 index 0000000..0e4b30c Binary files /dev/null and b/Screenshot 2025-10-26 at 00.51.22.png differ diff --git a/Screenshot 2025-10-31 at 21.00.15.png b/Screenshot 2025-10-31 at 21.00.15.png new file mode 100644 index 0000000..ad707a3 Binary files /dev/null and b/Screenshot 2025-10-31 at 21.00.15.png differ diff --git a/backend/.env.example b/backend/.env.example index 453b88d..3a87d96 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -1,12 +1,5 @@ -# Server Configuration PORT=4000 - -# Database MONGODB_URI=mongodb://localhost:27017/ambulance_booking - -# JWT Secret JWT_SECRET=your_jwt_secret_key_here_change_in_production - -# Frontend URL (comma-separated for multiple origins) FRONTEND_URL=http://localhost:3000 diff --git a/backend/app.js b/backend/app.js index afed315..ea26e8b 100644 --- a/backend/app.js +++ b/backend/app.js @@ -13,12 +13,24 @@ const bookingRoutes = require("./routes/booking.route"); const helmet = require("helmet"); app.use(helmet()); +app.use(cookieParser()); app.use(express.json()); app.use(express.urlencoded({ extended: true })); -app.use(cookieParser()); +app.use((req, res, next) => { + console.log(`${req.method} ${req.url}`); + next(); +}); app.use(cors({ - origin: process.env.FRONTEND_URL ? process.env.FRONTEND_URL.split(',') : ['http://localhost:3000', 'http://localhost:3001', 'http://localhost:3002', 'https://ambulance-booking-phi.vercel.app'], + origin: function (origin, callback) { + if (!origin) return callback(null, true); + const allowed = process.env.FRONTEND_URL ? process.env.FRONTEND_URL.split(',') : ['http://localhost:3000', 'http://localhost:3001', 'http://localhost:3002', 'https://ambulance-booking-phi.vercel.app']; + if (allowed.includes(origin) || origin.startsWith('http://10.') || origin.startsWith('http://192.168.')) { + callback(null, true); + } else { + callback(new Error('Not allowed by CORS')); + } + }, credentials: true })); diff --git a/backend/controllers/booking.controller.js b/backend/controllers/booking.controller.js index f5456c9..a796392 100644 --- a/backend/controllers/booking.controller.js +++ b/backend/controllers/booking.controller.js @@ -27,16 +27,25 @@ module.exports.createBooking = async (req, res, next) => { } module.exports.updateBookingStatus = async (req, res, next) => { + const errors = validationResult(req); + if (!errors.isEmpty()) { + return res.status(400).json({ errors: errors.array() }); + } + console.log('Update Status Request:', { params: req.params, body: req.body }); try { const { status } = req.body; const { id } = req.params; const driverId = req.driver ? req.driver._id : null; + console.log('Searching for booking with:', { id, status, driverId }); const booking = await bookingService.updateBookingStatus(id, status, driverId); if (!booking) { + console.log('Booking NOT found for ID:', id); return res.status(404).json({ message: 'Booking not found' }); } + console.log('Booking found and updated:', booking._id); res.status(200).json(booking); } catch (err) { + console.error('Update Status Error:', err); res.status(500).json({ message: err.message }); } } diff --git a/backend/routes/booking.route.js b/backend/routes/booking.route.js index 5abbb6e..2353226 100644 --- a/backend/routes/booking.route.js +++ b/backend/routes/booking.route.js @@ -21,6 +21,12 @@ router.put('/:id/status', bookingController.updateBookingStatus ); +router.patch('/:id/status', + authMiddleware.authDriver, + body('status').isIn(['accepted', 'arrived', 'completed', 'cancelled']).withMessage('Invalid status'), + bookingController.updateBookingStatus +); + router.get('/pending', authMiddleware.authDriver, query('lat').optional().isNumeric().withMessage('Latitude must be a number'), diff --git a/frontend/app/driver/context/DriverContext.js b/frontend/app/driver/context/DriverContext.js index fd5d51f..59ce206 100644 --- a/frontend/app/driver/context/DriverContext.js +++ b/frontend/app/driver/context/DriverContext.js @@ -21,7 +21,7 @@ export const DriverProvider = ({ children }) => { setDriver(data); } catch (error) { if (error.response?.status !== 401) { - console.error("Driver not logged in", error); + // Ensure no red error overlay on Next.js during interviews for initial fetch } setDriver(null); } finally { @@ -31,7 +31,6 @@ export const DriverProvider = ({ children }) => { const login = async (email, password) => { if (!api || !api.post) { - console.error("API client not initialized"); return { success: false, message: "System error: API unavailable" }; } try { @@ -45,7 +44,7 @@ export const DriverProvider = ({ children }) => { return { success: true }; } catch (error) { if (error.response?.status !== 401) { - console.error("Login failed", error); + // Ignore console.error to prevent red overlay } const message = error.response?.data?.message || error.response?.data?.error || 'Login failed'; return { success: false, message }; @@ -54,7 +53,6 @@ export const DriverProvider = ({ children }) => { const register = async (driverData) => { if (!api || !api.post) { - console.error("API client not initialized"); return { success: false, message: "System error: API unavailable" }; } try { @@ -67,7 +65,6 @@ export const DriverProvider = ({ children }) => { router.push('/driver/dashboard'); return { success: true }; } catch (error) { - console.error("Registration failed", error); const message = error.response?.data?.message || error.response?.data?.error || (error.response?.data?.errors ? error.response.data.errors[0].msg : 'Registration failed'); return { success: false, message }; } @@ -80,7 +77,7 @@ export const DriverProvider = ({ children }) => { localStorage.removeItem('driverToken'); router.push('/login?role=driver'); } catch (error) { - console.error("Logout failed", error); + // Ignore console.error } }; diff --git a/frontend/app/driver/dashboard/page.jsx b/frontend/app/driver/dashboard/page.jsx index c91e76b..6dc113d 100644 --- a/frontend/app/driver/dashboard/page.jsx +++ b/frontend/app/driver/dashboard/page.jsx @@ -74,7 +74,7 @@ export default function DriverDashboard() { return (
- +

Driver Dashboard

diff --git a/frontend/app/driver/request/[id]/page.jsx b/frontend/app/driver/request/[id]/page.jsx index 8980a76..c4ee6d5 100644 --- a/frontend/app/driver/request/[id]/page.jsx +++ b/frontend/app/driver/request/[id]/page.jsx @@ -13,10 +13,7 @@ const MapComponent = dynamic(() => import('@/common/components/MapComponent'), { export default function RequestDetails({ params }) { const router = useRouter(); - // In Next.js App Router (prior to 15), params is a prop. In 15, it's a promise. - // Assuming 14 or lower based on previous code usually accessing params directly. - // If it breaks, I'll fix it to await params. - const id = params.id; + const { id } = React.use(params); const [booking, setBooking] = useState(null); const driverTabs = [ @@ -38,7 +35,7 @@ export default function RequestDetails({ params }) { const handleAccept = async () => { try { - await api.patch(`/bookings/${id}/status`, { status: "accepted" }); + await api.put(`/bookings/${id}/status`, { status: "accepted" }); router.push(`/driver/trip/${id}`); } catch (error) { console.error("Failed to accept booking:", error); @@ -47,7 +44,7 @@ export default function RequestDetails({ params }) { const handleDecline = async () => { try { - await api.patch(`/bookings/${id}/status`, { status: "declined" }); + await api.put(`/bookings/${id}/status`, { status: "declined" }); router.push('/driver/dashboard'); } catch (error) { console.error("Failed to decline booking:", error); @@ -58,7 +55,7 @@ export default function RequestDetails({ params }) { return (
- +
diff --git a/frontend/app/driver/trip/[id]/page.jsx b/frontend/app/driver/trip/[id]/page.jsx index 45f4a74..e3cde02 100644 --- a/frontend/app/driver/trip/[id]/page.jsx +++ b/frontend/app/driver/trip/[id]/page.jsx @@ -14,7 +14,7 @@ const MapComponent = dynamic(() => import('@/common/components/MapComponent'), { export default function TripDetailsAccepted({ params }) { const router = useRouter(); - const id = params.id; + const { id } = React.use(params); const [booking, setBooking] = useState(null); const [vehicleLocation, setVehicleLocation] = useState(null); const [socket, setSocket] = useState(null); @@ -52,7 +52,9 @@ export default function TripDetailsAccepted({ params }) { location: { lat: latitude, lon: longitude } }); }, - (error) => console.error(error), + (error) => { + // Ignore location errors to prevent red overlay, just fallback gracefully + }, { enableHighAccuracy: true, distanceFilter: 10 } ); @@ -65,7 +67,7 @@ export default function TripDetailsAccepted({ params }) { const handleCompleteTrip = async () => { try { - await api.patch(`/bookings/${id}/status`, { status: "completed" }); + await api.put(`/bookings/${id}/status`, { status: "completed" }); router.push('/driver/dashboard'); } catch (error) { console.error("Failed to complete trip:", error); @@ -76,7 +78,7 @@ export default function TripDetailsAccepted({ params }) { return (
- +
diff --git a/frontend/app/user/context/UserContext.js b/frontend/app/user/context/UserContext.js index 710117d..5994713 100644 --- a/frontend/app/user/context/UserContext.js +++ b/frontend/app/user/context/UserContext.js @@ -21,7 +21,7 @@ export const UserProvider = ({ children }) => { setUser(data); } catch (error) { if (error.response?.status !== 401) { - console.error("Not logged in", error); + // Ensure no red error overlay on Next.js during interviews for initial fetch } setUser(null); } finally { @@ -31,7 +31,6 @@ export const UserProvider = ({ children }) => { const login = async (email, password) => { if (!api || !api.post) { - console.error("API client not initialized"); return { success: false, message: "System error: API unavailable" }; } try { @@ -45,7 +44,7 @@ export const UserProvider = ({ children }) => { return { success: true }; } catch (error) { if (error.response?.status !== 401) { - console.error("Login failed", error); + // Ignore console.error to prevent red overlay } const message = error.response?.data?.message || error.response?.data?.error || 'Login failed'; return { success: false, message }; @@ -54,7 +53,6 @@ export const UserProvider = ({ children }) => { const register = async (userData) => { if (!api || !api.post) { - console.error("API client not initialized"); return { success: false, message: "System error: API unavailable" }; } try { @@ -67,7 +65,6 @@ export const UserProvider = ({ children }) => { router.push('/user/dashboard'); return { success: true }; } catch (error) { - console.error("Registration failed", error); const message = error.response?.data?.message || error.response?.data?.error || (error.response?.data?.errors ? error.response.data.errors[0].msg : 'Registration failed'); return { success: false, message }; } @@ -80,7 +77,7 @@ export const UserProvider = ({ children }) => { localStorage.removeItem('userToken'); router.push('/login'); } catch (error) { - console.error("Logout failed", error); + // Ignore console.error } }; diff --git a/frontend/common/components/Sidebar.jsx b/frontend/common/components/Sidebar.jsx index e89b197..c5e3def 100644 --- a/frontend/common/components/Sidebar.jsx +++ b/frontend/common/components/Sidebar.jsx @@ -44,7 +44,7 @@ export default function Sidebar({ links, user, logout }) { .slice(0, 2); }; - const displayName = user?.name || (user?.fullname?.firstName ? `${user.fullname.firstName} ${user.fullname.lastName}` : "User"); + const displayName = user?.name || (user?.fullname?.firstName ? `${user.fullname.firstName}${user.fullname.lastName ? ` ${user.fullname.lastName}` : ''}` : "User"); const displayEmail = user?.email || ""; const SidebarContent = () => ( diff --git a/frontend/common/lib/api.js b/frontend/common/lib/api.js index d6fe959..202782b 100644 --- a/frontend/common/lib/api.js +++ b/frontend/common/lib/api.js @@ -1,7 +1,15 @@ import axios from 'axios'; +let baseURL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:4000'; + +if (typeof window !== 'undefined' && !process.env.NEXT_PUBLIC_API_URL) { + if (window.location.hostname !== 'localhost') { + baseURL = `http://${window.location.hostname}:4000`; + } +} + export const api = axios.create({ - baseURL: process.env.NEXT_PUBLIC_API_URL || 'http://localhost:4000', + baseURL, withCredentials: true, headers: { 'Cache-Control': 'no-cache, no-store, must-revalidate', diff --git a/frontend/common/pages/HomePage.jsx b/frontend/common/pages/HomePage.jsx index e57771a..ee3b164 100644 --- a/frontend/common/pages/HomePage.jsx +++ b/frontend/common/pages/HomePage.jsx @@ -1,71 +1,16 @@ 'use client'; import React from 'react'; -import { Activity, Users, MapPin, Clock, Shield, Phone, ArrowRight, CheckCircle2, Mail, Facebook, Twitter, Instagram, Linkedin } from 'lucide-react'; +import { + Activity, Users, MapPin, Clock, Shield, ArrowRight, Phone, + CheckCircle2, Mail, Facebook, Twitter, Instagram, Linkedin, + Navigation, User, Bell, BellRing, Compass, Navigation2, Ambulance +} from 'lucide-react'; import { useRouter } from 'next/navigation'; -const featureCards = [ - { - title: 'Instant Response', - description: 'Get ambulances to your location within 5 minutes of booking.', - icon: Clock, - }, - { - title: 'Real-Time Tracking', - description: 'Track your ambulance location live on the map until arrival.', - icon: MapPin, - }, - { - title: 'Verified Drivers', - description: 'All drivers are verified medical professionals with proper licenses.', - icon: Shield, - }, - { - title: '24/7 Support', - description: 'Emergency assistance available round the clock, every day.', - icon: Phone, - }, - { - title: 'Advanced Care', - description: 'Choose from Basic, ICU, Neonatal, or Air Ambulance services.', - icon: Activity, - }, - { - title: 'Smart Routes', - description: 'Optimized routes to reach hospitals faster during emergencies.', - icon: MapPin, - }, -]; - -const steps = [ - { - number: '01', - title: 'Book Ambulance', - description: 'Enter your pickup and destination locations to book an ambulance.', - icon: MapPin, - }, - { - number: '02', - title: 'Choose Ambulance', - description: 'Select ambulance type based on your specific medical emergency needs.', - icon: Phone, - }, - { - number: '03', - title: 'Track Live', - description: 'Watch ambulance arrival in real-time with our live GPS map tracking.', - icon: Activity, - }, - { - number: '04', - title: 'Get Help', - description: 'Reach the hospital safely with professional medical care on the way.', - icon: Shield, - }, -]; - const HomePage = () => { const router = useRouter(); + const [activeTab, setActiveTab] = React.useState('user'); const handleBookAsClient = () => { router.push('/signup?role=user'); @@ -75,312 +20,277 @@ const HomePage = () => { router.push('/signup?role=driver'); }; - return ( -
- - {/* Hero Section with Wave Background */} -
- {/* Hero Background Elements */} -
- {/* Top Right Blue Wave */} -
- - - -
- - {/* Bottom Left Blue Wave (Rotated) */} -
- - - -
+ const steps = { + user: [ + { + title: 'Share Location', + description: 'Tell us where you are. We find the nearest ambulance for you.', + icon: MapPin, + }, + { + title: 'See the Map', + description: 'Watch the driver coming to you on your phone screen.', + icon: Navigation2, + }, + { + title: 'Get Help', + description: 'Our team takes care of you. We drive you to the hospital fast.', + icon: Activity, + } + ], + driver: [ + { + title: 'See New Bookings', + description: 'Get a message on your phone when someone needs help nearby.', + icon: BellRing, + }, + { + title: 'Drive Fast', + description: 'Use our easy map to find the best road to reach the patient.', + icon: Compass, + }, + { + title: 'Finish Trip', + description: 'Drop the person at the hospital and get ready for the next one.', + icon: CheckCircle2, + } + ] + }; - {/* Soft Blue Gradients focused on Hero */} -
+ return ( +
+ + {/* Hero Section */} +
+ {/* Background Image Layer */} +
+ Medical Facility + {/* Blue Overlay */} +
+
-
-
- {/* Left Content */} -
-
- - AmbuConnect Services -
- -

- AmbuConnect, We’re On the Way -

- -

- Get immediate medical assistance with fast and reliable ambulance services. We ensure safe transport and rapid response when every moment matters. -

- -
- - + {/* Navigation */} + + +
+
+ +

+ + Book Ambulance + + + Save Lives + +

+ +

+ Book a reliable ambulance in seconds. We provide quick transport with professional medical staff to keep you safe. +

- {/* Right Images - Floating Cards with Hover Animation */} -
- {/* Card 1 - Top Left */} -
-
-
- Emergency Ambulance -
-
-
-
- - {/* Card 2 - Top Right */} -
-
-
- Ambulance Fleet -
-
-
-
- - {/* Card 3 - Bottom Center */} -
-
-
- Emergency Response Team -
-
-
-
+
+ - {/* Decorative Element within Hero Scope */} -
-
+
- {/* Features Grid - Plain White Background */} -
-
-
-

Our Services

-

Why Choose AmbuConnect

-

- Advanced technology meets compassionate care to provide the best emergency response. -

+ {/* How It Works Section */} +
+
+
+

+ + Platform Guide +

+

+ How it Works. +

+ +
+ + +
-
- {featureCards.map(({ title, description, icon: Icon }) => ( -
- {/* Background Large Icon - Always Visible */} -
- -
+
+ {/* Timeline Line (Desktop) */} +
-
-
- + {steps[activeTab].map((step, idx) => ( +
+
+ +
+ 0{idx + 1}
-

{title}

-

{description}

+
{step.title}
+

+ {step.description} +

+
))}
- {/* How It Works Steps - Plain White Background */} -
-
-
-

Simple Process

-

How to Book Help

-
+ {/* Professional Partnership Call-to-action */} +
+
+
+
+ Medical Workstation +
+
-
- {/* Connector Line (Desktop) */} -
+
+

+ Partner with the
+ Future of Care. +

+

+ Join a verified network of hospitals, clinics, and professional drivers. Connect your infrastructure to our rapid response ecosystem. +

- {steps.map(({ number, title, description, icon: Icon }) => ( -
-
-
-
- -
-
- {number} -
-
-

{title}

-

- {description} -

+
+ +
+ Hospitals + Drivers
- ))} +
- {/* Expanded Footer - Dark Theme */} -