From a9e21cbe3cc7d9deefb9582a0fb99c84c02a00e8 Mon Sep 17 00:00:00 2001 From: AnuragJangid18 Date: Sat, 5 Sep 2026 18:52:26 +0530 Subject: [PATCH] first commit --- .env | 6 +- backend/index.js | 305 +++++++++------------------- scripts/migrate_vehicles.js | 235 +++++++++++++++++++++ src/App.jsx | 35 ++-- src/components/Footer/Footer.jsx | 6 + src/pages/Admin/AdminDashboard.jsx | 316 +++++++++++++++++++++++++++++ src/pages/Admin/AdminLogin.jsx | 109 ++++++++++ src/pages/HomePage.jsx | 6 +- src/pages/SearchPage.jsx | 176 ++++++++-------- 9 files changed, 874 insertions(+), 320 deletions(-) create mode 100644 scripts/migrate_vehicles.js create mode 100644 src/pages/Admin/AdminDashboard.jsx create mode 100644 src/pages/Admin/AdminLogin.jsx diff --git a/.env b/.env index 1fd11ee..4a8efef 100644 --- a/.env +++ b/.env @@ -20,4 +20,8 @@ DB_HOST=localhost DB_PORT=3306 DB_USER=root DB_PASSWORD=Z41kfdw8e8@sql -DB_NAME=roadrentals \ No newline at end of file +DB_NAME=roadrentals + +# Admin Credentials +ADMIN_ID=Himanshu Jaiswal +ADMIN_PASSWORD=Himan@1314 \ No newline at end of file diff --git a/backend/index.js b/backend/index.js index b305f2b..d7d95b7 100644 --- a/backend/index.js +++ b/backend/index.js @@ -114,214 +114,7 @@ app.post("/api/bookings", async (req, res) => { } }); -// Pricing Engine & Car Data -const VEHICLES = [ - // LUXURY TIER - { - id: 1, - name: "Audi A6 Premium", - type: "LUXURY", - seats: "4+1", - luggage: "2 Bags", - image: "audi_a6", - baseFarePerDay: 12000, - driverAllowancePerDay: 600, - extraKmRate: 45, - includedKmPerDay: 250, - badge: "Executive", - features: ["Leather Interior", "Dual Climate", "Premium Audio", "Sunroof"] - }, - { - id: 2, - name: "BMW 7 Series", - type: "LUXURY", - seats: "4+1", - luggage: "3 Bags", - image: "bmw_7_series", - baseFarePerDay: 25000, - driverAllowancePerDay: 800, - extraKmRate: 85, - includedKmPerDay: 200, - badge: "VVIP Choice", - features: ["Rear-seat Entertainment", "Massage Seats", "Quiet Cabin", "Chauffeur Driven"] - }, - { - id: 3, - name: "Mercedes S-Class", - type: "LUXURY", - seats: "4+1", - luggage: "2 Bags", - image: "mercedes_s_class", - baseFarePerDay: 28000, - driverAllowancePerDay: 800, - extraKmRate: 90, - includedKmPerDay: 200, - badge: "Ultimate Luxury", - features: ["Ambient Lighting", "Burmester Sound", "Premium Chauffeur"] - }, - // SUV/MUV TIER - { - id: 4, - name: "Toyota Fortuner 4x4", - type: "SUV", - seats: "6+1", - luggage: "4 Bags", - image: "fortuner", - baseFarePerDay: 6500, - driverAllowancePerDay: 500, - extraKmRate: 25, - includedKmPerDay: 300, - badge: "Advanture King", - features: ["Off-road capable", "Powerful AC", "Spacious", "Rugged Built"] - }, - { - id: 5, - name: "Innova Crysta", - type: "MUV", - seats: "7+1", - luggage: "3 Bags", - image: "innova_crysta", - baseFarePerDay: 4500, - driverAllowancePerDay: 400, - extraKmRate: 18, - includedKmPerDay: 300, - badge: "Best Seller", - features: ["Clean Interiors", "Captain Seats", "Smooth Ride", "Carrier Available"] - }, - { - id: 6, - name: "Toyota Innova", - type: "MUV", - seats: "7+1", - luggage: "2 Bags", - image: "innova", - baseFarePerDay: 3800, - driverAllowancePerDay: 400, - extraKmRate: 16, - includedKmPerDay: 300, - badge: "Value MUV", - features: ["Comfortable", "Spacious", "Economical for Family"] - }, - // SEDAN TIER - { - id: 7, - name: "Toyota Camry", - type: "SEDAN", - seats: "4+1", - luggage: "2 Bags", - image: "camry", - baseFarePerDay: 4200, - driverAllowancePerDay: 400, - extraKmRate: 18, - includedKmPerDay: 300, - badge: "Corporate Elite", - features: ["Hybrid Tech", "Quiet Ride", "Executive Space"] - }, - { - id: 8, - name: "Toyota Etios", - type: "SEDAN", - seats: "4+1", - luggage: "2 Bags", - image: "etios", - baseFarePerDay: 2800, - driverAllowancePerDay: 400, - extraKmRate: 13, - includedKmPerDay: 300, - badge: "Economic", - features: ["Spacious Boot", "Good Legroom", "Standard AC"] - }, - { - id: 9, - name: "Swift Dzire", - type: "SEDAN", - seats: "4+1", - luggage: "2 Bags", - image: "dzire", - baseFarePerDay: 2600, - driverAllowancePerDay: 400, - extraKmRate: 13, - includedKmPerDay: 300, - badge: "Economic Choice", - features: ["Popular", "Quick Maneuver", "Efficient AC"] - }, - // GROUP TRAVEL - { - id: 11, - name: "Audi Q7 Luxury SUV", - type: "SUV", - seats: "6+1", - luggage: "4 Bags", - image: "audi_q7", - baseFarePerDay: 15000, - driverAllowancePerDay: 700, - extraKmRate: 55, - includedKmPerDay: 250, - badge: "Luxury SUV", - features: ["Quattro 4x4", "Panoramic Sunroof", "Premium Audio", "Heated Seats"] - }, - { - id: 12, - name: "BMW 5 Series", - type: "LUXURY", - seats: "4+1", - luggage: "2 Bags", - image: "bmw_5_series", - baseFarePerDay: 14000, - driverAllowancePerDay: 600, - extraKmRate: 50, - includedKmPerDay: 250, - badge: "Business Class", - features: ["Dynamic Drive", "Luxury Interiors", "Silent Hybrid", "Touchscreen Control"] - }, - { - id: 13, - name: "Mercedes E-Class", - type: "LUXURY", - seats: "4+1", - luggage: "2 Bags", - image: "mercedes_e_class", - baseFarePerDay: 14500, - driverAllowancePerDay: 600, - extraKmRate: 52, - includedKmPerDay: 250, - badge: "Timeless Elegance", - features: ["Soft Close Doors", "Ambient Light", "Professional Driver", "WIFI Enabled"] - }, - { - id: 14, - name: "Toyota Corolla Altis", - type: "SEDAN", - seats: "4+1", - luggage: "2 Bags", - image: "corolla_altis", - baseFarePerDay: 3800, - driverAllowancePerDay: 400, - extraKmRate: 16, - includedKmPerDay: 300, - badge: "Executive Sedan", - features: ["Durable", "Comfortable Suspension", "Clean Air Filter"] - } -]; - -// Lead Capture -app.post("/api/leads", async (req, res) => { - try { - const { name, mobile_number, tripType, pickupLocation, dropLocation } = req.body; - if (!name || !mobile_number) return res.status(400).json({ detail: "Name and Mobile are required" }); - - await pool.execute( - `INSERT INTO Leads (name, mobile_number, trip_type, pickup_location, drop_location) VALUES (?, ?, ?, ?, ?)`, - [name, mobile_number, tripType || null, pickupLocation || null, dropLocation || null] - ); - res.status(201).json({ status: 1, message: "Lead captured" }); - } catch (error) { - console.error("Lead Error:", error); - res.status(500).json({ detail: "Failed to capture lead" }); - } -}); - -// Centralized Pricing Calculator +// --- DYNAMIC PRICING ENGINE (DB DRIVEN) --- app.post("/api/pricing/calculate", async (req, res) => { try { const { tripType, pickupDate, returnDate, packageType } = req.body; @@ -331,7 +124,12 @@ app.post("/api/pricing/calculate", async (req, res) => { const diffTime = Math.abs(end - start); const durationDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)) || 1; - const results = VEHICLES.map(vehicle => { + // Fetch ACTIVE vehicles from DB + const [rows] = await pool.execute("SELECT * FROM Vehicles WHERE isAvailable = TRUE"); + + const results = rows.map(vehicle => { + const features = typeof vehicle.features === 'string' ? JSON.parse(vehicle.features) : vehicle.features; + let totalBaseFare = vehicle.baseFarePerDay * durationDays; let totalDriverAllowance = vehicle.driverAllowancePerDay * durationDays; @@ -343,11 +141,11 @@ app.post("/api/pricing/calculate", async (req, res) => { totalDriverAllowance = 0; } - // Special Case: Airport Transfers (Flat Rates) + // Special Case: Airport Transfers if (tripType === 'airport') { if (vehicle.type === 'SEDAN') totalBaseFare = 1200; else if (vehicle.type === 'SUV') totalBaseFare = 2200; - else totalBaseFare = 1800; // MUV/Other + else totalBaseFare = 1800; totalDriverAllowance = 0; } @@ -356,18 +154,19 @@ app.post("/api/pricing/calculate", async (req, res) => { return { ...vehicle, + features, calc: { days: durationDays, baseFare: totalBaseFare, driverAllowance: totalDriverAllowance, - totalKmIncluded: vehicle.includedKmPerDay * (tripType === 'airport' ? 0.2 : durationDays), // Approx for airport + totalKmIncluded: vehicle.includedKmPerDay * (tripType === 'airport' ? 0.2 : durationDays), grandTotal, advancePayment, math: tripType === 'airport' || tripType === 'local' ? `Fixed ${tripType.charAt(0).toUpperCase() + tripType.slice(1)} Rate` : `(₹${vehicle.baseFarePerDay} x ${durationDays} Days) + ₹${totalDriverAllowance} Driver` }, - inclusions: ["AC", "Professional Driver", tripType === 'airport' ? "Airport Toll Included" : "First " + (vehicle.includedKmPerDay * durationDays) + " Kms"], + inclusions: ["AC", "Professional Driver", tripType === 'airport' ? "Airport Toll Included" : "First " + (vehicle.includedKmPerDay * (tripType === 'airport' ? 1 : durationDays)) + " Kms"], exclusions: ["Parking Charges", "Extra Waiting Time", "Inter-state Permits"] }; }); @@ -379,6 +178,86 @@ app.post("/api/pricing/calculate", async (req, res) => { } }); +// --- ADMIN API ENDPOINTS --- + +// Secure Admin Login +app.post("/api/admin/login", async (req, res) => { + const { adminId, password } = req.body; + + // Environment-based credentials for security + const VALID_ADMIN_ID = process.env.ADMIN_ID || "admin"; + const VALID_ADMIN_PASS = process.env.ADMIN_PASSWORD || "admin123"; + + if (adminId === VALID_ADMIN_ID && password === VALID_ADMIN_PASS) { + res.json({ success: true, token: "secure_session_token_xyz_987" }); + } else { + res.status(401).json({ detail: "Invalid Admin ID or Password" }); + } +}); + +// Get Full Fleet for Management +app.get("/api/admin/vehicles", async (req, res) => { + try { + const [rows] = await pool.execute("SELECT * FROM Vehicles ORDER BY id DESC"); + const cars = rows.map(v => ({ + ...v, + features: typeof v.features === 'string' ? JSON.parse(v.features) : v.features + })); + res.json(cars); + } catch (err) { + res.status(500).json({ detail: "Fail to fetch fleet" }); + } +}); + +// Update Availability or Pricing +app.patch("/api/admin/vehicles/:id", async (req, res) => { + try { + const { id } = req.params; + const { baseFarePerDay, isAvailable } = req.body; + + if (baseFarePerDay !== undefined) { + await pool.execute("UPDATE Vehicles SET baseFarePerDay = ? WHERE id = ?", [baseFarePerDay, id]); + } + if (isAvailable !== undefined) { + await pool.execute("UPDATE Vehicles SET isAvailable = ? WHERE id = ?", [isAvailable, id]); + } + + res.json({ success: true, message: "Vehicle updated successfully" }); + } catch (err) { + res.status(500).json({ detail: "Failed to update vehicle" }); + } +}); + +// Add New Cab +app.post("/api/admin/vehicles", async (req, res) => { + try { + const v = req.body; + const features = JSON.stringify(v.features || []); + + await pool.execute( + `INSERT INTO Vehicles (name, type, seats, luggage, image, baseFarePerDay, driverAllowancePerDay, extraKmRate, includedKmPerDay, badge, features) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + [v.name, v.type, v.seats, v.luggage, v.image, v.baseFarePerDay, v.driverAllowancePerDay, v.extraKmRate, v.includedKmPerDay, v.badge, features] + ); + + res.status(201).json({ success: true, message: "New vehicle added" }); + } catch (err) { + res.status(500).json({ detail: "Failed to add new vehicle" }); + } +}); + +// Delete Cab +app.delete("/api/admin/vehicles/:id", async (req, res) => { + try { + const { id } = req.params; + await pool.execute("DELETE FROM Vehicles WHERE id = ?", [id]); + res.json({ success: true, message: "Vehicle removed" }); + } catch (err) { + res.status(500).json({ detail: "Failed to delete vehicle" }); + } +}); + + const PORT = 8000; app.listen(PORT, () => { diff --git a/scripts/migrate_vehicles.js b/scripts/migrate_vehicles.js new file mode 100644 index 0000000..a560c11 --- /dev/null +++ b/scripts/migrate_vehicles.js @@ -0,0 +1,235 @@ +import "dotenv/config"; +import mysql from "mysql2/promise"; + +const vehicles = [ + { + name: "Audi A6 Premium", + type: "LUXURY", + seats: "4+1", + luggage: "2 Bags", + image: "audi_a6", + baseFarePerDay: 12000, + driverAllowancePerDay: 600, + extraKmRate: 45, + includedKmPerDay: 250, + badge: "Executive", + features: JSON.stringify(["Leather Interior", "Dual Climate", "Premium Audio", "Sunroof"]) + }, + { + name: "BMW 7 Series", + type: "LUXURY", + seats: "4+1", + luggage: "3 Bags", + image: "bmw_7_series", + baseFarePerDay: 25000, + driverAllowancePerDay: 800, + extraKmRate: 85, + includedKmPerDay: 200, + badge: "VVIP Choice", + features: JSON.stringify(["Rear-seat Entertainment", "Massage Seats", "Quiet Cabin", "Chauffeur Driven"]) + }, + { + name: "Mercedes S-Class", + type: "LUXURY", + seats: "4+1", + luggage: "2 Bags", + image: "mercedes_s_class", + baseFarePerDay: 28000, + driverAllowancePerDay: 800, + extraKmRate: 90, + includedKmPerDay: 200, + badge: "Ultimate Luxury", + features: JSON.stringify(["Ambient Lighting", "Burmester Sound", "Premium Chauffeur"]) + }, + { + name: "Toyota Fortuner 4x4", + type: "SUV", + seats: "6+1", + luggage: "4 Bags", + image: "fortuner", + baseFarePerDay: 6500, + driverAllowancePerDay: 500, + extraKmRate: 25, + includedKmPerDay: 300, + badge: "Advanture King", + features: JSON.stringify(["Off-road capable", "Powerful AC", "Spacious", "Rugged Built"]) + }, + { + name: "Innova Crysta", + type: "MUV", + seats: "7+1", + luggage: "3 Bags", + image: "innova_crysta", + baseFarePerDay: 4500, + driverAllowancePerDay: 400, + extraKmRate: 18, + includedKmPerDay: 300, + badge: "Best Seller", + features: JSON.stringify(["Clean Interiors", "Captain Seats", "Smooth Ride", "Carrier Available"]) + }, + { + name: "Toyota Innova", + type: "MUV", + seats: "7+1", + luggage: "2 Bags", + image: "innova", + baseFarePerDay: 3800, + driverAllowancePerDay: 400, + extraKmRate: 16, + includedKmPerDay: 300, + badge: "Value MUV", + features: JSON.stringify(["Comfortable", "Spacious", "Economical for Family"]) + }, + { + name: "Toyota Camry", + type: "SEDAN", + seats: "4+1", + luggage: "2 Bags", + image: "camry", + baseFarePerDay: 4200, + driverAllowancePerDay: 400, + extraKmRate: 18, + includedKmPerDay: 300, + badge: "Corporate Elite", + features: JSON.stringify(["Hybrid Tech", "Quiet Ride", "Executive Space"]) + }, + { + name: "Toyota Etios", + type: "SEDAN", + seats: "4+1", + luggage: "2 Bags", + image: "etios", + baseFarePerDay: 2800, + driverAllowancePerDay: 400, + extraKmRate: 13, + includedKmPerDay: 300, + badge: "Economic", + features: JSON.stringify(["Spacious Boot", "Good Legroom", "Standard AC"]) + }, + { + name: "Swift Dzire", + type: "SEDAN", + seats: "4+1", + luggage: "2 Bags", + image: "dzire", + baseFarePerDay: 2600, + driverAllowancePerDay: 400, + extraKmRate: 13, + includedKmPerDay: 300, + badge: "Economic Choice", + features: JSON.stringify(["Popular", "Quick Maneuver", "Efficient AC"]) + }, + { + name: "Audi Q7 Luxury SUV", + type: "SUV", + seats: "6+1", + luggage: "4 Bags", + image: "audi_q7", + baseFarePerDay: 15000, + driverAllowancePerDay: 700, + extraKmRate: 55, + includedKmPerDay: 250, + badge: "Luxury SUV", + features: JSON.stringify(["Quattro 4x4", "Panoramic Sunroof", "Premium Audio", "Heated Seats"]) + }, + { + name: "BMW 5 Series", + type: "LUXURY", + seats: "4+1", + luggage: "2 Bags", + image: "bmw_5_series", + baseFarePerDay: 14000, + driverAllowancePerDay: 600, + extraKmRate: 50, + includedKmPerDay: 250, + badge: "Business Class", + features: JSON.stringify(["Dynamic Drive", "Luxury Interiors", "Silent Hybrid", "Touchscreen Control"]) + }, + { + name: "Mercedes E-Class", + type: "LUXURY", + seats: "4+1", + luggage: "2 Bags", + image: "mercedes_e_class", + baseFarePerDay: 14500, + driverAllowancePerDay: 600, + extraKmRate: 52, + includedKmPerDay: 250, + badge: "Timeless Elegance", + features: JSON.stringify(["Soft Close Doors", "Ambient Light", "Professional Driver", "WIFI Enabled"]) + }, + { + name: "Toyota Corolla Altis", + type: "SEDAN", + seats: "4+1", + luggage: "2 Bags", + image: "corolla_altis", + baseFarePerDay: 3800, + driverAllowancePerDay: 400, + extraKmRate: 16, + includedKmPerDay: 300, + badge: "Executive Sedan", + features: JSON.stringify(["Durable", "Comfortable Suspension", "Clean Air Filter"]) + }, + { + name: "Toyota Commuter", + type: "VAN", + seats: "12+1", + luggage: "6 Bags", + image: "toyota_commuter", + baseFarePerDay: 8500, + driverAllowancePerDay: 600, + extraKmRate: 28, + includedKmPerDay: 300, + badge: "Large Group", + features: JSON.stringify(["12 Seater", "High Ceiling", "Multiple Bags Space"]) + } +]; + +async function runMigration() { + const pool = mysql.createPool({ + host: process.env.DB_HOST || "localhost", + user: process.env.DB_USER || "root", + password: process.env.DB_PASSWORD, + database: process.env.DB_NAME || "roadrentals", + }); + + try { + console.log("Creating Vehicles table..."); + await pool.execute(` + CREATE TABLE IF NOT EXISTS Vehicles ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) NOT NULL, + type VARCHAR(50), + seats VARCHAR(20), + luggage VARCHAR(50), + image VARCHAR(255), + baseFarePerDay INT, + driverAllowancePerDay INT, + extraKmRate INT, + includedKmPerDay INT, + badge VARCHAR(100), + features TEXT, + isAvailable BOOLEAN DEFAULT TRUE, + createdAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + `); + + console.log("Seeding vehicles..."); + for (const v of vehicles) { + await pool.execute( + `INSERT INTO Vehicles (name, type, seats, luggage, image, baseFarePerDay, driverAllowancePerDay, extraKmRate, includedKmPerDay, badge, features) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + [v.name, v.type, v.seats, v.luggage, v.image, v.baseFarePerDay, v.driverAllowancePerDay, v.extraKmRate, v.includedKmPerDay, v.badge, v.features] + ); + } + + console.log("Migration successful!"); + } catch (error) { + console.error("Migration failed:", error); + } finally { + await pool.end(); + } +} + +runMigration(); diff --git a/src/App.jsx b/src/App.jsx index dc9b8ac..79cb706 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -2,6 +2,8 @@ import { useState } from "react"; import { BrowserRouter as Router, Routes, Route } from "react-router-dom"; import HomePage from "./pages/HomePage"; import SearchPage from "./pages/SearchPage"; +import AdminLogin from "./pages/Admin/AdminLogin"; +import AdminDashboard from "./pages/Admin/AdminDashboard"; import AuthModal from "./components/Auth/AuthModal"; import "./index.css"; @@ -13,29 +15,32 @@ function App() {
- setIsAuthModalOpen(true)} - user={user} + setIsAuthModalOpen(true)} + user={user} /> - } + } /> - setIsAuthModalOpen(true)} - user={user} + setIsAuthModalOpen(true)} + user={user} /> - } + } /> + } /> + } /> + } /> - setIsAuthModalOpen(false)} + setIsAuthModalOpen(false)} user={user} setUser={setUser} /> diff --git a/src/components/Footer/Footer.jsx b/src/components/Footer/Footer.jsx index b0d0a9c..7dfa489 100644 --- a/src/components/Footer/Footer.jsx +++ b/src/components/Footer/Footer.jsx @@ -1,3 +1,4 @@ +import { Link } from "react-router-dom"; import Logo from "../Logo/Logo"; const Footer = () => { @@ -81,6 +82,11 @@ const Footer = () => {

Copyright © 2018-2025 road.rentals Tours & Travels. All Rights Reserved.

+
+ + Owner Login + +
diff --git a/src/pages/Admin/AdminDashboard.jsx b/src/pages/Admin/AdminDashboard.jsx new file mode 100644 index 0000000..52dc1dc --- /dev/null +++ b/src/pages/Admin/AdminDashboard.jsx @@ -0,0 +1,316 @@ +import React, { useState, useEffect } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { Car, CreditCard, Power, Plus, Trash2, LayoutDashboard, Settings, LogOut, Search, MoreVertical, X, Check } from 'lucide-react'; + +const AdminDashboard = () => { + const [vehicles, setVehicles] = useState([]); + const [isLoading, setIsLoading] = useState(true); + const [showAddModal, setShowAddModal] = useState(false); + const [editingId, setEditingId] = useState(null); + const [newCar, setNewCar] = useState({ + name: '', type: 'SEDAN', seats: '4+1', luggage: '2 Bags', image: 'dzire', + baseFarePerDay: 2500, driverAllowancePerDay: 400, extraKmRate: 13, + includedKmPerDay: 300, badge: 'Standard', features: [] + }); + + const navigate = useNavigate(); + + useEffect(() => { + const token = localStorage.getItem('adminToken'); + if (!token) navigate('/admin/login'); + else fetchVehicles(); + }, []); + + const fetchVehicles = async () => { + try { + const res = await fetch('/api/admin/vehicles'); + const data = await res.json(); + setVehicles(data); + } catch (err) { + console.error(err); + } finally { + setIsLoading(false); + } + }; + + const toggleAvailability = async (id, currentStatus) => { + try { + await fetch(`/api/admin/vehicles/${id}`, { + method: 'PATCH', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ isAvailable: !currentStatus }) + }); + fetchVehicles(); + } catch (err) { + console.error(err); + } + }; + + const updatePrice = async (id, newPrice) => { + try { + await fetch(`/api/admin/vehicles/${id}`, { + method: 'PATCH', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ baseFarePerDay: parseInt(newPrice) }) + }); + setEditingId(null); + fetchVehicles(); + } catch (err) { + console.error(err); + } + }; + + const deleteVehicle = async (id) => { + if (!window.confirm('Delete this vehicle from fleet?')) return; + try { + await fetch(`/api/admin/vehicles/${id}`, { method: 'DELETE' }); + fetchVehicles(); + } catch (err) { + console.error(err); + } + }; + + const handleAddVehicle = async (e) => { + e.preventDefault(); + try { + await fetch('/api/admin/vehicles', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(newCar) + }); + setShowAddModal(false); + fetchVehicles(); + } catch (err) { + console.error(err); + } + }; + + const logout = () => { + localStorage.removeItem('adminToken'); + navigate('/admin/login'); + }; + + return ( +
+ {/* Sidebar */} +
+
+

+
+ +
+ road.rentals +

+

Admin Panel v2.0

+
+ + + +
+ +
+
+ + {/* Main Content */} +
+
+
+

Fleet Management

+

Inventory control and dynamic pricing

+
+ +
+ +
+ {/* Stats */} +
+
+

Total Fleet

+

{vehicles.length} Cars

+
+
+

Active Now

+

{vehicles.filter(v => v.isAvailable).length} Cars

+
+
+

Maintenance

+

{vehicles.filter(v => !v.isAvailable).length} Cars

+
+
+

Fleet Revenue

+

Dynamic

+
+
+ + {/* Table */} +
+ + + + + + + + + + + + {isLoading ? ( + + ) : vehicles.map(vehicle => ( + + + + + + + + ))} + +
Vehicle DetailsType / CapacityBase Price / DayStatusActions
Loading fleet data...
+
+
+ +
+
+

{vehicle.name}

+

{vehicle.badge || 'Standard'}

+
+
+
+ {vehicle.type} + {vehicle.seats} Seater + + {editingId === vehicle.id ? ( +
+ { + if (e.key === 'Enter') updatePrice(vehicle.id, e.target.value); + if (e.key === 'Escape') setEditingId(null); + }} + /> +
+ ) : ( +
setEditingId(vehicle.id)} + className="font-bold text-slate-900 cursor-pointer hover:text-indigo-600" + > + ₹{vehicle.baseFarePerDay} +
+ )} +
+ + + +
+
+
+
+ + {/* Add Cab Modal */} + {showAddModal && ( +
+
+
+

Add New Fleet Vehicle

+ +
+
+
+
+ + setNewCar({...newCar, name: e.target.value})} + /> +
+
+ + +
+
+ + setNewCar({...newCar, seats: e.target.value})} + /> +
+
+ + setNewCar({...newCar, baseFarePerDay: parseInt(e.target.value)})} + /> +
+
+ +
+
+
+ )} +
+ ); +}; + +export default AdminDashboard; diff --git a/src/pages/Admin/AdminLogin.jsx b/src/pages/Admin/AdminLogin.jsx new file mode 100644 index 0000000..7913aae --- /dev/null +++ b/src/pages/Admin/AdminLogin.jsx @@ -0,0 +1,109 @@ +import React, { useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { Lock, User, ShieldCheck, AlertCircle } from 'lucide-react'; + +const AdminLogin = () => { + const [adminId, setAdminId] = useState(''); + const [password, setPassword] = useState(''); + const [error, setError] = useState(''); + const [isLoading, setIsLoading] = useState(false); + const navigate = useNavigate(); + + const handleLogin = async (e) => { + e.preventDefault(); + setIsLoading(true); + setError(''); + + try { + const response = await fetch('/api/admin/login', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ adminId, password }) + }); + + const data = await response.json(); + + if (response.ok) { + localStorage.setItem('adminToken', data.token); + navigate('/admin/dashboard'); + } else { + setError(data.detail || 'Invalid credentials'); + } + } catch (err) { + setError('Connection failed. Please try again.'); + } finally { + setIsLoading(false); + } + }; + + return ( +
+
+
+
+ +
+

Admin Portal

+

Secure access for road.rentals owner

+
+ + {error && ( +
+ + {error} +
+ )} + +
+
+ +
+ + setAdminId(e.target.value)} + className="w-full pl-10 pr-4 py-3 rounded-xl border border-slate-200 focus:border-indigo-500 focus:ring-4 focus:ring-indigo-50/50 outline-none transition-all" + placeholder="Enter your Admin ID" + required + /> +
+
+ +
+ +
+ + setPassword(e.target.value)} + className="w-full pl-10 pr-4 py-3 rounded-xl border border-slate-200 focus:border-indigo-500 focus:ring-4 focus:ring-indigo-50/50 outline-none transition-all" + placeholder="••••••••" + required + /> +
+
+ + +
+ +

+ Road.Rentals Official Admin System +

+
+
+ ); +}; + +export default AdminLogin; diff --git a/src/pages/HomePage.jsx b/src/pages/HomePage.jsx index eb9068e..faa9be0 100644 --- a/src/pages/HomePage.jsx +++ b/src/pages/HomePage.jsx @@ -12,9 +12,9 @@ import Footer from "../components/Footer/Footer"; const HomePage = ({ onLoginClick, user }) => { return ( <> - diff --git a/src/pages/SearchPage.jsx b/src/pages/SearchPage.jsx index a7e5552..0c9db1c 100644 --- a/src/pages/SearchPage.jsx +++ b/src/pages/SearchPage.jsx @@ -1,9 +1,9 @@ import React, { useMemo, useState, useEffect } from "react"; import { useSearchParams, Link } from "react-router-dom"; -import { - Calendar, MapPin, Users, Luggage, ShieldCheck, - ArrowLeft, Info, CheckCircle2, Loader2, X, - ChevronRight, ArrowRight, User, Phone +import { + Calendar, MapPin, Users, Luggage, ShieldCheck, + ArrowLeft, Info, CheckCircle2, Loader2, X, + ChevronRight, ArrowRight, User, Phone } from "lucide-react"; import Navbar from "../components/Navbar/Navbar"; import Footer from "../components/Footer/Footer"; @@ -13,8 +13,8 @@ import { api } from "../lib/api"; import innovaCrystaImg from "../assets/vehicles/innova_crysta.png"; import innovaImg from "../assets/vehicles/innova.png"; import fortunerImg from "../assets/vehicles/fortuner.png"; -import dzireImg from "../assets/vehicles/dzire.png"; -import etiosImg from "../assets/vehicles/etios.png"; +import dzireImg from "../assets/vehicles/dzire.png"; +import etiosImg from "../assets/vehicles/etios.png"; import audiA6Img from "../assets/vehicles/audi_a6.png"; import audiQ7Img from "../assets/vehicles/audi_q7.png"; import bmw5Img from "../assets/vehicles/bmw_5_series.png"; @@ -105,7 +105,7 @@ const LeadCaptureModal = ({ isOpen, onSubmit, searchData }) => { onChange={(e) => setMobile(e.target.value)} /> - + - +

No spam. Only trip updates.

@@ -135,7 +135,7 @@ const SearchPage = ({ onLoginClick, user }) => { const [showLeadModal, setShowLeadModal] = useState(!user); // Don't show if user logged in const [cars, setCars] = useState([]); const [isLoading, setIsLoading] = useState(true); - + // Extract search details const tripType = searchParams.get("tripType") || "round"; const pickupLocation = searchParams.get("pickup") || "Not Specified"; @@ -199,16 +199,16 @@ const SearchPage = ({ onLoginClick, user }) => { return (
- + {/* Lead Capture Modal */} - setShowLeadModal(false)} searchData={{ tripType, pickup: pickupLocation, drop: dropLocation }} />
- + {/* Search Summary Header */}
@@ -254,7 +254,7 @@ const SearchPage = ({ onLoginClick, user }) => {
- + {/* Main Listings */}
@@ -263,7 +263,7 @@ const SearchPage = ({ onLoginClick, user }) => { Found {cars.length} Options

- + {isLoading ? (
@@ -275,14 +275,14 @@ const SearchPage = ({ onLoginClick, user }) => { {/* Car Image Wrapper */}
{car.name} - + {/* Badge */} {car.badge && (
{car.badge}
)} - +
★★★★★ Top Rated @@ -298,14 +298,14 @@ const SearchPage = ({ onLoginClick, user }) => {
{car.type}
-
- - {car.seats} -
-
- - {car.luggage} -
+
+ + {car.seats} +
+
+ + {car.luggage} +
@@ -318,15 +318,15 @@ const SearchPage = ({ onLoginClick, user }) => { {/* Transparency Math Block */}
- - - Transparent Breakdown - - {car.calc.math} + + + Transparent Breakdown + + {car.calc.math}
- 20% Booking Advance - Pay ₹{car.calc.advancePayment} to Confirm + 20% Booking Advance + Pay ₹{car.calc.advancePayment} to Confirm
@@ -334,7 +334,7 @@ const SearchPage = ({ onLoginClick, user }) => {

- Included + Included

    {car.inclusions.map((inc, i) => ( @@ -346,7 +346,7 @@ const SearchPage = ({ onLoginClick, user }) => {

- Excluded + Excluded

    {car.exclusions.map((exc, i) => ( @@ -364,7 +364,7 @@ const SearchPage = ({ onLoginClick, user }) => { Available Distance {car.calc.totalKmIncluded} Km Included
-
)} - +
);