├── .gitattributes ├── frontend ├── src │ ├── index.css │ ├── context │ │ ├── UserContext.jsx │ │ ├── SocketContext.jsx │ │ └── CapatainContext.jsx │ ├── main.jsx │ ├── pages │ │ ├── UserLogout.jsx │ │ ├── CaptainLogout.jsx │ │ ├── Start.jsx │ │ ├── UserProtectWrapper.jsx │ │ ├── CaptainProtectWrapper.jsx │ │ ├── CaptainRiding.jsx │ │ ├── Captainlogin.jsx │ │ ├── UserLogin.jsx │ │ ├── Riding.jsx │ │ ├── UserSignup.jsx │ │ ├── CaptainHome.jsx │ │ ├── CaptainSignup.jsx │ │ └── Home.jsx │ ├── App.css │ ├── components │ │ ├── LocationSearchPanel.jsx │ │ ├── CaptainDetails.jsx │ │ ├── LookingForDriver.jsx │ │ ├── LiveTracking.jsx │ │ ├── WaitingForDriver.jsx │ │ ├── ConfirmRide.jsx │ │ ├── RidePopUp.jsx │ │ ├── VehiclePanel.jsx │ │ ├── FinishRide.jsx │ │ └── ConfirmRidePopUp.jsx │ ├── App.jsx │ └── assets │ │ └── react.svg ├── postcss.config.js ├── vite.config.js ├── tailwind.config.js ├── index.html ├── README.md ├── package.json ├── eslint.config.js └── public │ └── vite.svg ├── Backend ├── db │ └── db.js ├── server.js ├── models │ ├── blacklistToken.model.js │ ├── ride.model.js │ ├── user.model.js │ └── captain.model.js ├── services │ ├── user.service.js │ ├── captain.service.js │ ├── maps.service.js │ └── ride.service.js ├── package.json ├── routes │ ├── maps.routes.js │ ├── user.routes.js │ ├── captain.routes.js │ └── ride.routes.js ├── app.js ├── controllers │ ├── map.controller.js │ ├── user.controller.js │ ├── captain.controller.js │ └── ride.controller.js ├── middlewares │ └── auth.middleware.js ├── socket.js └── README.md ├── .gitignore └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /frontend/src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; -------------------------------------------------------------------------------- /frontend/postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /frontend/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | // https://vite.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }) 8 | -------------------------------------------------------------------------------- /frontend/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | export default { 3 | content: [ 4 | "./index.html", 5 | "./src/**/*.{js,ts,jsx,tsx}", 6 | ], 7 | theme: { 8 | extend: {}, 9 | }, 10 | plugins: [], 11 | } 12 | 13 | -------------------------------------------------------------------------------- /Backend/db/db.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | 4 | function connectToDb() { 5 | mongoose.connect(process.env.DB_CONNECT 6 | ).then(() => { 7 | console.log('Connected to DB'); 8 | }).catch(err => console.log(err)); 9 | } 10 | 11 | 12 | module.exports = connectToDb; -------------------------------------------------------------------------------- /Backend/server.js: -------------------------------------------------------------------------------- 1 | const http = require('http'); 2 | const app = require('./app'); 3 | const { initializeSocket } = require('./socket'); 4 | const port = process.env.PORT || 3000; 5 | 6 | const server = http.createServer(app); 7 | 8 | initializeSocket(server); 9 | 10 | server.listen(port, () => { 11 | console.log(`Server is running on port ${port}`); 12 | }); -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | 3 | # Logs 4 | logs 5 | *.log 6 | npm-debug.log* 7 | yarn-debug.log* 8 | yarn-error.log* 9 | pnpm-debug.log* 10 | lerna-debug.log* 11 | 12 | node_modules 13 | dist 14 | dist-ssr 15 | *.local 16 | 17 | # Editor directories and files 18 | .vscode/* 19 | !.vscode/extensions.json 20 | .idea 21 | .DS_Store 22 | *.suo 23 | *.ntvs* 24 | *.njsproj 25 | *.sln 26 | *.sw? 27 | -------------------------------------------------------------------------------- /frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Uber Clone - Lakshay 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Backend/models/blacklistToken.model.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const blacklistTokenSchema = new mongoose.Schema({ 4 | token: { 5 | type: String, 6 | required: true, 7 | unique: true 8 | }, 9 | createdAt: { 10 | type: Date, 11 | default: Date.now, 12 | expires: 86400 // 24 hours in seconds 13 | } 14 | }); 15 | 16 | module.exports = mongoose.model('BlacklistToken', blacklistTokenSchema); -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | # React + Vite 2 | 3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. 4 | 5 | Currently, two official plugins are available: 6 | 7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh 8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh 9 | -------------------------------------------------------------------------------- /Backend/services/user.service.js: -------------------------------------------------------------------------------- 1 | const userModel = require('../models/user.model'); 2 | 3 | 4 | module.exports.createUser = async ({ 5 | firstname, lastname, email, password 6 | }) => { 7 | if (!firstname || !email || !password) { 8 | throw new Error('All fields are required'); 9 | } 10 | const user = userModel.create({ 11 | fullname: { 12 | firstname, 13 | lastname 14 | }, 15 | email, 16 | password 17 | }) 18 | 19 | return user; 20 | } -------------------------------------------------------------------------------- /Backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "backend", 3 | "version": "1.0.0", 4 | "main": "server.js", 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1" 7 | }, 8 | "keywords": [], 9 | "author": "", 10 | "license": "ISC", 11 | "description": "", 12 | "dependencies": { 13 | "axios": "^1.7.7", 14 | "bcrypt": "^5.1.1", 15 | "cookie-parser": "^1.4.7", 16 | "cors": "^2.8.5", 17 | "dotenv": "^16.4.7", 18 | "express": "^4.21.1", 19 | "express-validator": "^7.2.0", 20 | "jsonwebtoken": "^9.0.2", 21 | "mongoose": "^8.9.5", 22 | "socket.io": "^4.8.1" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /frontend/src/context/UserContext.jsx: -------------------------------------------------------------------------------- 1 | import React, { createContext, useState } from 'react' 2 | 3 | export const UserDataContext = createContext() 4 | 5 | 6 | const UserContext = ({ children }) => { 7 | 8 | const [ user, setUser ] = useState({ 9 | email: '', 10 | fullName: { 11 | firstName: '', 12 | lastName: '' 13 | } 14 | }) 15 | 16 | return ( 17 |
18 | 19 | {children} 20 | 21 |
22 | ) 23 | } 24 | 25 | export default UserContext -------------------------------------------------------------------------------- /frontend/src/main.jsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react' 2 | import { createRoot } from 'react-dom/client' 3 | import './index.css' 4 | import App from './App.jsx' 5 | import { BrowserRouter } from 'react-router-dom'; 6 | import UserContext from './context/UserContext.jsx'; 7 | import CaptainContext from './context/CapatainContext.jsx'; 8 | import SocketProvider from './context/SocketContext.jsx'; 9 | 10 | createRoot(document.getElementById('root')).render( 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | ) 23 | -------------------------------------------------------------------------------- /frontend/src/pages/UserLogout.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import axios from 'axios' 3 | import { useNavigate } from 'react-router-dom' 4 | 5 | export const UserLogout = () => { 6 | 7 | const token = localStorage.getItem('token') 8 | const navigate = useNavigate() 9 | 10 | axios.get(`${import.meta.env.VITE_API_URL}/users/logout`, { 11 | headers: { 12 | Authorization: `Bearer ${token}` 13 | } 14 | }).then((response) => { 15 | if (response.status === 200) { 16 | localStorage.removeItem('token') 17 | navigate('/login') 18 | } 19 | }) 20 | 21 | return ( 22 |
UserLogout
23 | ) 24 | } 25 | 26 | export default UserLogout 27 | -------------------------------------------------------------------------------- /Backend/services/captain.service.js: -------------------------------------------------------------------------------- 1 | const captainModel = require('../models/captain.model'); 2 | 3 | 4 | module.exports.createCaptain = async ({ 5 | firstname, lastname, email, password, color, plate, capacity, vehicleType 6 | }) => { 7 | if (!firstname || !email || !password || !color || !plate || !capacity || !vehicleType) { 8 | throw new Error('All fields are required'); 9 | } 10 | const captain = captainModel.create({ 11 | fullname: { 12 | firstname, 13 | lastname 14 | }, 15 | email, 16 | password, 17 | vehicle: { 18 | color, 19 | plate, 20 | capacity, 21 | vehicleType 22 | } 23 | }) 24 | 25 | return captain; 26 | } -------------------------------------------------------------------------------- /frontend/src/pages/CaptainLogout.jsx: -------------------------------------------------------------------------------- 1 | 2 | import React from 'react' 3 | import axios from 'axios' 4 | import { useNavigate } from 'react-router-dom' 5 | 6 | export const CaptainLogout = () => { 7 | const token = localStorage.getItem('captain-token') 8 | const navigate = useNavigate() 9 | 10 | axios.get(`${import.meta.env.VITE_API_URL}/captains/logout`, { 11 | headers: { 12 | Authorization: `Bearer ${token}` 13 | } 14 | }).then((response) => { 15 | if (response.status === 200) { 16 | localStorage.removeItem('captain-token') 17 | navigate('/captain-login') 18 | } 19 | }) 20 | 21 | return ( 22 |
CaptainLogout
23 | ) 24 | } 25 | 26 | export default CaptainLogout -------------------------------------------------------------------------------- /frontend/src/App.css: -------------------------------------------------------------------------------- 1 | #root { 2 | max-width: 1280px; 3 | margin: 0 auto; 4 | padding: 2rem; 5 | text-align: center; 6 | } 7 | 8 | .logo { 9 | height: 6em; 10 | padding: 1.5em; 11 | will-change: filter; 12 | transition: filter 300ms; 13 | } 14 | .logo:hover { 15 | filter: drop-shadow(0 0 2em #646cffaa); 16 | } 17 | .logo.react:hover { 18 | filter: drop-shadow(0 0 2em #61dafbaa); 19 | } 20 | 21 | @keyframes logo-spin { 22 | from { 23 | transform: rotate(0deg); 24 | } 25 | to { 26 | transform: rotate(360deg); 27 | } 28 | } 29 | 30 | @media (prefers-reduced-motion: no-preference) { 31 | a:nth-of-type(2) .logo { 32 | animation: logo-spin infinite 20s linear; 33 | } 34 | } 35 | 36 | .card { 37 | padding: 2em; 38 | } 39 | 40 | .read-the-docs { 41 | color: #888; 42 | } 43 | -------------------------------------------------------------------------------- /frontend/src/context/SocketContext.jsx: -------------------------------------------------------------------------------- 1 | 2 | import React, { createContext, useEffect } from 'react'; 3 | import { io } from 'socket.io-client'; 4 | 5 | export const SocketContext = createContext(); 6 | 7 | const socket = io(`${import.meta.env.VITE_BASE_URL}`); // Replace with your server URL 8 | 9 | const SocketProvider = ({ children }) => { 10 | useEffect(() => { 11 | // Basic connection logic 12 | socket.on('connect', () => { 13 | console.log('Connected to server'); 14 | }); 15 | 16 | socket.on('disconnect', () => { 17 | console.log('Disconnected from server'); 18 | }); 19 | 20 | }, []); 21 | 22 | 23 | 24 | return ( 25 | 26 | {children} 27 | 28 | ); 29 | }; 30 | 31 | export default SocketProvider; -------------------------------------------------------------------------------- /Backend/routes/maps.routes.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | const authMiddleware = require('../middlewares/auth.middleware'); 4 | const mapController = require('../controllers/map.controller'); 5 | const { query } = require('express-validator'); 6 | 7 | router.get('/get-coordinates', 8 | query('address').isString().isLength({ min: 3 }), 9 | authMiddleware.authUser, 10 | mapController.getCoordinates 11 | ); 12 | 13 | router.get('/get-distance-time', 14 | query('origin').isString().isLength({ min: 3 }), 15 | query('destination').isString().isLength({ min: 3 }), 16 | authMiddleware.authUser, 17 | mapController.getDistanceTime 18 | ) 19 | 20 | router.get('/get-suggestions', 21 | query('input').isString().isLength({ min: 3 }), 22 | authMiddleware.authUser, 23 | mapController.getAutoCompleteSuggestions 24 | ) 25 | 26 | 27 | 28 | module.exports = router; -------------------------------------------------------------------------------- /frontend/src/context/CapatainContext.jsx: -------------------------------------------------------------------------------- 1 | // frontend/src/context/CaptainContext.jsx 2 | import { createContext, useState, useContext } from 'react'; 3 | 4 | export const CaptainDataContext = createContext(); 5 | 6 | const CaptainContext = ({ children }) => { 7 | const [ captain, setCaptain ] = useState(null); 8 | const [ isLoading, setIsLoading ] = useState(false); 9 | const [ error, setError ] = useState(null); 10 | 11 | const updateCaptain = (captainData) => { 12 | setCaptain(captainData); 13 | }; 14 | 15 | const value = { 16 | captain, 17 | setCaptain, 18 | isLoading, 19 | setIsLoading, 20 | error, 21 | setError, 22 | updateCaptain 23 | }; 24 | 25 | return ( 26 | 27 | {children} 28 | 29 | ); 30 | }; 31 | 32 | export default CaptainContext; -------------------------------------------------------------------------------- /Backend/app.js: -------------------------------------------------------------------------------- 1 | const dotenv = require('dotenv'); 2 | dotenv.config(); 3 | const express = require('express'); 4 | const cors = require('cors'); 5 | const app = express(); 6 | const cookieParser = require('cookie-parser'); 7 | const connectToDb = require('./db/db'); 8 | const userRoutes = require('./routes/user.routes'); 9 | const captainRoutes = require('./routes/captain.routes'); 10 | const mapsRoutes = require('./routes/maps.routes'); 11 | const rideRoutes = require('./routes/ride.routes'); 12 | 13 | connectToDb(); 14 | 15 | app.use(cors()); 16 | app.use(express.json()); 17 | app.use(express.urlencoded({ extended: true })); 18 | app.use(cookieParser()); 19 | 20 | 21 | 22 | app.get('/', (req, res) => { 23 | res.send('Hello World'); 24 | }); 25 | 26 | app.use('/users', userRoutes); 27 | app.use('/captains', captainRoutes); 28 | app.use('/maps', mapsRoutes); 29 | app.use('/rides', rideRoutes); 30 | 31 | 32 | 33 | 34 | module.exports = app; 35 | 36 | -------------------------------------------------------------------------------- /frontend/src/pages/Start.jsx: -------------------------------------------------------------------------------- 1 | 2 | import React from 'react' 3 | import { Link } from 'react-router-dom' 4 | 5 | const Start = () => { 6 | return ( 7 |
8 |
9 | 10 |
11 |

Get Started with Uber

12 | Continue 13 |
14 |
15 |
16 | ) 17 | } 18 | 19 | export default Start -------------------------------------------------------------------------------- /Backend/routes/user.routes.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | const { body } = require("express-validator") 4 | const userController = require('../controllers/user.controller'); 5 | const authMiddleware = require('../middlewares/auth.middleware'); 6 | 7 | 8 | router.post('/register', [ 9 | body('email').isEmail().withMessage('Invalid Email'), 10 | body('fullname.firstname').isLength({ min: 3 }).withMessage('First name must be at least 3 characters long'), 11 | body('password').isLength({ min: 6 }).withMessage('Password must be at least 6 characters long') 12 | ], 13 | userController.registerUser 14 | ) 15 | 16 | router.post('/login', [ 17 | body('email').isEmail().withMessage('Invalid Email'), 18 | body('password').isLength({ min: 6 }).withMessage('Password must be at least 6 characters long') 19 | ], 20 | userController.loginUser 21 | ) 22 | 23 | router.get('/profile', authMiddleware.authUser, userController.getUserProfile) 24 | 25 | router.get('/logout', authMiddleware.authUser, userController.logoutUser) 26 | 27 | 28 | 29 | module.exports = router; -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "lint": "eslint .", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "@gsap/react": "^2.1.1", 14 | "@react-google-maps/api": "^2.20.3", 15 | "axios": "^1.7.9", 16 | "gsap": "^3.12.7", 17 | "react": "^18.3.1", 18 | "react-dom": "^18.3.1", 19 | "react-router-dom": "^6.28.1", 20 | "remixicon": "^4.5.0", 21 | "socket.io-client": "^4.8.1" 22 | }, 23 | "devDependencies": { 24 | "@eslint/js": "^9.13.0", 25 | "@types/react": "^18.3.12", 26 | "@types/react-dom": "^18.3.1", 27 | "@vitejs/plugin-react": "^4.3.3", 28 | "autoprefixer": "^10.4.20", 29 | "eslint": "^9.13.0", 30 | "eslint-plugin-react": "^7.37.2", 31 | "eslint-plugin-react-hooks": "^5.0.0", 32 | "eslint-plugin-react-refresh": "^0.4.14", 33 | "globals": "^15.11.0", 34 | "postcss": "^8.4.49", 35 | "tailwindcss": "^3.4.15", 36 | "vite": "^5.4.10" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /frontend/eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from '@eslint/js' 2 | import globals from 'globals' 3 | import react from 'eslint-plugin-react' 4 | import reactHooks from 'eslint-plugin-react-hooks' 5 | import reactRefresh from 'eslint-plugin-react-refresh' 6 | 7 | export default [ 8 | { ignores: ['dist'] }, 9 | { 10 | files: ['**/*.{js,jsx}'], 11 | languageOptions: { 12 | ecmaVersion: 2020, 13 | globals: globals.browser, 14 | parserOptions: { 15 | ecmaVersion: 'latest', 16 | ecmaFeatures: { jsx: true }, 17 | sourceType: 'module', 18 | }, 19 | }, 20 | settings: { react: { version: '18.3' } }, 21 | plugins: { 22 | react, 23 | 'react-hooks': reactHooks, 24 | 'react-refresh': reactRefresh, 25 | }, 26 | rules: { 27 | ...js.configs.recommended.rules, 28 | ...react.configs.recommended.rules, 29 | ...react.configs['jsx-runtime'].rules, 30 | ...reactHooks.configs.recommended.rules, 31 | 'react/jsx-no-target-blank': 'off', 32 | 'react-refresh/only-export-components': [ 33 | 'warn', 34 | { allowConstantExport: true }, 35 | ], 36 | }, 37 | }, 38 | ] 39 | -------------------------------------------------------------------------------- /frontend/src/components/LocationSearchPanel.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const LocationSearchPanel = ({ suggestions, setVehiclePanel, setPanelOpen, setPickup, setDestination, activeField }) => { 4 | 5 | const handleSuggestionClick = (suggestion) => { 6 | if (activeField === 'pickup') { 7 | setPickup(suggestion) 8 | } else if (activeField === 'destination') { 9 | setDestination(suggestion) 10 | } 11 | // setVehiclePanel(true) 12 | // setPanelOpen(false) 13 | } 14 | 15 | return ( 16 |
17 | {/* Display fetched suggestions */} 18 | { 19 | suggestions.map((elem, idx) => ( 20 |
handleSuggestionClick(elem)} className='flex gap-4 border-2 p-3 border-gray-50 active:border-black rounded-xl items-center my-2 justify-start'> 21 |

22 |

{elem}

23 |
24 | )) 25 | } 26 |
27 | ) 28 | } 29 | 30 | export default LocationSearchPanel -------------------------------------------------------------------------------- /Backend/models/ride.model.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | 4 | const rideSchema = new mongoose.Schema({ 5 | user: { 6 | type: mongoose.Schema.Types.ObjectId, 7 | ref: 'user', 8 | required: true 9 | }, 10 | captain: { 11 | type: mongoose.Schema.Types.ObjectId, 12 | ref: 'captain', 13 | }, 14 | pickup: { 15 | type: String, 16 | required: true, 17 | }, 18 | destination: { 19 | type: String, 20 | required: true, 21 | }, 22 | fare: { 23 | type: Number, 24 | required: true, 25 | }, 26 | 27 | status: { 28 | type: String, 29 | enum: [ 'pending', 'accepted', "ongoing", 'completed', 'cancelled' ], 30 | default: 'pending', 31 | }, 32 | 33 | duration: { 34 | type: Number, 35 | }, // in seconds 36 | 37 | distance: { 38 | type: Number, 39 | }, // in meters 40 | 41 | paymentID: { 42 | type: String, 43 | }, 44 | orderId: { 45 | type: String, 46 | }, 47 | signature: { 48 | type: String, 49 | }, 50 | 51 | otp: { 52 | type: String, 53 | select: false, 54 | required: true, 55 | }, 56 | }) 57 | 58 | module.exports = mongoose.model('ride', rideSchema); -------------------------------------------------------------------------------- /frontend/src/pages/UserProtectWrapper.jsx: -------------------------------------------------------------------------------- 1 | import React, { useContext, useEffect, useState } from 'react' 2 | import { UserDataContext } from '../context/UserContext' 3 | import { useNavigate } from 'react-router-dom' 4 | import axios from 'axios' 5 | 6 | const UserProtectWrapper = ({ 7 | children 8 | }) => { 9 | const token = localStorage.getItem('token') 10 | const navigate = useNavigate() 11 | const { user, setUser } = useContext(UserDataContext) 12 | const [ isLoading, setIsLoading ] = useState(true) 13 | 14 | useEffect(() => { 15 | if (!token) { 16 | navigate('/login') 17 | } 18 | 19 | axios.get(`${import.meta.env.VITE_BASE_URL}/users/profile`, { 20 | headers: { 21 | Authorization: `Bearer ${token}` 22 | } 23 | }).then(response => { 24 | if (response.status === 200) { 25 | setUser(response.data) 26 | setIsLoading(false) 27 | } 28 | }) 29 | .catch(err => { 30 | console.log(err) 31 | localStorage.removeItem('token') 32 | navigate('/login') 33 | }) 34 | }, [ token ]) 35 | 36 | if (isLoading) { 37 | return ( 38 |
Loading...
39 | ) 40 | } 41 | 42 | return ( 43 | <> 44 | {children} 45 | 46 | ) 47 | } 48 | 49 | export default UserProtectWrapper -------------------------------------------------------------------------------- /frontend/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Backend/models/user.model.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const bcrypt = require('bcrypt'); 3 | const jwt = require('jsonwebtoken'); 4 | 5 | 6 | const userSchema = new mongoose.Schema({ 7 | fullname: { 8 | firstname: { 9 | type: String, 10 | required: true, 11 | minlength: [ 3, 'First name must be at least 3 characters long' ], 12 | }, 13 | lastname: { 14 | type: String, 15 | minlength: [ 3, 'Last name must be at least 3 characters long' ], 16 | } 17 | }, 18 | email: { 19 | type: String, 20 | required: true, 21 | unique: true, 22 | minlength: [ 5, 'Email must be at least 5 characters long' ], 23 | }, 24 | password: { 25 | type: String, 26 | required: true, 27 | select: false, 28 | }, 29 | socketId: { 30 | type: String, 31 | }, 32 | }) 33 | 34 | userSchema.methods.generateAuthToken = function () { 35 | const token = jwt.sign({ _id: this._id }, process.env.JWT_SECRET, { expiresIn: '24h' }); 36 | return token; 37 | } 38 | 39 | userSchema.methods.comparePassword = async function (password) { 40 | return await bcrypt.compare(password, this.password); 41 | } 42 | 43 | userSchema.statics.hashPassword = async function (password) { 44 | return await bcrypt.hash(password, 10); 45 | } 46 | 47 | const userModel = mongoose.model('user', userSchema); 48 | 49 | 50 | module.exports = userModel; -------------------------------------------------------------------------------- /frontend/src/pages/CaptainProtectWrapper.jsx: -------------------------------------------------------------------------------- 1 | import React, { useContext, useEffect, useState } from 'react' 2 | import { CaptainDataContext } from '../context/CapatainContext' 3 | import { useNavigate } from 'react-router-dom' 4 | import axios from 'axios' 5 | 6 | const CaptainProtectWrapper = ({ 7 | children 8 | }) => { 9 | 10 | const token = localStorage.getItem('token') 11 | const navigate = useNavigate() 12 | const { captain, setCaptain } = useContext(CaptainDataContext) 13 | const [ isLoading, setIsLoading ] = useState(true) 14 | 15 | 16 | 17 | 18 | useEffect(() => { 19 | if (!token) { 20 | navigate('/captain-login') 21 | } 22 | 23 | axios.get(`${import.meta.env.VITE_BASE_URL}/captains/profile`, { 24 | headers: { 25 | Authorization: `Bearer ${token}` 26 | } 27 | }).then(response => { 28 | if (response.status === 200) { 29 | setCaptain(response.data.captain) 30 | setIsLoading(false) 31 | } 32 | }) 33 | .catch(err => { 34 | 35 | localStorage.removeItem('token') 36 | navigate('/captain-login') 37 | }) 38 | }, [ token ]) 39 | 40 | 41 | 42 | if (isLoading) { 43 | return ( 44 |
Loading...
45 | ) 46 | } 47 | 48 | 49 | 50 | return ( 51 | <> 52 | {children} 53 | 54 | ) 55 | } 56 | 57 | export default CaptainProtectWrapper -------------------------------------------------------------------------------- /Backend/routes/captain.routes.js: -------------------------------------------------------------------------------- 1 | const captainController = require('../controllers/captain.controller'); 2 | const express = require('express'); 3 | const router = express.Router(); 4 | const { body } = require("express-validator") 5 | const authMiddleware = require('../middlewares/auth.middleware'); 6 | 7 | 8 | router.post('/register', [ 9 | body('email').isEmail().withMessage('Invalid Email'), 10 | body('fullname.firstname').isLength({ min: 3 }).withMessage('First name must be at least 3 characters long'), 11 | body('password').isLength({ min: 6 }).withMessage('Password must be at least 6 characters long'), 12 | body('vehicle.color').isLength({ min: 3 }).withMessage('Color must be at least 3 characters long'), 13 | body('vehicle.plate').isLength({ min: 3 }).withMessage('Plate must be at least 3 characters long'), 14 | body('vehicle.capacity').isInt({ min: 1 }).withMessage('Capacity must be at least 1'), 15 | body('vehicle.vehicleType').isIn([ 'car', 'motorcycle', 'auto' ]).withMessage('Invalid vehicle type') 16 | ], 17 | captainController.registerCaptain 18 | ) 19 | 20 | 21 | router.post('/login', [ 22 | body('email').isEmail().withMessage('Invalid Email'), 23 | body('password').isLength({ min: 6 }).withMessage('Password must be at least 6 characters long') 24 | ], 25 | captainController.loginCaptain 26 | ) 27 | 28 | 29 | router.get('/profile', authMiddleware.authCaptain, captainController.getCaptainProfile) 30 | 31 | router.get('/logout', authMiddleware.authCaptain, captainController.logoutCaptain) 32 | 33 | 34 | module.exports = router; -------------------------------------------------------------------------------- /Backend/routes/ride.routes.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | const { body, query } = require('express-validator'); 4 | const rideController = require('../controllers/ride.controller'); 5 | const authMiddleware = require('../middlewares/auth.middleware'); 6 | 7 | 8 | router.post('/create', 9 | authMiddleware.authUser, 10 | body('pickup').isString().isLength({ min: 3 }).withMessage('Invalid pickup address'), 11 | body('destination').isString().isLength({ min: 3 }).withMessage('Invalid destination address'), 12 | body('vehicleType').isString().isIn([ 'auto', 'car', 'moto' ]).withMessage('Invalid vehicle type'), 13 | rideController.createRide 14 | ) 15 | 16 | router.get('/get-fare', 17 | authMiddleware.authUser, 18 | query('pickup').isString().isLength({ min: 3 }).withMessage('Invalid pickup address'), 19 | query('destination').isString().isLength({ min: 3 }).withMessage('Invalid destination address'), 20 | rideController.getFare 21 | ) 22 | 23 | router.post('/confirm', 24 | authMiddleware.authCaptain, 25 | body('rideId').isMongoId().withMessage('Invalid ride id'), 26 | rideController.confirmRide 27 | ) 28 | 29 | router.get('/start-ride', 30 | authMiddleware.authCaptain, 31 | query('rideId').isMongoId().withMessage('Invalid ride id'), 32 | query('otp').isString().isLength({ min: 6, max: 6 }).withMessage('Invalid OTP'), 33 | rideController.startRide 34 | ) 35 | 36 | router.post('/end-ride', 37 | authMiddleware.authCaptain, 38 | body('rideId').isMongoId().withMessage('Invalid ride id'), 39 | rideController.endRide 40 | ) 41 | 42 | 43 | 44 | module.exports = router; -------------------------------------------------------------------------------- /Backend/controllers/map.controller.js: -------------------------------------------------------------------------------- 1 | const mapService = require('../services/maps.service'); 2 | const { validationResult } = require('express-validator'); 3 | 4 | 5 | module.exports.getCoordinates = async (req, res, next) => { 6 | const errors = validationResult(req); 7 | if (!errors.isEmpty()) { 8 | return res.status(400).json({ errors: errors.array() }); 9 | } 10 | 11 | 12 | const { address } = req.query; 13 | 14 | try { 15 | const coordinates = await mapService.getAddressCoordinate(address); 16 | res.status(200).json(coordinates); 17 | } catch (error) { 18 | res.status(404).json({ message: 'Coordinates not found' }); 19 | } 20 | } 21 | 22 | module.exports.getDistanceTime = async (req, res, next) => { 23 | 24 | try { 25 | 26 | const errors = validationResult(req); 27 | if (!errors.isEmpty()) { 28 | return res.status(400).json({ errors: errors.array() }); 29 | } 30 | 31 | const { origin, destination } = req.query; 32 | 33 | const distanceTime = await mapService.getDistanceTime(origin, destination); 34 | 35 | res.status(200).json(distanceTime); 36 | 37 | } catch (err) { 38 | console.error(err); 39 | res.status(500).json({ message: 'Internal server error' }); 40 | } 41 | } 42 | 43 | module.exports.getAutoCompleteSuggestions = async (req, res, next) => { 44 | 45 | try { 46 | 47 | const errors = validationResult(req); 48 | if (!errors.isEmpty()) { 49 | return res.status(400).json({ errors: errors.array() }); 50 | } 51 | 52 | const { input } = req.query; 53 | 54 | const suggestions = await mapService.getAutoCompleteSuggestions(input); 55 | 56 | res.status(200).json(suggestions); 57 | } catch (err) { 58 | console.error(err); 59 | res.status(500).json({ message: 'Internal server error' }); 60 | } 61 | } -------------------------------------------------------------------------------- /Backend/middlewares/auth.middleware.js: -------------------------------------------------------------------------------- 1 | const userModel = require('../models/user.model'); 2 | const bcrypt = require('bcrypt'); 3 | const jwt = require('jsonwebtoken'); 4 | const blackListTokenModel = require('../models/blackListToken.model'); 5 | const captainModel = require('../models/captain.model'); 6 | 7 | 8 | module.exports.authUser = async (req, res, next) => { 9 | const token = req.cookies.token || req.headers.authorization?.split(' ')[ 1 ]; 10 | 11 | if (!token) { 12 | return res.status(401).json({ message: 'Unauthorized' }); 13 | } 14 | 15 | 16 | const isBlacklisted = await blackListTokenModel.findOne({ token: token }); 17 | 18 | if (isBlacklisted) { 19 | return res.status(401).json({ message: 'Unauthorized' }); 20 | } 21 | 22 | try { 23 | 24 | const decoded = jwt.verify(token, process.env.JWT_SECRET); 25 | const user = await userModel.findById(decoded._id) 26 | 27 | req.user = user; 28 | 29 | return next(); 30 | 31 | } catch (err) { 32 | return res.status(401).json({ message: 'Unauthorized' }); 33 | } 34 | } 35 | 36 | module.exports.authCaptain = async (req, res, next) => { 37 | const token = req.cookies.token || req.headers.authorization?.split(' ')[ 1 ]; 38 | 39 | 40 | if (!token) { 41 | return res.status(401).json({ message: 'Unauthorized' }); 42 | } 43 | 44 | const isBlacklisted = await blackListTokenModel.findOne({ token: token }); 45 | 46 | 47 | 48 | if (isBlacklisted) { 49 | return res.status(401).json({ message: 'Unauthorized' }); 50 | } 51 | 52 | try { 53 | const decoded = jwt.verify(token, process.env.JWT_SECRET); 54 | const captain = await captainModel.findById(decoded._id) 55 | req.captain = captain; 56 | 57 | return next() 58 | } catch (err) { 59 | console.log(err); 60 | 61 | res.status(401).json({ message: 'Unauthorized' }); 62 | } 63 | } -------------------------------------------------------------------------------- /Backend/socket.js: -------------------------------------------------------------------------------- 1 | const socketIo = require('socket.io'); 2 | const userModel = require('./models/user.model'); 3 | const captainModel = require('./models/captain.model'); 4 | 5 | let io; 6 | 7 | function initializeSocket(server) { 8 | io = socketIo(server, { 9 | cors: { 10 | origin: '*', 11 | methods: [ 'GET', 'POST' ] 12 | } 13 | }); 14 | 15 | io.on('connection', (socket) => { 16 | console.log(`Client connected: ${socket.id}`); 17 | 18 | 19 | socket.on('join', async (data) => { 20 | const { userId, userType } = data; 21 | 22 | if (userType === 'user') { 23 | await userModel.findByIdAndUpdate(userId, { socketId: socket.id }); 24 | } else if (userType === 'captain') { 25 | await captainModel.findByIdAndUpdate(userId, { socketId: socket.id }); 26 | } 27 | }); 28 | 29 | 30 | socket.on('update-location-captain', async (data) => { 31 | const { userId, location } = data; 32 | 33 | if (!location || !location.ltd || !location.lng) { 34 | return socket.emit('error', { message: 'Invalid location data' }); 35 | } 36 | 37 | await captainModel.findByIdAndUpdate(userId, { 38 | location: { 39 | ltd: location.ltd, 40 | lng: location.lng 41 | } 42 | }); 43 | }); 44 | 45 | socket.on('disconnect', () => { 46 | console.log(`Client disconnected: ${socket.id}`); 47 | }); 48 | }); 49 | } 50 | 51 | const sendMessageToSocketId = (socketId, messageObject) => { 52 | 53 | console.log(messageObject); 54 | 55 | if (io) { 56 | io.to(socketId).emit(messageObject.event, messageObject.data); 57 | } else { 58 | console.log('Socket.io not initialized.'); 59 | } 60 | } 61 | 62 | module.exports = { initializeSocket, sendMessageToSocketId }; -------------------------------------------------------------------------------- /frontend/src/App.jsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react' 2 | import { Route, Routes } from 'react-router-dom' 3 | import Start from './pages/Start' 4 | import UserLogin from './pages/UserLogin' 5 | import UserSignup from './pages/UserSignup' 6 | import Captainlogin from './pages/Captainlogin' 7 | import CaptainSignup from './pages/CaptainSignup' 8 | import Home from './pages/Home' 9 | import UserProtectWrapper from './pages/UserProtectWrapper' 10 | import UserLogout from './pages/UserLogout' 11 | import CaptainHome from './pages/CaptainHome' 12 | import CaptainProtectWrapper from './pages/CaptainProtectWrapper' 13 | import CaptainLogout from './pages/CaptainLogout' 14 | import Riding from './pages/Riding' 15 | import CaptainRiding from './pages/CaptainRiding' 16 | import 'remixicon/fonts/remixicon.css' 17 | 18 | const App = () => { 19 | 20 | return ( 21 |
22 | 23 | } /> 24 | } /> 25 | } /> 26 | } /> 27 | 28 | } /> 29 | } /> 30 | } /> 31 | 34 | 35 | 36 | } /> 37 | 39 | 40 | 41 | } /> 42 | 44 | 45 | 46 | 47 | } /> 48 | 50 | 51 | 52 | } /> 53 | 54 |
55 | ) 56 | } 57 | 58 | export default App -------------------------------------------------------------------------------- /frontend/src/components/CaptainDetails.jsx: -------------------------------------------------------------------------------- 1 | 2 | import React, { useContext } from 'react' 3 | import { CaptainDataContext } from '../context/CapatainContext' 4 | 5 | const CaptainDetails = () => { 6 | 7 | const { captain } = useContext(CaptainDataContext) 8 | 9 | return ( 10 |
11 |
12 |
13 | 14 |

{captain.fullname.firstname + " " + captain.fullname.lastname}

15 |
16 |
17 |

₹295.20

18 |

Earned

19 |
20 |
21 |
22 |
23 | 24 |
10.2
25 |

Hours Online

26 |
27 |
28 | 29 |
10.2
30 |

Hours Online

31 |
32 |
33 | 34 |
10.2
35 |

Hours Online

36 |
37 | 38 |
39 |
40 | ) 41 | } 42 | 43 | export default CaptainDetails -------------------------------------------------------------------------------- /frontend/src/components/LookingForDriver.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const LookingForDriver = (props) => { 4 | return ( 5 |
6 |
{ 7 | props.setVehicleFound(false) 8 | }}>
9 |

Looking for a Driver

10 | 11 |
12 | 13 |
14 |
15 | 16 |
17 |

562/11-A

18 |

{props.pickup}

19 |
20 |
21 |
22 | 23 |
24 |

562/11-A

25 |

{props.destination}

26 |
27 |
28 |
29 | 30 |
31 |

₹{props.fare[ props.vehicleType ]}

32 |

Cash Cash

33 |
34 |
35 |
36 |
37 |
38 | ) 39 | } 40 | 41 | export default LookingForDriver -------------------------------------------------------------------------------- /frontend/src/components/LiveTracking.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react' 2 | import { LoadScript, GoogleMap, Marker } from '@react-google-maps/api' 3 | 4 | const containerStyle = { 5 | width: '100%', 6 | height: '100%', 7 | }; 8 | 9 | const center = { 10 | lat: -3.745, 11 | lng: -38.523 12 | }; 13 | 14 | const LiveTracking = () => { 15 | const [ currentPosition, setCurrentPosition ] = useState(center); 16 | 17 | useEffect(() => { 18 | navigator.geolocation.getCurrentPosition((position) => { 19 | const { latitude, longitude } = position.coords; 20 | setCurrentPosition({ 21 | lat: latitude, 22 | lng: longitude 23 | }); 24 | }); 25 | 26 | const watchId = navigator.geolocation.watchPosition((position) => { 27 | const { latitude, longitude } = position.coords; 28 | setCurrentPosition({ 29 | lat: latitude, 30 | lng: longitude 31 | }); 32 | }); 33 | 34 | return () => navigator.geolocation.clearWatch(watchId); 35 | }, []); 36 | 37 | useEffect(() => { 38 | const updatePosition = () => { 39 | navigator.geolocation.getCurrentPosition((position) => { 40 | const { latitude, longitude } = position.coords; 41 | 42 | console.log('Position updated:', latitude, longitude); 43 | setCurrentPosition({ 44 | lat: latitude, 45 | lng: longitude 46 | }); 47 | }); 48 | }; 49 | 50 | updatePosition(); // Initial position update 51 | 52 | const intervalId = setInterval(updatePosition, 1000); // Update every 10 seconds 53 | 54 | }, []); 55 | 56 | return ( 57 | 58 | 63 | 64 | 65 | 66 | ) 67 | } 68 | 69 | export default LiveTracking -------------------------------------------------------------------------------- /frontend/src/components/WaitingForDriver.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const WaitingForDriver = (props) => { 4 | return ( 5 |
6 |
{ 7 | props.waitingForDriver(false) 8 | }}>
9 | 10 |
11 | 12 |
13 |

{props.ride?.captain.fullname.firstname}

14 |

{props.ride?.captain.vehicle.plate}

15 |

Maruti Suzuki Alto

16 |

{props.ride?.otp}

17 |
18 |
19 | 20 |
21 |
22 |
23 | 24 |
25 |

562/11-A

26 |

{props.ride?.pickup}

27 |
28 |
29 |
30 | 31 |
32 |

562/11-A

33 |

{props.ride?.destination}

34 |
35 |
36 |
37 | 38 |
39 |

₹{props.ride?.fare}

40 |

Cash Cash

41 |
42 |
43 |
44 |
45 |
46 | ) 47 | } 48 | 49 | export default WaitingForDriver -------------------------------------------------------------------------------- /Backend/controllers/user.controller.js: -------------------------------------------------------------------------------- 1 | const userModel = require('../models/user.model'); 2 | const userService = require('../services/user.service'); 3 | const { validationResult } = require('express-validator'); 4 | const blackListTokenModel = require('../models/blackListToken.model'); 5 | 6 | module.exports.registerUser = async (req, res, next) => { 7 | 8 | const errors = validationResult(req); 9 | if (!errors.isEmpty()) { 10 | return res.status(400).json({ errors: errors.array() }); 11 | } 12 | 13 | const { fullname, email, password } = req.body; 14 | 15 | const isUserAlready = await userModel.findOne({ email }); 16 | 17 | if (isUserAlready) { 18 | return res.status(400).json({ message: 'User already exist' }); 19 | } 20 | 21 | const hashedPassword = await userModel.hashPassword(password); 22 | 23 | const user = await userService.createUser({ 24 | firstname: fullname.firstname, 25 | lastname: fullname.lastname, 26 | email, 27 | password: hashedPassword 28 | }); 29 | 30 | const token = user.generateAuthToken(); 31 | 32 | res.status(201).json({ token, user }); 33 | 34 | 35 | } 36 | 37 | module.exports.loginUser = async (req, res, next) => { 38 | 39 | const errors = validationResult(req); 40 | if (!errors.isEmpty()) { 41 | return res.status(400).json({ errors: errors.array() }); 42 | } 43 | 44 | const { email, password } = req.body; 45 | 46 | const user = await userModel.findOne({ email }).select('+password'); 47 | 48 | if (!user) { 49 | return res.status(401).json({ message: 'Invalid email or password' }); 50 | } 51 | 52 | const isMatch = await user.comparePassword(password); 53 | 54 | if (!isMatch) { 55 | return res.status(401).json({ message: 'Invalid email or password' }); 56 | } 57 | 58 | const token = user.generateAuthToken(); 59 | 60 | res.cookie('token', token); 61 | 62 | res.status(200).json({ token, user }); 63 | } 64 | 65 | module.exports.getUserProfile = async (req, res, next) => { 66 | 67 | res.status(200).json(req.user); 68 | 69 | } 70 | 71 | module.exports.logoutUser = async (req, res, next) => { 72 | res.clearCookie('token'); 73 | const token = req.cookies.token || req.headers.authorization.split(' ')[ 1 ]; 74 | 75 | await blackListTokenModel.create({ token }); 76 | 77 | res.status(200).json({ message: 'Logged out' }); 78 | 79 | } -------------------------------------------------------------------------------- /frontend/src/components/ConfirmRide.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const ConfirmRide = (props) => { 4 | return ( 5 |
6 |
{ 7 | props.setConfirmRidePanel(false) 8 | }}>
9 |

Confirm your Ride

10 | 11 |
12 | 13 |
14 |
15 | 16 |
17 |

562/11-A

18 |

{props.pickup}

19 |
20 |
21 |
22 | 23 |
24 |

562/11-A

25 |

{props.destination}

26 |
27 |
28 |
29 | 30 |
31 |

₹{props.fare[ props.vehicleType ]}

32 |

Cash Cash

33 |
34 |
35 |
36 | 42 |
43 |
44 | ) 45 | } 46 | 47 | export default ConfirmRide -------------------------------------------------------------------------------- /Backend/models/captain.model.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | const bcrypt = require('bcrypt') 3 | const jwt = require('jsonwebtoken') 4 | 5 | const captainSchema = new mongoose.Schema({ 6 | fullname: { 7 | firstname: { 8 | type: String, 9 | required: true, 10 | minlength: [ 3, 'Firstname must be at least 3 characters long' ], 11 | }, 12 | lastname: { 13 | type: String, 14 | minlength: [ 3, 'Lastname must be at least 3 characters long' ], 15 | } 16 | }, 17 | email: { 18 | type: String, 19 | required: true, 20 | unique: true, 21 | lowercase: true, 22 | match: [ /^\S+@\S+\.\S+$/, 'Please enter a valid email' ] 23 | }, 24 | password: { 25 | type: String, 26 | required: true, 27 | select: false, 28 | }, 29 | socketId: { 30 | type: String, 31 | }, 32 | 33 | status: { 34 | type: String, 35 | enum: [ 'active', 'inactive' ], 36 | default: 'inactive', 37 | }, 38 | 39 | vehicle: { 40 | color: { 41 | type: String, 42 | required: true, 43 | minlength: [ 3, 'Color must be at least 3 characters long' ], 44 | }, 45 | plate: { 46 | type: String, 47 | required: true, 48 | minlength: [ 3, 'Plate must be at least 3 characters long' ], 49 | }, 50 | capacity: { 51 | type: Number, 52 | required: true, 53 | min: [ 1, 'Capacity must be at least 1' ], 54 | }, 55 | vehicleType: { 56 | type: String, 57 | required: true, 58 | enum: [ 'car', 'motorcycle', 'auto' ], 59 | } 60 | }, 61 | 62 | location: { 63 | ltd: { 64 | type: Number, 65 | }, 66 | lng: { 67 | type: Number, 68 | } 69 | } 70 | }) 71 | 72 | 73 | captainSchema.methods.generateAuthToken = function () { 74 | const token = jwt.sign({ _id: this._id }, process.env.JWT_SECRET, { expiresIn: '24h' }); 75 | return token; 76 | } 77 | 78 | 79 | captainSchema.methods.comparePassword = async function (password) { 80 | return await bcrypt.compare(password, this.password); 81 | } 82 | 83 | 84 | captainSchema.statics.hashPassword = async function (password) { 85 | return await bcrypt.hash(password, 10); 86 | } 87 | 88 | const captainModel = mongoose.model('captain', captainSchema) 89 | 90 | 91 | module.exports = captainModel; -------------------------------------------------------------------------------- /frontend/src/pages/CaptainRiding.jsx: -------------------------------------------------------------------------------- 1 | import React, { useRef, useState } from 'react' 2 | import { Link, useLocation } from 'react-router-dom' 3 | import FinishRide from '../components/FinishRide' 4 | import { useGSAP } from '@gsap/react' 5 | import gsap from 'gsap' 6 | import LiveTracking from '../components/LiveTracking' 7 | 8 | const CaptainRiding = () => { 9 | 10 | const [ finishRidePanel, setFinishRidePanel ] = useState(false) 11 | const finishRidePanelRef = useRef(null) 12 | const location = useLocation() 13 | const rideData = location.state?.ride 14 | 15 | 16 | 17 | useGSAP(function () { 18 | if (finishRidePanel) { 19 | gsap.to(finishRidePanelRef.current, { 20 | transform: 'translateY(0)' 21 | }) 22 | } else { 23 | gsap.to(finishRidePanelRef.current, { 24 | transform: 'translateY(100%)' 25 | }) 26 | } 27 | }, [ finishRidePanel ]) 28 | 29 | 30 | return ( 31 |
32 | 33 |
34 | 35 | 36 | 37 | 38 |
39 | 40 |
{ 42 | setFinishRidePanel(true) 43 | }} 44 | > 45 |
{ 46 | 47 | }}>
48 |

{'4 KM away'}

49 | 50 |
51 |
52 | 55 |
56 | 57 |
58 | 59 |
60 | 61 |
62 | ) 63 | } 64 | 65 | export default CaptainRiding -------------------------------------------------------------------------------- /Backend/controllers/captain.controller.js: -------------------------------------------------------------------------------- 1 | const captainModel = require('../models/captain.model'); 2 | const captainService = require('../services/captain.service'); 3 | const blackListTokenModel = require('../models/blackListToken.model'); 4 | const { validationResult } = require('express-validator'); 5 | 6 | 7 | module.exports.registerCaptain = async (req, res, next) => { 8 | 9 | const errors = validationResult(req); 10 | if (!errors.isEmpty()) { 11 | return res.status(400).json({ errors: errors.array() }); 12 | } 13 | 14 | const { fullname, email, password, vehicle } = req.body; 15 | 16 | const isCaptainAlreadyExist = await captainModel.findOne({ email }); 17 | 18 | if (isCaptainAlreadyExist) { 19 | return res.status(400).json({ message: 'Captain already exist' }); 20 | } 21 | 22 | 23 | const hashedPassword = await captainModel.hashPassword(password); 24 | 25 | const captain = await captainService.createCaptain({ 26 | firstname: fullname.firstname, 27 | lastname: fullname.lastname, 28 | email, 29 | password: hashedPassword, 30 | color: vehicle.color, 31 | plate: vehicle.plate, 32 | capacity: vehicle.capacity, 33 | vehicleType: vehicle.vehicleType 34 | }); 35 | 36 | const token = captain.generateAuthToken(); 37 | 38 | res.status(201).json({ token, captain }); 39 | 40 | } 41 | 42 | module.exports.loginCaptain = async (req, res, next) => { 43 | const errors = validationResult(req); 44 | if (!errors.isEmpty()) { 45 | return res.status(400).json({ errors: errors.array() }); 46 | } 47 | 48 | const { email, password } = req.body; 49 | 50 | const captain = await captainModel.findOne({ email }).select('+password'); 51 | 52 | if (!captain) { 53 | return res.status(401).json({ message: 'Invalid email or password' }); 54 | } 55 | 56 | const isMatch = await captain.comparePassword(password); 57 | 58 | if (!isMatch) { 59 | return res.status(401).json({ message: 'Invalid email or password' }); 60 | } 61 | 62 | const token = captain.generateAuthToken(); 63 | 64 | res.cookie('token', token); 65 | 66 | res.status(200).json({ token, captain }); 67 | } 68 | 69 | module.exports.getCaptainProfile = async (req, res, next) => { 70 | res.status(200).json({ captain: req.captain }); 71 | } 72 | 73 | module.exports.logoutCaptain = async (req, res, next) => { 74 | const token = req.cookies.token || req.headers.authorization?.split(' ')[ 1 ]; 75 | 76 | await blackListTokenModel.create({ token }); 77 | 78 | res.clearCookie('token'); 79 | 80 | res.status(200).json({ message: 'Logout successfully' }); 81 | } -------------------------------------------------------------------------------- /frontend/src/pages/Captainlogin.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import { Link } from 'react-router-dom' 3 | import { useNavigate } from 'react-router-dom' 4 | import axios from 'axios' 5 | import { CaptainDataContext } from '../context/CapatainContext' 6 | 7 | const Captainlogin = () => { 8 | 9 | const [ email, setEmail ] = useState('') 10 | const [ password, setPassword ] = useState('') 11 | 12 | const { captain, setCaptain } = React.useContext(CaptainDataContext) 13 | const navigate = useNavigate() 14 | 15 | 16 | 17 | const submitHandler = async (e) => { 18 | e.preventDefault(); 19 | const captain = { 20 | email: email, 21 | password 22 | } 23 | 24 | const response = await axios.post(`${import.meta.env.VITE_BASE_URL}/captains/login`, captain) 25 | 26 | if (response.status === 200) { 27 | const data = response.data 28 | 29 | setCaptain(data.captain) 30 | localStorage.setItem('token', data.token) 31 | navigate('/captain-home') 32 | 33 | } 34 | 35 | setEmail('') 36 | setPassword('') 37 | } 38 | return ( 39 |
40 |
41 | 42 | 43 |
{ 44 | submitHandler(e) 45 | }}> 46 |

What's your email

47 | { 51 | setEmail(e.target.value) 52 | }} 53 | className='bg-[#eeeeee] mb-7 rounded-lg px-4 py-2 border w-full text-lg placeholder:text-base' 54 | type="email" 55 | placeholder='email@example.com' 56 | /> 57 | 58 |

Enter Password

59 | 60 | { 64 | setPassword(e.target.value) 65 | }} 66 | required type="password" 67 | placeholder='password' 68 | /> 69 | 70 | 73 | 74 |
75 |

Join a fleet? Register as a Captain

76 |
77 |
78 | Sign in as User 82 |
83 |
84 | ) 85 | } 86 | 87 | export default Captainlogin -------------------------------------------------------------------------------- /Backend/services/maps.service.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios'); 2 | const captainModel = require('../models/captain.model'); 3 | 4 | module.exports.getAddressCoordinate = async (address) => { 5 | const apiKey = process.env.GOOGLE_MAPS_API; 6 | const url = `https://maps.googleapis.com/maps/api/geocode/json?address=${encodeURIComponent(address)}&key=${apiKey}`; 7 | 8 | try { 9 | const response = await axios.get(url); 10 | if (response.data.status === 'OK') { 11 | const location = response.data.results[ 0 ].geometry.location; 12 | return { 13 | ltd: location.lat, 14 | lng: location.lng 15 | }; 16 | } else { 17 | throw new Error('Unable to fetch coordinates'); 18 | } 19 | } catch (error) { 20 | console.error(error); 21 | throw error; 22 | } 23 | } 24 | 25 | module.exports.getDistanceTime = async (origin, destination) => { 26 | if (!origin || !destination) { 27 | throw new Error('Origin and destination are required'); 28 | } 29 | 30 | const apiKey = process.env.GOOGLE_MAPS_API; 31 | 32 | const url = `https://maps.googleapis.com/maps/api/distancematrix/json?origins=${encodeURIComponent(origin)}&destinations=${encodeURIComponent(destination)}&key=${apiKey}`; 33 | 34 | try { 35 | 36 | 37 | const response = await axios.get(url); 38 | if (response.data.status === 'OK') { 39 | 40 | if (response.data.rows[ 0 ].elements[ 0 ].status === 'ZERO_RESULTS') { 41 | throw new Error('No routes found'); 42 | } 43 | 44 | return response.data.rows[ 0 ].elements[ 0 ]; 45 | } else { 46 | throw new Error('Unable to fetch distance and time'); 47 | } 48 | 49 | } catch (err) { 50 | console.error(err); 51 | throw err; 52 | } 53 | } 54 | 55 | module.exports.getAutoCompleteSuggestions = async (input) => { 56 | if (!input) { 57 | throw new Error('query is required'); 58 | } 59 | 60 | const apiKey = process.env.GOOGLE_MAPS_API; 61 | const url = `https://maps.googleapis.com/maps/api/place/autocomplete/json?input=${encodeURIComponent(input)}&key=${apiKey}`; 62 | 63 | try { 64 | const response = await axios.get(url); 65 | if (response.data.status === 'OK') { 66 | return response.data.predictions.map(prediction => prediction.description).filter(value => value); 67 | } else { 68 | throw new Error('Unable to fetch suggestions'); 69 | } 70 | } catch (err) { 71 | console.error(err); 72 | throw err; 73 | } 74 | } 75 | 76 | module.exports.getCaptainsInTheRadius = async (ltd, lng, radius) => { 77 | 78 | // radius in km 79 | 80 | 81 | const captains = await captainModel.find({ 82 | location: { 83 | $geoWithin: { 84 | $centerSphere: [ [ ltd, lng ], radius / 6371 ] 85 | } 86 | } 87 | }); 88 | 89 | return captains; 90 | 91 | 92 | } -------------------------------------------------------------------------------- /frontend/src/pages/UserLogin.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useContext } from 'react' 2 | import { Link } from 'react-router-dom' 3 | import { UserDataContext } from '../context/UserContext' 4 | import { useNavigate } from 'react-router-dom' 5 | import axios from 'axios' 6 | 7 | const UserLogin = () => { 8 | const [ email, setEmail ] = useState('') 9 | const [ password, setPassword ] = useState('') 10 | const [ userData, setUserData ] = useState({}) 11 | 12 | const { user, setUser } = useContext(UserDataContext) 13 | const navigate = useNavigate() 14 | 15 | 16 | 17 | const submitHandler = async (e) => { 18 | e.preventDefault(); 19 | 20 | const userData = { 21 | email: email, 22 | password: password 23 | } 24 | 25 | const response = await axios.post(`${import.meta.env.VITE_BASE_URL}/users/login`, userData) 26 | 27 | if (response.status === 200) { 28 | const data = response.data 29 | setUser(data.user) 30 | localStorage.setItem('token', data.token) 31 | navigate('/home') 32 | } 33 | 34 | 35 | setEmail('') 36 | setPassword('') 37 | } 38 | 39 | return ( 40 |
41 |
42 | 43 | 44 |
{ 45 | submitHandler(e) 46 | }}> 47 |

What's your email

48 | { 52 | setEmail(e.target.value) 53 | }} 54 | className='bg-[#eeeeee] mb-7 rounded-lg px-4 py-2 border w-full text-lg placeholder:text-base' 55 | type="email" 56 | placeholder='email@example.com' 57 | /> 58 | 59 |

Enter Password

60 | 61 | { 65 | setPassword(e.target.value) 66 | }} 67 | required type="password" 68 | placeholder='password' 69 | /> 70 | 71 | 74 | 75 |
76 |

New here? Create new Account

77 |
78 |
79 | Sign in as Captain 83 |
84 |
85 | ) 86 | } 87 | 88 | export default UserLogin -------------------------------------------------------------------------------- /frontend/src/pages/Riding.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Link, useLocation } from 'react-router-dom' // Added useLocation 3 | import { useEffect, useContext } from 'react' 4 | import { SocketContext } from '../context/SocketContext' 5 | import { useNavigate } from 'react-router-dom' 6 | import LiveTracking from '../components/LiveTracking' 7 | 8 | const Riding = () => { 9 | const location = useLocation() 10 | const { ride } = location.state || {} // Retrieve ride data 11 | const { socket } = useContext(SocketContext) 12 | const navigate = useNavigate() 13 | 14 | socket.on("ride-ended", () => { 15 | navigate('/home') 16 | }) 17 | 18 | 19 | return ( 20 |
21 | 22 | 23 | 24 |
25 | 26 | 27 |
28 |
29 |
30 | 31 |
32 |

{ride?.captain.fullname.firstname}

33 |

{ride?.captain.vehicle.plate}

34 |

Maruti Suzuki Alto

35 | 36 |
37 |
38 | 39 |
40 |
41 | 42 |
43 | 44 |
45 |

562/11-A

46 |

{ride?.destination}

47 |
48 |
49 |
50 | 51 |
52 |

₹{ride?.fare}

53 |

Cash Cash

54 |
55 |
56 |
57 |
58 | 59 |
60 |
61 | ) 62 | } 63 | 64 | export default Riding -------------------------------------------------------------------------------- /frontend/src/components/RidePopUp.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const RidePopUp = (props) => { 4 | return ( 5 |
6 |
{ 7 | props.setRidePopupPanel(false) 8 | }}>
9 |

New Ride Available!

10 |
11 |
12 | 13 |

{props.ride?.user.fullname.firstname + " " + props.ride?.user.fullname.lastname}

14 |
15 |
2.2 KM
16 |
17 |
18 |
19 |
20 | 21 |
22 |

562/11-A

23 |

{props.ride?.pickup}

24 |
25 |
26 |
27 | 28 |
29 |

562/11-A

30 |

{props.ride?.destination}

31 |
32 |
33 |
34 | 35 |
36 |

₹{props.ride?.fare}

37 |

Cash Cash

38 |
39 |
40 |
41 |
42 | 47 | 48 | 52 | 53 | 54 |
55 |
56 |
57 | ) 58 | } 59 | 60 | export default RidePopUp -------------------------------------------------------------------------------- /frontend/src/components/VehiclePanel.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const VehiclePanel = (props) => { 4 | return ( 5 |
6 |
{ 7 | props.setVehiclePanel(false) 8 | }}>
9 |

Choose a Vehicle

10 |
{ 11 | props.setConfirmRidePanel(true) 12 | props.selectVehicle('car') 13 | }} className='flex border-2 active:border-black mb-2 rounded-xl w-full p-3 items-center justify-between'> 14 | 15 |
16 |

UberGo 4

17 |
2 mins away
18 |

Affordable, compact rides

19 |
20 |

₹{props.fare.car}

21 |
22 |
{ 23 | props.setConfirmRidePanel(true) 24 | props.selectVehicle('moto') 25 | }} className='flex border-2 active:border-black mb-2 rounded-xl w-full p-3 items-center justify-between'> 26 | 27 |
28 |

Moto 1

29 |
3 mins away
30 |

Affordable motorcycle rides

31 |
32 |

₹{props.fare.moto}

33 |
34 |
{ 35 | props.setConfirmRidePanel(true) 36 | props.selectVehicle('auto') 37 | }} className='flex border-2 active:border-black mb-2 rounded-xl w-full p-3 items-center justify-between'> 38 | 39 |
40 |

UberAuto 3

41 |
3 mins away
42 |

Affordable Auto rides

43 |
44 |

₹{props.fare.auto}

45 |
46 |
47 | ) 48 | } 49 | 50 | export default VehiclePanel -------------------------------------------------------------------------------- /Backend/controllers/ride.controller.js: -------------------------------------------------------------------------------- 1 | const rideService = require('../services/ride.service'); 2 | const { validationResult } = require('express-validator'); 3 | const mapService = require('../services/maps.service'); 4 | const { sendMessageToSocketId } = require('../socket'); 5 | const rideModel = require('../models/ride.model'); 6 | 7 | 8 | module.exports.createRide = async (req, res) => { 9 | const errors = validationResult(req); 10 | if (!errors.isEmpty()) { 11 | return res.status(400).json({ errors: errors.array() }); 12 | } 13 | 14 | const { userId, pickup, destination, vehicleType } = req.body; 15 | 16 | try { 17 | const ride = await rideService.createRide({ user: req.user._id, pickup, destination, vehicleType }); 18 | res.status(201).json(ride); 19 | 20 | const pickupCoordinates = await mapService.getAddressCoordinate(pickup); 21 | 22 | const captainsInRadius = await mapService.getCaptainsInTheRadius(pickupCoordinates.lat, pickupCoordinates.lng, 2); 23 | 24 | ride.otp = "" 25 | 26 | const rideWithUser = await rideModel.findOne({ _id: ride._id }).populate('user'); 27 | 28 | captainsInRadius.map(captain => { 29 | sendMessageToSocketId(captain.socketId, { 30 | event: 'new-ride', 31 | data: rideWithUser 32 | }); 33 | }); 34 | 35 | } catch (err) { 36 | console.log(err); 37 | return res.status(500).json({ message: err.message }); 38 | } 39 | 40 | }; 41 | 42 | module.exports.getFare = async (req, res) => { 43 | const errors = validationResult(req); 44 | if (!errors.isEmpty()) { 45 | return res.status(400).json({ errors: errors.array() }); 46 | } 47 | 48 | const { pickup, destination } = req.query; 49 | 50 | try { 51 | // ...existing code... 52 | } catch (err) { 53 | // ...existing code... 54 | } 55 | } 56 | 57 | module.exports.confirmRide = async (req, res) => { 58 | const errors = validationResult(req); 59 | if (!errors.isEmpty()) { 60 | return res.status(400).json({ errors: errors.array() }); 61 | } 62 | 63 | const { rideId } = req.body; 64 | 65 | try { 66 | // ...existing code... 67 | } catch (err) { 68 | // ...existing code... 69 | } 70 | } 71 | 72 | module.exports.startRide = async (req, res) => { 73 | const errors = validationResult(req); 74 | if (!errors.isEmpty()) { 75 | return res.status(400).json({ errors: errors.array() }); 76 | } 77 | 78 | const { rideId, otp } = req.query; 79 | 80 | try { 81 | // ...existing code... 82 | } catch (err) { 83 | // ...existing code... 84 | } 85 | } 86 | 87 | module.exports.endRide = async (req, res) => { 88 | const errors = validationResult(req); 89 | if (!errors.isEmpty()) { 90 | return res.status(400).json({ errors: errors.array() }); 91 | } 92 | 93 | const { rideId } = req.body; 94 | 95 | try { 96 | const ride = await rideService.endRide({ rideId, captain: req.captain }); 97 | 98 | sendMessageToSocketId(ride.user.socketId, { 99 | event: 'ride-ended', 100 | data: ride 101 | }); 102 | 103 | return res.status(200).json(ride); 104 | } catch (err) { 105 | return res.status(500).json({ message: err.message }); 106 | } 107 | } -------------------------------------------------------------------------------- /frontend/src/components/FinishRide.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Link } from 'react-router-dom' 3 | import axios from 'axios' 4 | import { useNavigate } from 'react-router-dom' 5 | 6 | 7 | const FinishRide = (props) => { 8 | 9 | const navigate = useNavigate() 10 | 11 | async function endRide() { 12 | const response = await axios.post(`${import.meta.env.VITE_BASE_URL}/rides/end-ride`, { 13 | 14 | rideId: props.ride._id 15 | 16 | 17 | }, { 18 | headers: { 19 | Authorization: `Bearer ${localStorage.getItem('token')}` 20 | } 21 | }) 22 | 23 | if (response.status === 200) { 24 | navigate('/captain-home') 25 | } 26 | 27 | } 28 | 29 | return ( 30 |
31 |
{ 32 | props.setFinishRidePanel(false) 33 | }}>
34 |

Finish this Ride

35 |
36 |
37 | 38 |

{props.ride?.user.fullname.firstname}

39 |
40 |
2.2 KM
41 |
42 |
43 |
44 |
45 | 46 |
47 |

562/11-A

48 |

{props.ride?.pickup}

49 |
50 |
51 |
52 | 53 |
54 |

562/11-A

55 |

{props.ride?.destination}

56 |
57 |
58 |
59 | 60 |
61 |

₹{props.ride?.fare}

62 |

Cash Cash

63 |
64 |
65 |
66 | 67 |
68 | 69 | 72 | 73 | 74 |
75 |
76 |
77 | ) 78 | } 79 | 80 | export default FinishRide -------------------------------------------------------------------------------- /frontend/src/assets/react.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/components/ConfirmRidePopUp.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import { Link } from 'react-router-dom' 3 | import axios from 'axios' 4 | import { useNavigate } from 'react-router-dom' 5 | 6 | const ConfirmRidePopUp = (props) => { 7 | const [ otp, setOtp ] = useState('') 8 | const navigate = useNavigate() 9 | 10 | const submitHander = async (e) => { 11 | e.preventDefault() 12 | 13 | const response = await axios.get(`${import.meta.env.VITE_BASE_URL}/rides/start-ride`, { 14 | params: { 15 | rideId: props.ride._id, 16 | otp: otp 17 | }, 18 | headers: { 19 | Authorization: `Bearer ${localStorage.getItem('token')}` 20 | } 21 | }) 22 | 23 | if (response.status === 200) { 24 | props.setConfirmRidePopupPanel(false) 25 | props.setRidePopupPanel(false) 26 | navigate('/captain-riding', { state: { ride: props.ride } }) 27 | } 28 | 29 | 30 | } 31 | return ( 32 |
33 |
{ 34 | props.setRidePopupPanel(false) 35 | }}>
36 |

Confirm this ride to Start

37 |
38 |
39 | 40 |

{props.ride?.user.fullname.firstname}

41 |
42 |
2.2 KM
43 |
44 |
45 |
46 |
47 | 48 |
49 |

562/11-A

50 |

{props.ride?.pickup}

51 |
52 |
53 |
54 | 55 |
56 |

562/11-A

57 |

{props.ride?.destination}

58 |
59 |
60 |
61 | 62 |
63 |

₹{props.ride?.fare}

64 |

Cash Cash

65 |
66 |
67 |
68 | 69 |
70 |
71 | setOtp(e.target.value)} type="text" className='bg-[#eee] px-6 py-4 font-mono text-lg rounded-lg w-full mt-3' placeholder='Enter OTP' /> 72 | 73 | 74 | 79 | 80 |
81 |
82 |
83 |
84 | ) 85 | } 86 | 87 | export default ConfirmRidePopUp -------------------------------------------------------------------------------- /Backend/services/ride.service.js: -------------------------------------------------------------------------------- 1 | const rideModel = require('../models/ride.model'); 2 | const mapService = require('./maps.service'); 3 | const bcrypt = require('bcrypt'); 4 | const crypto = require('crypto'); 5 | 6 | async function getFare(pickup, destination) { 7 | 8 | if (!pickup || !destination) { 9 | throw new Error('Pickup and destination are required'); 10 | } 11 | 12 | const distanceTime = await mapService.getDistanceTime(pickup, destination); 13 | 14 | const baseFare = { 15 | auto: 30, 16 | car: 50, 17 | moto: 20 18 | }; 19 | 20 | const perKmRate = { 21 | auto: 10, 22 | car: 15, 23 | moto: 8 24 | }; 25 | 26 | const perMinuteRate = { 27 | auto: 2, 28 | car: 3, 29 | moto: 1.5 30 | }; 31 | 32 | 33 | 34 | const fare = { 35 | auto: Math.round(baseFare.auto + ((distanceTime.distance.value / 1000) * perKmRate.auto) + ((distanceTime.duration.value / 60) * perMinuteRate.auto)), 36 | car: Math.round(baseFare.car + ((distanceTime.distance.value / 1000) * perKmRate.car) + ((distanceTime.duration.value / 60) * perMinuteRate.car)), 37 | moto: Math.round(baseFare.moto + ((distanceTime.distance.value / 1000) * perKmRate.moto) + ((distanceTime.duration.value / 60) * perMinuteRate.moto)) 38 | }; 39 | 40 | return fare; 41 | 42 | 43 | } 44 | 45 | module.exports.getFare = getFare; 46 | 47 | 48 | function getOtp(num) { 49 | function generateOtp(num) { 50 | const otp = crypto.randomInt(Math.pow(10, num - 1), Math.pow(10, num)).toString(); 51 | return otp; 52 | } 53 | return generateOtp(num); 54 | } 55 | 56 | 57 | module.exports.createRide = async ({ 58 | user, pickup, destination, vehicleType 59 | }) => { 60 | if (!user || !pickup || !destination || !vehicleType) { 61 | throw new Error('All fields are required'); 62 | } 63 | 64 | const fare = await getFare(pickup, destination); 65 | 66 | 67 | 68 | const ride = rideModel.create({ 69 | user, 70 | pickup, 71 | destination, 72 | otp: getOtp(6), 73 | fare: fare[ vehicleType ] 74 | }) 75 | 76 | return ride; 77 | } 78 | 79 | module.exports.confirmRide = async ({ 80 | rideId, captain 81 | }) => { 82 | if (!rideId) { 83 | throw new Error('Ride id is required'); 84 | } 85 | 86 | await rideModel.findOneAndUpdate({ 87 | _id: rideId 88 | }, { 89 | status: 'accepted', 90 | captain: captain._id 91 | }) 92 | 93 | const ride = await rideModel.findOne({ 94 | _id: rideId 95 | }).populate('user').populate('captain').select('+otp'); 96 | 97 | if (!ride) { 98 | throw new Error('Ride not found'); 99 | } 100 | 101 | return ride; 102 | 103 | } 104 | 105 | module.exports.startRide = async ({ rideId, otp, captain }) => { 106 | if (!rideId || !otp) { 107 | throw new Error('Ride id and OTP are required'); 108 | } 109 | 110 | const ride = await rideModel.findOne({ 111 | _id: rideId 112 | }).populate('user').populate('captain').select('+otp'); 113 | 114 | if (!ride) { 115 | throw new Error('Ride not found'); 116 | } 117 | 118 | if (ride.status !== 'accepted') { 119 | throw new Error('Ride not accepted'); 120 | } 121 | 122 | if (ride.otp !== otp) { 123 | throw new Error('Invalid OTP'); 124 | } 125 | 126 | await rideModel.findOneAndUpdate({ 127 | _id: rideId 128 | }, { 129 | status: 'ongoing' 130 | }) 131 | 132 | return ride; 133 | } 134 | 135 | module.exports.endRide = async ({ rideId, captain }) => { 136 | if (!rideId) { 137 | throw new Error('Ride id is required'); 138 | } 139 | 140 | const ride = await rideModel.findOne({ 141 | _id: rideId, 142 | captain: captain._id 143 | }).populate('user').populate('captain').select('+otp'); 144 | 145 | if (!ride) { 146 | throw new Error('Ride not found'); 147 | } 148 | 149 | if (ride.status !== 'ongoing') { 150 | throw new Error('Ride not ongoing'); 151 | } 152 | 153 | await rideModel.findOneAndUpdate({ 154 | _id: rideId 155 | }, { 156 | status: 'completed' 157 | }) 158 | 159 | return ride; 160 | } 161 | 162 | -------------------------------------------------------------------------------- /frontend/src/pages/UserSignup.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useContext } from 'react' 2 | import { Link, useNavigate } from 'react-router-dom' 3 | import axios from 'axios' 4 | import { UserDataContext } from '../context/UserContext' 5 | 6 | 7 | 8 | const UserSignup = () => { 9 | const [ email, setEmail ] = useState('') 10 | const [ password, setPassword ] = useState('') 11 | const [ firstName, setFirstName ] = useState('') 12 | const [ lastName, setLastName ] = useState('') 13 | const [ userData, setUserData ] = useState({}) 14 | 15 | const navigate = useNavigate() 16 | 17 | 18 | 19 | const { user, setUser } = useContext(UserDataContext) 20 | 21 | 22 | 23 | 24 | const submitHandler = async (e) => { 25 | e.preventDefault() 26 | const newUser = { 27 | fullname: { 28 | firstname: firstName, 29 | lastname: lastName 30 | }, 31 | email: email, 32 | password: password 33 | } 34 | 35 | const response = await axios.post(`${import.meta.env.VITE_BASE_URL}/users/register`, newUser) 36 | 37 | if (response.status === 201) { 38 | const data = response.data 39 | setUser(data.user) 40 | localStorage.setItem('token', data.token) 41 | navigate('/home') 42 | } 43 | 44 | 45 | setEmail('') 46 | setFirstName('') 47 | setLastName('') 48 | setPassword('') 49 | 50 | } 51 | return ( 52 |
53 |
54 |
55 | 56 | 57 |
{ 58 | submitHandler(e) 59 | }}> 60 | 61 |

What's your name

62 |
63 | { 70 | setFirstName(e.target.value) 71 | }} 72 | /> 73 | { 80 | setLastName(e.target.value) 81 | }} 82 | /> 83 |
84 | 85 |

What's your email

86 | { 90 | setEmail(e.target.value) 91 | }} 92 | className='bg-[#eeeeee] mb-7 rounded-lg px-4 py-2 border w-full text-lg placeholder:text-base' 93 | type="email" 94 | placeholder='email@example.com' 95 | /> 96 | 97 |

Enter Password

98 | 99 | { 103 | setPassword(e.target.value) 104 | }} 105 | required type="password" 106 | placeholder='password' 107 | /> 108 | 109 | 112 | 113 |
114 |

Already have a account? Login here

115 |
116 |
117 |

This site is protected by reCAPTCHA and the Google Privacy 118 | Policy and Terms of Service apply.

119 |
120 |
121 |
122 | ) 123 | } 124 | 125 | export default UserSignup -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🚗 Full Stack Uber Clone 🚖 2 | 3 | ## 💡 Description 4 | 5 | This **Uber Clone** is a fully functional web application built using the **MERN stack** (MongoDB, Express.js, React, and Node.js). The application emulates the core features of the popular ride-hailing service Uber, providing an intuitive user experience for both **users** (passengers) and **captains** (drivers). It allows users to register, authenticate, book rides, track real-time locations, and manage profiles, while captains can receive ride requests and manage their rides. 6 | 7 | With features like real-time location tracking, Google Maps integration, and a seamless booking system, this Uber clone delivers a high-quality ride-sharing service with all the essentials. 8 | 9 | ## 🚀 Main Features 10 | 11 | ### **User Features:** 12 | - **🔐 User Registration & Login:** Secure authentication system allowing users to register, log in, and manage their personal profiles. 13 | - **📍 Real-Time Location Tracking:** Integrated with **Google Maps API** to track user and captain locations in real time, display route suggestions, and calculate ride distances/times. 14 | - **🚗 Ride Booking System:** Users can book rides by selecting pick-up and drop-off locations, view nearby captains, and track ride progress in real-time. 15 | - **📊 User Dashboard:** Users have a home screen where they can manage their ride history, view ongoing rides, and update their profile. 16 | - **🔔 Real-Time Notifications:** Users receive updates on ride status, including when a captain is on the way, the ride is complete, and other essential notifications. 17 | 18 | ### **Captain (Driver) Features:** 19 | - **🔐 Captain Authentication:** Separate login for captains to access the driver dashboard and manage ride requests. 20 | - **🗺️ Ride Management:** Captains can view and accept ride requests, track rides, and manage ride details, including start and end locations. 21 | - **📍 Location Tracking:** Captains' location is tracked in real-time, allowing users to see their availability for ride bookings. 22 | - **📝 Profile Management:** Captains can update their profiles, manage their ride history, and view past earnings. 23 | 24 | ### **Core System Features:** 25 | - **🌍 Google Maps Location Search:** A location search feature powered by **Google Maps**, allowing both users and captains to search for destinations, view route suggestions, and determine estimated travel time. 26 | - **⚙️ Ride Management Backend:** A comprehensive backend system that handles ride creation, management, and status updates for both users and captains. 27 | - **📱 Responsive UI:** The application is designed with **modern, responsive UI** to ensure a smooth and intuitive user experience across all devices. 28 | 29 | ## ⚙️ Technologies Used 30 | 31 | - **Frontend:** 32 | - **React.js** (Dynamic, fast, and interactive user interface) 33 | - **Google Maps API** (For location tracking and mapping features) 34 | 35 | - **Backend:** 36 | - **Node.js** (Server-side JavaScript runtime environment) 37 | - **Express.js** (For API routing and middleware) 38 | - **MongoDB** (NoSQL database for storing ride data, user information, and ride status) 39 | 40 | - **Authentication:** 41 | - **JWT (JSON Web Tokens)** (For secure user and captain authentication) 42 | 43 | - **Real-Time Features:** 44 | - **Socket.io** (For real-time updates and notifications between users and captains) 45 | 46 | ## 🏁 How to Run 47 | 48 | 1. **Clone the Repository:** 49 | ```bash 50 | git clone 51 | 52 | 2. Install Dependencies: 53 | 54 | - Backend: Navigate to the backend directory and install the required dependencies: 55 | 56 | ``` 57 | cd backend 58 | npm install 59 | ``` 60 | 61 | - Frontend: Navigate to the frontend directory and install the required dependencies: 62 | ``` 63 | cd frontend 64 | npm install 65 | ``` 66 | 67 | 3. Set Up Environment Variables: Create an .env file in the root of the backend directory and add the necessary variables: 68 | 69 | ``` 70 | JWT_SECRET= 71 | MONGO_URI= 72 | GOOGLE_MAPS_API_KEY= 73 | ``` 74 | 75 | 4. Run the Backend: 76 | 77 | ``` 78 | cd backend 79 | npm run dev 80 | ``` 81 | 82 | 5. Run the Frontend: 83 | 84 | ``` 85 | cd frontend 86 | npm run dev 87 | ``` 88 | 89 | - Access the Application: Open your browser and go to http://localhost:3000 to start using the Uber Clone platform. 90 | 91 | ## 🎯 Ideal For 92 | 93 | - 🚖 Ride-Hailing Services: Easily set up your own ride-hailing platform with this clone, using core features similar to Uber. 94 | - 🧑‍💻 Developers: A great project for developers looking to build a full-stack ride-booking platform with real-time tracking. 95 | - 🌍 Entrepreneurs & Startups: Quickly deploy a fully functional Uber-like platform for your own local ride-sharing service. 96 | -------------------------------------------------------------------------------- /frontend/src/pages/CaptainHome.jsx: -------------------------------------------------------------------------------- 1 | import React, { useRef, useState } from 'react' 2 | import { Link } from 'react-router-dom' 3 | import CaptainDetails from '../components/CaptainDetails' 4 | import RidePopUp from '../components/RidePopUp' 5 | import { useGSAP } from '@gsap/react' 6 | import gsap from 'gsap' 7 | import ConfirmRidePopUp from '../components/ConfirmRidePopUp' 8 | import { useEffect, useContext } from 'react' 9 | import { SocketContext } from '../context/SocketContext' 10 | import { CaptainDataContext } from '../context/CapatainContext' 11 | import axios from 'axios' 12 | 13 | const CaptainHome = () => { 14 | 15 | const [ ridePopupPanel, setRidePopupPanel ] = useState(false) 16 | const [ confirmRidePopupPanel, setConfirmRidePopupPanel ] = useState(false) 17 | 18 | const ridePopupPanelRef = useRef(null) 19 | const confirmRidePopupPanelRef = useRef(null) 20 | const [ ride, setRide ] = useState(null) 21 | 22 | const { socket } = useContext(SocketContext) 23 | const { captain } = useContext(CaptainDataContext) 24 | 25 | useEffect(() => { 26 | socket.emit('join', { 27 | userId: captain._id, 28 | userType: 'captain' 29 | }) 30 | const updateLocation = () => { 31 | if (navigator.geolocation) { 32 | navigator.geolocation.getCurrentPosition(position => { 33 | 34 | socket.emit('update-location-captain', { 35 | userId: captain._id, 36 | location: { 37 | ltd: position.coords.latitude, 38 | lng: position.coords.longitude 39 | } 40 | }) 41 | }) 42 | } 43 | } 44 | 45 | const locationInterval = setInterval(updateLocation, 10000) 46 | updateLocation() 47 | 48 | // return () => clearInterval(locationInterval) 49 | }, []) 50 | 51 | socket.on('new-ride', (data) => { 52 | 53 | setRide(data) 54 | setRidePopupPanel(true) 55 | 56 | }) 57 | 58 | async function confirmRide() { 59 | 60 | const response = await axios.post(`${import.meta.env.VITE_BASE_URL}/rides/confirm`, { 61 | 62 | rideId: ride._id, 63 | captainId: captain._id, 64 | 65 | 66 | }, { 67 | headers: { 68 | Authorization: `Bearer ${localStorage.getItem('token')}` 69 | } 70 | }) 71 | 72 | setRidePopupPanel(false) 73 | setConfirmRidePopupPanel(true) 74 | 75 | } 76 | 77 | 78 | useGSAP(function () { 79 | if (ridePopupPanel) { 80 | gsap.to(ridePopupPanelRef.current, { 81 | transform: 'translateY(0)' 82 | }) 83 | } else { 84 | gsap.to(ridePopupPanelRef.current, { 85 | transform: 'translateY(100%)' 86 | }) 87 | } 88 | }, [ ridePopupPanel ]) 89 | 90 | useGSAP(function () { 91 | if (confirmRidePopupPanel) { 92 | gsap.to(confirmRidePopupPanelRef.current, { 93 | transform: 'translateY(0)' 94 | }) 95 | } else { 96 | gsap.to(confirmRidePopupPanelRef.current, { 97 | transform: 'translateY(100%)' 98 | }) 99 | } 100 | }, [ confirmRidePopupPanel ]) 101 | 102 | return ( 103 |
104 |
105 | 106 | 107 | 108 | 109 |
110 |
111 | 112 | 113 |
114 |
115 | 116 |
117 |
118 | 124 |
125 |
126 | 129 |
130 |
131 | ) 132 | } 133 | 134 | export default CaptainHome -------------------------------------------------------------------------------- /frontend/src/pages/CaptainSignup.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import { Link } from 'react-router-dom' 3 | import { CaptainDataContext } from '../context/CapatainContext' 4 | import { useNavigate } from 'react-router-dom' 5 | import axios from 'axios' 6 | 7 | const CaptainSignup = () => { 8 | 9 | const navigate = useNavigate() 10 | 11 | const [ email, setEmail ] = useState('') 12 | const [ password, setPassword ] = useState('') 13 | const [ firstName, setFirstName ] = useState('') 14 | const [ lastName, setLastName ] = useState('') 15 | 16 | const [ vehicleColor, setVehicleColor ] = useState('') 17 | const [ vehiclePlate, setVehiclePlate ] = useState('') 18 | const [ vehicleCapacity, setVehicleCapacity ] = useState('') 19 | const [ vehicleType, setVehicleType ] = useState('') 20 | 21 | 22 | const { captain, setCaptain } = React.useContext(CaptainDataContext) 23 | 24 | 25 | const submitHandler = async (e) => { 26 | e.preventDefault() 27 | const captainData = { 28 | fullname: { 29 | firstname: firstName, 30 | lastname: lastName 31 | }, 32 | email: email, 33 | password: password, 34 | vehicle: { 35 | color: vehicleColor, 36 | plate: vehiclePlate, 37 | capacity: vehicleCapacity, 38 | vehicleType: vehicleType 39 | } 40 | } 41 | 42 | const response = await axios.post(`${import.meta.env.VITE_BASE_URL}/captains/register`, captainData) 43 | 44 | if (response.status === 201) { 45 | const data = response.data 46 | setCaptain(data.captain) 47 | localStorage.setItem('token', data.token) 48 | navigate('/captain-home') 49 | } 50 | 51 | setEmail('') 52 | setFirstName('') 53 | setLastName('') 54 | setPassword('') 55 | setVehicleColor('') 56 | setVehiclePlate('') 57 | setVehicleCapacity('') 58 | setVehicleType('') 59 | 60 | } 61 | return ( 62 |
63 |
64 | 65 | 66 |
{ 67 | submitHandler(e) 68 | }}> 69 | 70 |

What's our Captain's name

71 |
72 | { 79 | setFirstName(e.target.value) 80 | }} 81 | /> 82 | { 89 | setLastName(e.target.value) 90 | }} 91 | /> 92 |
93 | 94 |

What's our Captain's email

95 | { 99 | setEmail(e.target.value) 100 | }} 101 | className='bg-[#eeeeee] mb-7 rounded-lg px-4 py-2 border w-full text-lg placeholder:text-base' 102 | type="email" 103 | placeholder='email@example.com' 104 | /> 105 | 106 |

Enter Password

107 | 108 | { 112 | setPassword(e.target.value) 113 | }} 114 | required type="password" 115 | placeholder='password' 116 | /> 117 | 118 |

Vehicle Information

119 |
120 | { 127 | setVehicleColor(e.target.value) 128 | }} 129 | /> 130 | { 137 | setVehiclePlate(e.target.value) 138 | }} 139 | /> 140 |
141 |
142 | { 149 | setVehicleCapacity(e.target.value) 150 | }} 151 | /> 152 | 165 |
166 | 167 | 170 | 171 |
172 |

Already have a account? Login here

173 |
174 |
175 |

This site is protected by reCAPTCHA and the Google Privacy 176 | Policy and Terms of Service apply.

177 |
178 |
179 | ) 180 | } 181 | 182 | export default CaptainSignup -------------------------------------------------------------------------------- /frontend/src/pages/Home.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef, useState } from 'react' 2 | import { useGSAP } from '@gsap/react'; 3 | import gsap from 'gsap'; 4 | import axios from 'axios'; 5 | import 'remixicon/fonts/remixicon.css' 6 | import LocationSearchPanel from '../components/LocationSearchPanel'; 7 | import VehiclePanel from '../components/VehiclePanel'; 8 | import ConfirmRide from '../components/ConfirmRide'; 9 | import LookingForDriver from '../components/LookingForDriver'; 10 | import WaitingForDriver from '../components/WaitingForDriver'; 11 | import { SocketContext } from '../context/SocketContext'; 12 | import { useContext } from 'react'; 13 | import { UserDataContext } from '../context/UserContext'; 14 | import { useNavigate } from 'react-router-dom'; 15 | import LiveTracking from '../components/LiveTracking'; 16 | 17 | const Home = () => { 18 | const [ pickup, setPickup ] = useState('') 19 | const [ destination, setDestination ] = useState('') 20 | const [ panelOpen, setPanelOpen ] = useState(false) 21 | const vehiclePanelRef = useRef(null) 22 | const confirmRidePanelRef = useRef(null) 23 | const vehicleFoundRef = useRef(null) 24 | const waitingForDriverRef = useRef(null) 25 | const panelRef = useRef(null) 26 | const panelCloseRef = useRef(null) 27 | const [ vehiclePanel, setVehiclePanel ] = useState(false) 28 | const [ confirmRidePanel, setConfirmRidePanel ] = useState(false) 29 | const [ vehicleFound, setVehicleFound ] = useState(false) 30 | const [ waitingForDriver, setWaitingForDriver ] = useState(false) 31 | const [ pickupSuggestions, setPickupSuggestions ] = useState([]) 32 | const [ destinationSuggestions, setDestinationSuggestions ] = useState([]) 33 | const [ activeField, setActiveField ] = useState(null) 34 | const [ fare, setFare ] = useState({}) 35 | const [ vehicleType, setVehicleType ] = useState(null) 36 | const [ ride, setRide ] = useState(null) 37 | 38 | const navigate = useNavigate() 39 | 40 | const { socket } = useContext(SocketContext) 41 | const { user } = useContext(UserDataContext) 42 | 43 | useEffect(() => { 44 | socket.emit("join", { userType: "user", userId: user._id }) 45 | }, [ user ]) 46 | 47 | socket.on('ride-confirmed', ride => { 48 | 49 | 50 | setVehicleFound(false) 51 | setWaitingForDriver(true) 52 | setRide(ride) 53 | }) 54 | 55 | socket.on('ride-started', ride => { 56 | console.log("ride") 57 | setWaitingForDriver(false) 58 | navigate('/riding', { state: { ride } }) // Updated navigate to include ride data 59 | }) 60 | 61 | 62 | const handlePickupChange = async (e) => { 63 | setPickup(e.target.value) 64 | try { 65 | const response = await axios.get(`${import.meta.env.VITE_BASE_URL}/maps/get-suggestions`, { 66 | params: { input: e.target.value }, 67 | headers: { 68 | Authorization: `Bearer ${localStorage.getItem('token')}` 69 | } 70 | 71 | }) 72 | setPickupSuggestions(response.data) 73 | } catch { 74 | // handle error 75 | } 76 | } 77 | 78 | const handleDestinationChange = async (e) => { 79 | setDestination(e.target.value) 80 | try { 81 | const response = await axios.get(`${import.meta.env.VITE_BASE_URL}/maps/get-suggestions`, { 82 | params: { input: e.target.value }, 83 | headers: { 84 | Authorization: `Bearer ${localStorage.getItem('token')}` 85 | } 86 | }) 87 | setDestinationSuggestions(response.data) 88 | } catch { 89 | // handle error 90 | } 91 | } 92 | 93 | const submitHandler = (e) => { 94 | e.preventDefault() 95 | } 96 | 97 | useGSAP(function () { 98 | if (panelOpen) { 99 | gsap.to(panelRef.current, { 100 | height: '70%', 101 | padding: 24 102 | // opacity:1 103 | }) 104 | gsap.to(panelCloseRef.current, { 105 | opacity: 1 106 | }) 107 | } else { 108 | gsap.to(panelRef.current, { 109 | height: '0%', 110 | padding: 0 111 | // opacity:0 112 | }) 113 | gsap.to(panelCloseRef.current, { 114 | opacity: 0 115 | }) 116 | } 117 | }, [ panelOpen ]) 118 | 119 | 120 | useGSAP(function () { 121 | if (vehiclePanel) { 122 | gsap.to(vehiclePanelRef.current, { 123 | transform: 'translateY(0)' 124 | }) 125 | } else { 126 | gsap.to(vehiclePanelRef.current, { 127 | transform: 'translateY(100%)' 128 | }) 129 | } 130 | }, [ vehiclePanel ]) 131 | 132 | useGSAP(function () { 133 | if (confirmRidePanel) { 134 | gsap.to(confirmRidePanelRef.current, { 135 | transform: 'translateY(0)' 136 | }) 137 | } else { 138 | gsap.to(confirmRidePanelRef.current, { 139 | transform: 'translateY(100%)' 140 | }) 141 | } 142 | }, [ confirmRidePanel ]) 143 | 144 | useGSAP(function () { 145 | if (vehicleFound) { 146 | gsap.to(vehicleFoundRef.current, { 147 | transform: 'translateY(0)' 148 | }) 149 | } else { 150 | gsap.to(vehicleFoundRef.current, { 151 | transform: 'translateY(100%)' 152 | }) 153 | } 154 | }, [ vehicleFound ]) 155 | 156 | useGSAP(function () { 157 | if (waitingForDriver) { 158 | gsap.to(waitingForDriverRef.current, { 159 | transform: 'translateY(0)' 160 | }) 161 | } else { 162 | gsap.to(waitingForDriverRef.current, { 163 | transform: 'translateY(100%)' 164 | }) 165 | } 166 | }, [ waitingForDriver ]) 167 | 168 | 169 | async function findTrip() { 170 | setVehiclePanel(true) 171 | setPanelOpen(false) 172 | 173 | const response = await axios.get(`${import.meta.env.VITE_BASE_URL}/rides/get-fare`, { 174 | params: { pickup, destination }, 175 | headers: { 176 | Authorization: `Bearer ${localStorage.getItem('token')}` 177 | } 178 | }) 179 | 180 | 181 | setFare(response.data) 182 | 183 | 184 | } 185 | 186 | async function createRide() { 187 | const response = await axios.post(`${import.meta.env.VITE_BASE_URL}/rides/create`, { 188 | pickup, 189 | destination, 190 | vehicleType 191 | }, { 192 | headers: { 193 | Authorization: `Bearer ${localStorage.getItem('token')}` 194 | } 195 | }) 196 | 197 | 198 | } 199 | 200 | return ( 201 |
202 | 203 |
204 | {/* image for temporary use */} 205 | 206 |
207 |
208 |
209 |
{ 210 | setPanelOpen(false) 211 | }} className='absolute opacity-0 right-6 top-6 text-2xl'> 212 | 213 |
214 |

Find a trip

215 |
{ 216 | submitHandler(e) 217 | }}> 218 |
219 | { 221 | setPanelOpen(true) 222 | setActiveField('pickup') 223 | }} 224 | value={pickup} 225 | onChange={handlePickupChange} 226 | className='bg-[#eee] px-12 py-2 text-lg rounded-lg w-full' 227 | type="text" 228 | placeholder='Add a pick-up location' 229 | /> 230 | { 232 | setPanelOpen(true) 233 | setActiveField('destination') 234 | }} 235 | value={destination} 236 | onChange={handleDestinationChange} 237 | className='bg-[#eee] px-12 py-2 text-lg rounded-lg w-full mt-3' 238 | type="text" 239 | placeholder='Enter your destination' /> 240 |
241 | 246 |
247 |
248 | 256 |
257 |
258 |
259 | 262 |
263 |
264 | 272 |
273 |
274 | 281 |
282 |
283 | 288 |
289 |
290 | ) 291 | } 292 | 293 | export default Home -------------------------------------------------------------------------------- /Backend/README.md: -------------------------------------------------------------------------------- 1 | # Backend API Documentation 2 | 3 | ## `/users/register` Endpoint 4 | 5 | ### Description 6 | 7 | Registers a new user by creating a user account with the provided information. 8 | 9 | ### HTTP Method 10 | 11 | `POST` 12 | 13 | ### Request Body 14 | 15 | The request body should be in JSON format and include the following fields: 16 | 17 | - `fullname` (object): 18 | - `firstname` (string, required): User's first name (minimum 3 characters). 19 | - `lastname` (string, optional): User's last name (minimum 3 characters). 20 | - `email` (string, required): User's email address (must be a valid email). 21 | - `password` (string, required): User's password (minimum 6 characters). 22 | 23 | ### Example Response 24 | 25 | - `user` (object): 26 | - `fullname` (object). 27 | - `firstname` (string): User's first name (minimum 3 characters). 28 | - `lastname` (string): User's last name (minimum 3 characters). 29 | - `email` (string): User's email address (must be a valid email). 30 | - `password` (string): User's password (minimum 6 characters). 31 | - `token` (String): JWT Token 32 | 33 | ## `/users/login` Endpoint 34 | 35 | ### Description 36 | 37 | Authenticates a user using their email and password, returning a JWT token upon successful login. 38 | 39 | ### HTTP Method 40 | 41 | `POST` 42 | 43 | ### Endpoint 44 | 45 | `/users/login` 46 | 47 | ### Request Body 48 | 49 | The request body should be in JSON format and include the following fields: 50 | 51 | - `email` (string, required): User's email address (must be a valid email). 52 | - `password` (string, required): User's password (minimum 6 characters). 53 | 54 | ### Example Response 55 | 56 | - `user` (object): 57 | - `fullname` (object). 58 | - `firstname` (string): User's first name (minimum 3 characters). 59 | - `lastname` (string): User's last name (minimum 3 characters). 60 | - `email` (string): User's email address (must be a valid email). 61 | - `password` (string): User's password (minimum 6 characters). 62 | - `token` (String): JWT Token 63 | 64 | ## `/users/profile` Endpoint 65 | 66 | ### Description 67 | 68 | Retrieves the profile information of the currently authenticated user. 69 | 70 | ### HTTP Method 71 | 72 | `GET` 73 | 74 | ### Authentication 75 | 76 | Requires a valid JWT token in the Authorization header: 77 | `Authorization: Bearer ` 78 | 79 | ### Example Response 80 | 81 | - `user` (object): 82 | - `fullname` (object). 83 | - `firstname` (string): User's first name (minimum 3 characters). 84 | - `lastname` (string): User's last name (minimum 3 characters). 85 | - `email` (string): User's email address (must be a valid email). 86 | 87 | 88 | 89 | ## `/users/logout` Endpoint 90 | 91 | ### Description 92 | 93 | Logout the current user and blacklist the token provided in cookie or headers 94 | 95 | ### HTTP Method 96 | 97 | `GET` 98 | 99 | ### Authentication 100 | 101 | Requires a valid JWT token in the Authorization header or cookie: 102 | 103 | - `user` (object): 104 | - `fullname` (object). 105 | - `firstname` (string): User's first name (minimum 3 characters). 106 | - `lastname` (string): User's last name (minimum 3 characters). 107 | - `email` (string): User's email address (must be a valid email). 108 | - `password` (string): User's password (minimum 6 characters). 109 | - `token` (String): JWT Token## `/captains/register` Endpoint 110 | 111 | ### Description 112 | 113 | Registers a new captain by creating a captain account with the provided information. 114 | 115 | ### HTTP Method 116 | 117 | `POST` 118 | 119 | ### Request Body 120 | 121 | The request body should be in JSON format and include the following fields: 122 | 123 | - `fullname` (object): 124 | - `firstname` (string, required): Captain's first name (minimum 3 characters) 125 | - `lastname` (string, optional): Captain's last name 126 | - `email` (string, required): Captain's email address (must be a valid email) 127 | - `password` (string, required): Captain's password (minimum 6 characters) 128 | - `vehicle` (object): 129 | - `color` (string, required): Vehicle color (minimum 3 characters) 130 | - `plate` (string, required): Vehicle plate number (minimum 3 characters) 131 | - `capacity` (number, required): Vehicle passenger capacity (minimum 1) 132 | - `vehicleType` (string, required): Type of vehicle (must be 'car', 'motorcycle', or 'auto') 133 | 134 | ### Example Response 135 | 136 | 137 | ## `/captains/register` Endpoint 138 | 139 | ### Description 140 | 141 | Registers a new captain by creating a captain account with the provided information. 142 | 143 | ### HTTP Method 144 | 145 | `POST` 146 | 147 | ### Request Body 148 | 149 | The request body should be in JSON format and include the following fields: 150 | 151 | - `fullname` (object): 152 | - `firstname` (string, required): Captain's first name (minimum 3 characters). 153 | - `lastname` (string, optional): Captain's last name (minimum 3 characters). 154 | - `email` (string, required): Captain's email address (must be a valid email). 155 | - `password` (string, required): Captain's password (minimum 6 characters). 156 | - `vehicle` (object): 157 | - `color` (string, required): Vehicle color (minimum 3 characters). 158 | - `plate` (string, required): Vehicle plate number (minimum 3 characters). 159 | - `capacity` (number, required): Vehicle passenger capacity (minimum 1). 160 | - `vehicleType` (string, required): Type of vehicle (must be 'car', 'motorcycle', or 'auto'). 161 | 162 | ### Example Response 163 | 164 | - `captain` (object): 165 | - `fullname` (object). 166 | - `firstname` (string): Captain's first name (minimum 3 characters). 167 | - `lastname` (string): Captain's last name (minimum 3 characters). 168 | - `email` (string): Captain's email address (must be a valid email). 169 | - `password` (string): Captain's password (minimum 6 characters). 170 | - `vehicle` (object): 171 | - `color` (string): Vehicle color. 172 | - `plate` (string): Vehicle plate number. 173 | - `capacity` (number): Vehicle passenger capacity. 174 | - `vehicleType` (string): Type of vehicle. 175 | - `token` (String): JWT Token 176 | 177 | ## `/captains/login` Endpoint 178 | 179 | ### Description 180 | 181 | Authenticates a captain using their email and password, returning a JWT token upon successful login. 182 | 183 | ### HTTP Method 184 | 185 | `POST` 186 | 187 | ### Endpoint 188 | 189 | `/captains/login` 190 | 191 | ### Request Body 192 | 193 | The request body should be in JSON format and include the following fields: 194 | 195 | - `email` (string, required): Captain's email address (must be a valid email). 196 | - `password` (string, required): Captain's password (minimum 6 characters). 197 | 198 | ### Example Response 199 | 200 | - `captain` (object): 201 | - `fullname` (object). 202 | - `firstname` (string): Captain's first name (minimum 3 characters). 203 | - `lastname` (string): Captain's last name (minimum 3 characters). 204 | - `email` (string): Captain's email address (must be a valid email). 205 | - `password` (string): Captain's password (minimum 6 characters). 206 | - `vehicle` (object): 207 | - `color` (string): Vehicle color. 208 | - `plate` (string): Vehicle plate number. 209 | - `capacity` (number): Vehicle passenger capacity. 210 | - `vehicleType` (string): Type of vehicle. 211 | - `token` (String): JWT Token 212 | 213 | ## `/captains/profile` Endpoint 214 | 215 | ### Description 216 | 217 | Retrieves the profile information of the currently authenticated captain. 218 | 219 | ### HTTP Method 220 | 221 | `GET` 222 | 223 | ### Authentication 224 | 225 | Requires a valid JWT token in the Authorization header: 226 | `Authorization: Bearer ` 227 | 228 | ### Example Response 229 | 230 | - `captain` (object): 231 | - `fullname` (object). 232 | - `firstname` (string): Captain's first name (minimum 3 characters). 233 | - `lastname` (string): Captain's last name (minimum 3 characters). 234 | - `email` (string): Captain's email address (must be a valid email). 235 | - `vehicle` (object): 236 | - `color` (string): Vehicle color. 237 | - `plate` (string): Vehicle plate number. 238 | - `capacity` (number): Vehicle passenger capacity. 239 | - `vehicleType` (string): Type of vehicle. 240 | 241 | ## `/captains/logout` Endpoint 242 | 243 | ### Description 244 | 245 | Logout the current captain and blacklist the token provided in cookie or headers. 246 | 247 | ### HTTP Method 248 | 249 | `GET` 250 | 251 | ### Authentication 252 | 253 | Requires a valid JWT token in the Authorization header or cookie. 254 | 255 | ### Example Response 256 | 257 | - `message` (string): Logout successfully. 258 | 259 | 260 | ## `/maps/get-coordinates` Endpoint 261 | 262 | ### Description 263 | 264 | Retrieves the coordinates (latitude and longitude) for a given address. 265 | 266 | ### HTTP Method 267 | 268 | `GET` 269 | 270 | ### Request Parameters 271 | 272 | - `address` (string, required): The address for which to retrieve coordinates. 273 | 274 | ### Example Request 275 | 276 | GET `/maps/get-coordinates?address=1600+Amphitheatre+Parkway,+Mountain+View,+CA` 277 | 278 | ### Example Response 279 | 280 | ```json 281 | { 282 | "ltd": 37.4224764, 283 | "lng": -122.0842499 284 | } 285 | ``` 286 | 287 | ### Error Response 288 | 289 | - `400 Bad Request`: If the address parameter is missing or invalid. 290 | - `404 Not Found`: If the coordinates for the given address could not be found. 291 | 292 | ```json 293 | { 294 | "message": "Coordinates not found" 295 | } 296 | ``` 297 | 298 | ## `/maps/get-distance-time` Endpoint 299 | 300 | ### Description 301 | 302 | Retrieves the distance and estimated travel time between two locations. 303 | 304 | ### HTTP Method 305 | 306 | `GET` 307 | 308 | ### Request Parameters 309 | 310 | - `origin` (string, required): The starting address or location. 311 | - `destination` (string, required): The destination address or location. 312 | 313 | ### Example Request 314 | 315 | ``` 316 | GET /maps/get-distance-time?origin=New+York,NY&destination=Los+Angeles,CA 317 | ``` 318 | 319 | ### Example Response 320 | 321 | ```json 322 | { 323 | "distance": { 324 | "text": "2,789 miles", 325 | "value": 4486540 326 | }, 327 | "duration": { 328 | "text": "1 day 18 hours", 329 | "value": 154800 330 | } 331 | } 332 | ``` 333 | 334 | ### Error Response 335 | 336 | - `400 Bad Request`: If the origin or destination parameter is missing or invalid. 337 | - `404 Not Found`: If the distance and time for the given locations could not be found. 338 | 339 | ```json 340 | { 341 | "message": "No routes found" 342 | } 343 | ``` 344 | 345 | ## `/maps/get-suggestions` Endpoint 346 | 347 | ### Description 348 | 349 | Retrieves autocomplete suggestions for a given input string. 350 | 351 | ### HTTP Method 352 | 353 | `GET` 354 | 355 | ### Request Parameters 356 | 357 | - `input` (string, required): The input string for which to retrieve suggestions. 358 | 359 | ### Example Request 360 | 361 | ``` 362 | GET /maps/get-suggestions?input=1600+Amphitheatre 363 | ``` 364 | 365 | ### Example Response 366 | 367 | ```json 368 | [ 369 | "1600 Amphitheatre Parkway, Mountain View, CA, USA", 370 | "1600 Amphitheatre Pkwy, Mountain View, CA 94043, USA" 371 | ] 372 | ``` 373 | 374 | ### Error Response 375 | 376 | - `400 Bad Request`: If the input parameter is missing or invalid. 377 | - `500 Internal Server Error`: If there is an error retrieving suggestions. 378 | 379 | ```json 380 | { 381 | "message": "Unable to fetch suggestions" 382 | } 383 | ``` 384 | 385 | ## `/rides/create` Endpoint 386 | 387 | ### Description 388 | 389 | Creates a new ride with the provided information. 390 | 391 | ### HTTP Method 392 | 393 | `POST` 394 | 395 | ### Authentication 396 | 397 | Requires a valid JWT token in the Authorization header: 398 | `Authorization: Bearer ` 399 | 400 | ### Request Body 401 | 402 | The request body should be in JSON format and include the following fields: 403 | 404 | - `pickup` (string, required): The pickup address (minimum 3 characters). 405 | - `destination` (string, required): The destination address (minimum 3 characters). 406 | - `vehicleType` (string, required): The type of vehicle (must be 'auto', 'car', or 'moto'). 407 | 408 | ### Example Response 409 | 410 | - `ride` (object): 411 | - `user` (string): User ID. 412 | - `pickup` (string): Pickup address. 413 | - `destination` (string): Destination address. 414 | - `fare` (number): Fare amount. 415 | - `status` (string): Ride status. 416 | - `duration` (number): Duration in seconds. 417 | - `distance` (number): Distance in meters. 418 | - `otp` (string): OTP for the ride. 419 | 420 | ### Error Response 421 | 422 | - `400 Bad Request`: If any required field is missing or invalid. 423 | - `500 Internal Server Error`: If there is an error creating the ride. 424 | 425 | ```json 426 | { 427 | "message": "Error message" 428 | } 429 | ``` 430 | 431 | 432 | ## `/rides/get-fare` Endpoint 433 | 434 | ### Description 435 | 436 | Retrieves the fare estimate for a ride between the provided pickup and destination addresses. 437 | 438 | ### HTTP Method 439 | 440 | `GET` 441 | 442 | ### Authentication 443 | 444 | Requires a valid JWT token in the Authorization header: 445 | `Authorization: 446 | 447 | Bear 448 | 449 | er ` 450 | 451 | ### Request Parameters 452 | 453 | - `pickup` (string, required): The pickup address (minimum 3 characters). 454 | - `destination` (string, required): The destination address (minimum 3 characters). 455 | 456 | ### Example Request 457 | 458 | ``` 459 | GET /rides/get-fare?pickup=1600+Amphitheatre+Parkway,+Mountain+View,+CA&destination=1+Infinite+Loop,+Cupertino,+CA 460 | ``` 461 | 462 | ### Example Response 463 | 464 | ```json 465 | { 466 | "auto": 50.0, 467 | "car": 75.0, 468 | "moto": 40.0 469 | } 470 | ``` 471 | 472 | ### Error Response 473 | 474 | - `400 Bad Request`: If any required parameter is missing or invalid. 475 | - `500 Internal Server Error`: If there is an error calculating the fare. 476 | 477 | ```json 478 | { 479 | "message": "Error message" 480 | } 481 | ``` 482 | --------------------------------------------------------------------------------