├── .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 |
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 |
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 |
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 |
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 |
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 |
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 |
--------------------------------------------------------------------------------