first commit

This commit is contained in:
AnuragJangid18 2026-03-30 17:24:23 +05:30
commit 44995cb5db
60 changed files with 5472 additions and 0 deletions

1
src/App.css Normal file
View file

@ -0,0 +1 @@
/* App.css is no longer needed - all styles are handled by Tailwind via index.css */

42
src/App.jsx Normal file
View file

@ -0,0 +1,42 @@
import { useState } from "react";
import Navbar from "./components/Navbar/Navbar";
import HeroSection from "./components/Hero/HeroSection";
import CabsSection from "./components/Cabs/CabsSection";
import ServicesSection from "./components/Services/ServicesSection";
import PromoBanners from "./components/Promotions/PromoBanners";
import ReviewsSection from "./components/Reviews/ReviewsSection";
import WhyChooseUs from "./components/Highlights/WhyChooseUs";
import NewsSection from "./components/News/NewsSection";
import Footer from "./components/Footer/Footer";
import AuthModal from "./components/Auth/AuthModal";
import "./index.css";
function App() {
const [isAuthModalOpen, setIsAuthModalOpen] = useState(false);
const [user, setUser] = useState(null);
return (
<main className="min-h-screen">
<Navbar
user={user}
onLoginClick={() => setIsAuthModalOpen(true)}
/>
<HeroSection />
<CabsSection />
<ServicesSection />
<PromoBanners />
<ReviewsSection />
<WhyChooseUs />
<NewsSection />
<Footer />
<AuthModal
isOpen={isAuthModalOpen}
onClose={() => setIsAuthModalOpen(false)}
user={user}
setUser={setUser}
/>
</main>
);
}
export default App;

BIN
src/assets/hero.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

BIN
src/assets/hero_bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 819 KiB

BIN
src/assets/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 285 KiB

1
src/assets/react.svg Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>

After

Width:  |  Height:  |  Size: 4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 621 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 610 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 557 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 596 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 521 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 525 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 575 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 538 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 566 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 539 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 604 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 575 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 640 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 582 KiB

1
src/assets/vite.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.5 KiB

View file

@ -0,0 +1,108 @@
import React, { useState, useEffect } from "react";
import PhoneInputScreen from "./PhoneInputScreen";
import OtpVerificationScreen from "./OtpVerificationScreen";
import UserProfileScreen from "./UserProfileScreen";
const AuthModal = ({ isOpen, onClose, user, setUser }) => {
const [view, setView] = useState("phone");
useEffect(() => {
if (isOpen) {
setView(user ? "profile" : "phone");
}
}, [isOpen, user]);
if (!isOpen) return null;
return (
<div className="fixed inset-0 z-[100] flex items-center justify-center bg-black/50 backdrop-blur-sm px-4">
{/* Click outside to close */}
<div className="absolute inset-0" onClick={onClose} />
{/* Modal Container */}
<div
className={`relative w-full shadow-2xl transition-all duration-300 ${
view === "phone" ? "max-w-4xl bg-transparent" : "max-w-md bg-white rounded-2xl"
}`}
>
{/* Close button X */}
{(view === "otp" || view === "profile") && (
<button
onClick={onClose}
className="absolute top-4 right-4 z-10 text-gray-500 hover:text-dark-900 transition-colors"
>
<svg xmlns="http://www.w3.org/2000/svg" className="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
</button>
)}
{view === "phone" && (
<div className="relative">
<button
onClick={onClose}
className="absolute top-4 right-4 z-10 text-gray-500 hover:text-dark-900 transition-colors"
>
<svg xmlns="http://www.w3.org/2000/svg" className="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
</button>
</div>
)}
{/* Content View Switching */}
{view === "phone" && (
<PhoneInputScreen
onRequestOtp={() => setView("otp")}
onCancel={onClose}
onGoogleSuccess={async (tokenResponse) => {
try {
const res = await fetch(`https://www.googleapis.com/oauth2/v1/userinfo?access_token=${tokenResponse.access_token}`);
const data = await res.json();
setUser(data);
setView("profile");
} catch (err) {
alert("Failed to fetch Google profile.");
}
}}
onRegisterSuccess={(formData) => {
// Creating a mock user from the native registration form
const newUser = {
name: formData.name,
email: formData.email,
picture: `https://ui-avatars.com/api/?name=${formData.name.replace(' ', '+')}&background=random`,
isNewRegistration: true
};
setUser(newUser);
setView("profile");
}}
/>
)}
{view === "otp" && (
<OtpVerificationScreen
onBack={() => setView("phone")}
onSuccess={() => {
// Create a dummy user for the OTP flow demo
const fakeUser = {
name: "Customer",
email: "customer@phone.login",
picture: "https://ui-avatars.com/api/?name=Customer&background=random"
};
setUser(fakeUser);
setView("profile");
}}
/>
)}
{view === "profile" && (
<UserProfileScreen
user={user}
onLogout={() => {
setUser(null);
onClose();
}}
/>
)}
</div>
</div>
);
};
export default AuthModal;

View file

@ -0,0 +1,86 @@
import React, { useState, useRef, useEffect } from "react";
const OtpVerificationScreen = ({ onBack, onSuccess }) => {
const [otp, setOtp] = useState(["", "", "", "", "", ""]);
const [timeLeft, setTimeLeft] = useState(20);
const inputRefs = useRef([]);
useEffect(() => {
if (timeLeft > 0) {
const timer = setTimeout(() => setTimeLeft(timeLeft - 1), 1000);
return () => clearTimeout(timer);
}
}, [timeLeft]);
const handleChange = (index, value) => {
if (isNaN(value)) return;
const newOtp = [...otp];
newOtp[index] = value;
setOtp(newOtp);
// Auto-focus next input
if (value !== "" && index < 5) {
inputRefs.current[index + 1].focus();
}
};
const handleKeyDown = (index, e) => {
// Auto-focus previous input on backspace if current is empty
if (e.key === "Backspace" && index > 0 && otp[index] === "") {
inputRefs.current[index - 1].focus();
}
};
return (
<div className="w-full max-w-sm mx-auto p-4 sm:p-8 animate-fadeInUp">
<div className="text-center mb-8">
<h2 className="text-2xl font-bold text-dark-900 mb-2">Verify Your Number</h2>
<p className="text-sm text-dark-700 leading-relaxed">
We've sent a 6-digit code to your phone. Please enter it below to continue.
</p>
</div>
<div className="flex gap-2 justify-center mb-6">
{otp.map((data, index) => (
<input
key={index}
type="text"
maxLength={1}
ref={(el) => (inputRefs.current[index] = el)}
value={data}
onChange={(e) => handleChange(index, e.target.value)}
onKeyDown={(e) => handleKeyDown(index, e)}
className="w-10 h-10 sm:w-12 sm:h-12 border border-gray-300 rounded-lg text-center text-lg font-bold text-dark-900 focus:border-primary-600 focus:ring-1 focus:ring-primary-600 outline-none transition-all"
/>
))}
</div>
<button
onClick={onSuccess}
className="w-full bg-primary-600 hover:bg-primary-700 text-white font-bold text-sm py-3.5 rounded-2xl mb-4 transition-colors"
>
Continue
</button>
<div className="text-center mb-6">
{timeLeft > 0 ? (
<p className="text-sm text-dark-400">Resend OTP in {timeLeft}s</p>
) : (
<button className="text-sm text-primary-600 font-semibold hover:text-primary-700">
Resend OTP
</button>
)}
</div>
<div className="flex justify-between items-center text-sm font-semibold text-primary-600 mt-2">
<button onClick={onBack} className="flex items-center gap-1 hover:text-primary-700">
<svg xmlns="http://www.w3.org/2000/svg" className="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="m15 18-6-6 6-6"/></svg>
Edit number
</button>
</div>
</div>
);
};
export default OtpVerificationScreen;

View file

@ -0,0 +1,232 @@
import React, { useState } from "react";
import { useGoogleLogin } from "@react-oauth/google";
import { api } from "../../lib/api";
const PhoneInputScreen = ({ onRequestOtp, onCancel, onGoogleSuccess, onRegisterSuccess }) => {
const [activeTab, setActiveTab] = useState("login");
// Register Form State
const [name, setName] = useState("");
const [email, setEmail] = useState("");
const [phone, setPhone] = useState("");
const [password, setPassword] = useState("");
const [isRegistering, setIsRegistering] = useState(false);
const [errorMsg, setErrorMsg] = useState("");
const login = useGoogleLogin({
onSuccess: onGoogleSuccess,
onError: () => alert("Login Failed")
});
const handleRegister = async () => {
if (!name || !email || !phone || !password) {
setErrorMsg("Please fill all fields.");
return;
}
setIsRegistering(true);
setErrorMsg("");
try {
// Connects cleanly with the api configuration defined by VITE_API_URL or defaulted to local proxy.
const response = await api.post("/api/auth/register", {
name,
email,
phone,
password,
});
// Pass the successful name/email down the line dynamically
onRegisterSuccess({ name: response.user?.name || name, email: response.user?.email || email, phone: response.user?.phone || phone });
} catch (err) {
if (err.message.includes("409") || err.message.toLowerCase().includes("conflict") || err.message.toLowerCase().includes("already registered")) {
setErrorMsg("Email already registered. Did you mean to log in?");
} else {
setErrorMsg(err.message || "Registration failed. Please try again.");
}
} finally {
setIsRegistering(false);
}
};
return (
<div className="flex flex-col md:flex-row w-full h-full sm:h-[450px]">
{/* Left Promo Column */}
<div className="hidden md:flex flex-col md:w-5/12 relative rounded-l-2xl overflow-hidden bg-dark-900">
<img
src="https://images.unsplash.com/photo-1549317661-bd32c8ce0db2?auto=format&fit=crop&q=80&w=800"
alt="Steering wheel"
className="absolute inset-0 w-full h-full object-cover opacity-60 mix-blend-overlay"
/>
<div className="relative z-10 flex flex-col justify-end h-full p-8 pb-10 bg-gradient-to-t from-black/80 to-transparent">
<h2 className="text-4xl font-extrabold text-white mb-2 leading-tight">10% OFF<br />
<span className="text-xl font-bold">on your first booking!</span>
</h2>
<p className="text-sm text-gray-300 mb-6">
Use the coupon below to get flat 10% OFF on your first booking with road.rentals.
</p>
<div className="flex items-center justify-between border border-white/20 rounded-xl px-4 py-3 bg-black/40 backdrop-blur-md">
<span className="text-white font-mono font-bold tracking-wider">RE1ST10</span>
<button className="text-white hover:text-primary-400 transition-colors">
<svg xmlns="http://www.w3.org/2000/svg" className="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><rect width="14" height="14" x="8" y="8" rx="2" ry="2" /><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" /></svg>
</button>
</div>
</div>
</div>
{/* Right Auth Column */}
<div className="flex-1 p-6 sm:p-10 flex flex-col justify-center bg-white rounded-r-2xl animate-fadeInUp overflow-y-auto">
{/* Tab Switcher */}
<div className="flex bg-gray-100 rounded-xl p-1 mb-8 w-full max-w-[280px]">
<button
onClick={() => setActiveTab("login")}
className={`flex-1 text-sm font-bold py-2 rounded-lg transition-all ${activeTab === 'login' ? 'bg-white text-dark-900 shadow-sm' : 'text-gray-500 hover:text-dark-700'}`}
>
Log In
</button>
<button
onClick={() => setActiveTab("register")}
className={`flex-1 text-sm font-bold py-2 rounded-lg transition-all ${activeTab === 'register' ? 'bg-white text-dark-900 shadow-sm' : 'text-gray-500 hover:text-dark-700'}`}
>
Register
</button>
</div>
{activeTab === "login" ? (
<>
<h2 className="text-2xl font-bold text-dark-900 mb-2">Welcome Back</h2>
<p className="text-sm text-dark-500 mb-8 leading-relaxed">
Sign in to book your rides, manage trips, and enjoy a seamless travel experience
</p>
{/* Phone Input Box */}
<div className="flex rounded-xl border border-gray-300 focus-within:border-primary-600 focus-within:ring-1 focus-within:ring-primary-600 transition-all mb-6">
<div className="flex flex-shrink-0 items-center gap-1.5 px-4 bg-gray-50 border-r border-gray-300 rounded-l-xl">
<span className="text-xl">🇮🇳</span>
<svg xmlns="http://www.w3.org/2000/svg" className="w-4 h-4 text-dark-700" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="m6 9 6 6 6-6" /></svg>
</div>
<input
type="tel"
placeholder="Phone number"
className="flex-1 w-full outline-none px-4 py-3.5 text-sm font-semibold rounded-r-xl placeholder:font-normal"
/>
</div>
{/* Buttons Row */}
<div className="grid grid-cols-2 gap-3 mb-6">
<button
onClick={onCancel}
className="w-full bg-gray-50 border border-gray-200 hover:bg-gray-100 text-dark-900 font-bold text-sm py-3.5 rounded-xl transition-colors"
>
Cancel
</button>
<button
onClick={onRequestOtp}
className="w-full bg-primary-600 hover:bg-primary-700 text-white font-bold text-sm py-3.5 rounded-xl transition-colors"
>
Request OTP
</button>
</div>
</>
) : (
<>
<h2 className="text-2xl font-bold text-dark-900 mb-2">Create an Account</h2>
<p className="text-sm text-dark-500 mb-6 leading-relaxed">
Register now to start your reliable travel journey with road.rentals
</p>
{/* Register Fields */}
<div className="space-y-4 mb-6">
<input
type="text"
placeholder="Full Name"
value={name}
onChange={(e) => setName(e.target.value)}
className="w-full rounded-xl border border-gray-300 focus:border-primary-600 focus:ring-1 focus:ring-primary-600 outline-none px-4 py-3.5 text-sm transition-all"
/>
<input
type="email"
placeholder="Email Address"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="w-full rounded-xl border border-gray-300 focus:border-primary-600 focus:ring-1 focus:ring-primary-600 outline-none px-4 py-3.5 text-sm transition-all"
/>
<div className="flex rounded-xl border border-gray-300 focus-within:border-primary-600 focus-within:ring-1 focus-within:ring-primary-600 transition-all">
<div className="flex flex-shrink-0 items-center gap-1.5 px-4 bg-gray-50 border-r border-gray-300 rounded-l-xl">
<span className="text-xl">🇮🇳</span>
<svg xmlns="http://www.w3.org/2000/svg" className="w-4 h-4 text-dark-700" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="m6 9 6 6 6-6" /></svg>
</div>
<input
type="tel"
placeholder="Mobile Number"
value={phone}
onChange={(e) => setPhone(e.target.value)}
className="flex-1 w-full outline-none px-4 py-3.5 text-sm font-semibold rounded-r-xl placeholder:font-normal"
/>
</div>
<input
type="password"
placeholder="Secure Password"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="w-full rounded-xl border border-gray-300 focus:border-primary-600 focus:ring-1 focus:ring-primary-600 outline-none px-4 py-3.5 text-sm transition-all"
/>
{errorMsg && (
<div className="text-red-500 text-sm font-medium mt-2 px-1">
{errorMsg}
</div>
)}
</div>
{/* Buttons Row */}
<div className="grid grid-cols-2 gap-3 mb-6">
<button
onClick={onCancel}
className="w-full bg-gray-50 border border-gray-200 hover:bg-gray-100 text-dark-900 font-bold text-sm py-3.5 rounded-xl transition-colors"
>
Cancel
</button>
<button
onClick={handleRegister}
disabled={isRegistering}
className="w-full bg-primary-600 hover:bg-primary-700 text-white font-bold text-sm py-3.5 rounded-xl transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
>
{isRegistering ? "Creating Account..." : "Create Account"}
</button>
</div>
</>
)}
{/* Divider */}
<div className="relative flex items-center mb-6">
<div className="flex-grow border-t border-gray-100"></div>
<span className="flex-shrink-0 mx-4 text-gray-400 text-sm">or</span>
<div className="flex-grow border-t border-gray-100"></div>
</div>
{/* Google Btn */}
<button
onClick={() => login()}
className="w-full flex items-center justify-center gap-3 bg-gray-50 border border-dark-200 hover:bg-gray-100 text-dark-900 font-bold text-sm py-3.5 rounded-xl transition-colors"
>
<svg className="w-5 h-5" viewBox="0 0 48 48">
<path fill="#EA4335" d="M24 9.5c3.54 0 6.71 1.22 9.21 3.6l6.85-6.85C35.9 2.38 30.47 0 24 0 14.62 0 6.51 5.38 2.56 13.22l7.98 6.19C12.43 13.72 17.74 9.5 24 9.5z" />
<path fill="#4285F4" d="M46.98 24.55c0-1.57-.15-3.09-.38-4.55H24v9.02h12.94c-.58 2.96-2.26 5.48-4.78 7.18l7.73 6c4.51-4.18 7.09-10.36 7.09-17.65z" />
<path fill="#FBBC05" d="M10.53 28.59c-.48-1.45-.76-2.99-.76-4.59s.27-3.14.76-4.59l-7.98-6.19C.92 16.46 0 20.12 0 24c0 3.88.92 7.54 2.56 10.78l7.97-6.19z" />
<path fill="#34A853" d="M24 48c6.48 0 11.93-2.13 15.89-5.81l-7.73-6c-2.15 1.45-4.92 2.3-8.16 2.3-6.26 0-11.57-4.22-13.47-9.91l-7.98 6.19C6.51 42.62 14.62 48 24 48z" />
</svg>
Login with Google
</button>
</div>
</div>
);
};
export default PhoneInputScreen;

View file

@ -0,0 +1,45 @@
import React from "react";
const UserProfileScreen = ({ user, onLogout }) => {
if (!user) return null;
return (
<div className="w-full max-w-sm mx-auto p-4 sm:p-8 animate-fadeInUp flex flex-col items-center">
{/* Success Checkmark */}
<div className="w-16 h-16 bg-green-100 rounded-full flex items-center justify-center mb-6">
<svg className="w-8 h-8 text-green-500" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round">
<polyline points="20 6 9 17 4 12" />
</svg>
</div>
<h2 className="text-2xl font-bold text-dark-900 mb-2">
{user.isNewRegistration ? "Successfully Registered!" : "Successfully Logged In!"}
</h2>
<p className="text-sm text-dark-500 mb-8 text-center">
Welcome to your road.rentals dashboard.
</p>
{/* User Info Card */}
<div className="w-full bg-gray-50 border border-gray-200 rounded-2xl p-6 flex flex-col items-center mb-6">
<img
src={user.picture}
alt={user.name}
className="w-20 h-20 rounded-full border-4 border-white shadow-md mb-4 object-cover"
/>
<h3 className="text-lg font-bold text-dark-900">{user.name}</h3>
<p className="text-sm text-dark-500">{user.email}</p>
</div>
{/* Logout Button */}
<button
onClick={onLogout}
className="w-full bg-red-50 hover:bg-red-100 text-red-600 font-bold text-sm py-3.5 rounded-xl transition-colors flex items-center justify-center gap-2"
>
<svg xmlns="http://www.w3.org/2000/svg" className="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" y1="12" x2="9" y2="12"/></svg>
Logout
</button>
</div>
);
};
export default UserProfileScreen;

View file

@ -0,0 +1,29 @@
import { useState } from "react";
import { cabsData } from "../../data/cabsData";
import { filterTabs } from "../../constants/filterTabs";
import SectionHeader from "./SectionHeader";
import VehicleGrid from "./VehicleGrid";
import FilterTabs from "./FilterTabs";
const CabsSection = () => {
const [activeTab, setActiveTab] = useState("driver-rentals");
return (
<section id="fleet" className="py-16 md:py-20 px-4 sm:px-6 lg:px-8 bg-section-bg">
<div className="max-w-7xl mx-auto">
<SectionHeader
title="Our Fleet Affordable to Luxury"
subtitle="Choose from our wide range of well-maintained vehicles for local trips, outstation travel, and airport transfers across Bangalore."
/>
<FilterTabs
tabs={filterTabs}
activeTab={activeTab}
onTabChange={setActiveTab}
/>
<VehicleGrid vehicles={cabsData} />
</div>
</section>
);
};
export default CabsSection;

View file

@ -0,0 +1,38 @@
const FilterTabs = ({ tabs, activeTab, onTabChange }) => {
return (
<div className="flex justify-center mb-10">
<div className="inline-flex gap-1 p-2 bg-white/40 backdrop-blur-xl rounded-2xl shadow-[0_8px_30px_rgb(0,0,0,0.08)] border border-white/60 relative z-10 transition-all duration-300 hover:bg-white/50 hover:shadow-[0_8px_30px_rgb(0,0,0,0.12)]">
{tabs.map((tab) => {
const isActive = tab.key === activeTab;
return (
<button
key={tab.key}
onClick={() => !tab.disabled && onTabChange(tab.key)}
className={`
group relative px-5 py-2.5 text-sm font-medium rounded-lg
transition-all duration-250 ease-out
${
tab.disabled
? "opacity-60 cursor-default bg-transparent text-gray-400"
: isActive
? "bg-primary-600 text-white shadow-md cursor-pointer"
: "text-dark-700 hover:bg-white/40 hover:text-primary-600 cursor-pointer"
}
`}
>
{tab.label}
{tab.disabled && (
<span className="absolute -top-10 left-1/2 -translate-x-1/2 px-3 py-1 bg-dark-900 text-white text-xs font-bold rounded shadow-lg opacity-0 group-hover:opacity-100 transition-opacity duration-200 whitespace-nowrap pointer-events-none z-10">
Coming Soon
<span className="absolute top-full left-1/2 -translate-x-1/2 border-[5px] border-transparent border-t-dark-900"></span>
</span>
)}
</button>
);
})}
</div>
</div>
);
};
export default FilterTabs;

View file

@ -0,0 +1,17 @@
const SectionHeader = ({ title, subtitle }) => {
return (
<div className="text-center mb-10">
<h2 className="text-3xl md:text-4xl font-bold text-dark-900 tracking-tight">
{title}
</h2>
{subtitle && (
<p className="mt-3 text-base text-badge-text max-w-2xl mx-auto leading-relaxed">
{subtitle}
</p>
)}
<div className="mt-4 mx-auto w-16 h-1 rounded-full bg-primary-600" />
</div>
);
};
export default SectionHeader;

View file

@ -0,0 +1,35 @@
const featureIcons = {
"Air Conditioner": (
<svg xmlns="http://www.w3.org/2000/svg" className="w-3.5 h-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="M12 2v10" /><path d="m4.93 10.93 1.41 1.41" /><path d="M2 18h2" /><path d="M20 18h2" /><path d="m19.07 10.93-1.41 1.41" /><path d="M22 22H2" /><path d="m8 6 4-4 4 4" /><path d="M16 18a4 4 0 0 0-8 0" />
</svg>
),
"Audio System": (
<svg xmlns="http://www.w3.org/2000/svg" className="w-3.5 h-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="M9 18V5l12-2v13" /><circle cx="6" cy="18" r="3" /><circle cx="18" cy="16" r="3" />
</svg>
),
"Spacious Leg Space": (
<svg xmlns="http://www.w3.org/2000/svg" className="w-3.5 h-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="M19 9V6a2 2 0 0 0-2-2H7a2 2 0 0 0-2 2v3" /><path d="M3 16h18" /><path d="M3 11h2a2 2 0 0 1 2 2v3" /><path d="M17 11h2a2 2 0 0 1 2 2v3" />
</svg>
),
};
const VehicleBadges = ({ features }) => {
return (
<div className="flex flex-wrap gap-2 px-5 pb-3">
{features.map((feature) => (
<span
key={feature}
className="inline-flex items-center gap-1.5 px-2.5 py-1 bg-badge-bg text-badge-text text-xs font-medium rounded-md"
>
{featureIcons[feature] || null}
{feature}
</span>
))}
</div>
);
};
export default VehicleBadges;

View file

@ -0,0 +1,17 @@
import VehicleImage from "./VehicleImage";
import VehicleInfo from "./VehicleInfo";
import VehicleBadges from "./VehicleBadges";
import ViewDetailsBtn from "./ViewDetailsBtn";
const VehicleCard = ({ vehicle }) => {
return (
<article className="group bg-white rounded-xl shadow-card hover:shadow-card-hover transition-all duration-300 ease-out overflow-hidden">
<VehicleImage src={vehicle.image} alt={vehicle.name} />
<VehicleInfo name={vehicle.name} seats={vehicle.seats} />
<VehicleBadges features={vehicle.features} />
<ViewDetailsBtn price={vehicle.price} detailUrl={vehicle.detailUrl} />
</article>
);
};
export default VehicleCard;

View file

@ -0,0 +1,14 @@
const VehicleImage = ({ src, alt }) => {
return (
<div className="w-full h-52 overflow-hidden rounded-t-xl bg-gray-100">
<img
src={src}
alt={alt}
className="w-full h-full object-cover transition-transform duration-500 ease-out group-hover:scale-110"
loading="lazy"
/>
</div>
);
};
export default VehicleImage;

View file

@ -0,0 +1,29 @@
const VehicleInfo = ({ name, seats }) => {
return (
<div className="px-5 pt-4 pb-2">
<h3 className="text-lg font-semibold text-dark-900 leading-tight">
{name}
</h3>
<div className="flex items-center gap-1.5 mt-1.5 text-badge-text text-sm">
<svg
xmlns="http://www.w3.org/2000/svg"
className="w-4 h-4"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2" />
<circle cx="9" cy="7" r="4" />
<path d="M22 21v-2a4 4 0 0 0-3-3.87" />
<path d="M16 3.13a4 4 0 0 1 0 7.75" />
</svg>
<span>{seats} seats</span>
</div>
</div>
);
};
export default VehicleInfo;

View file

@ -0,0 +1,31 @@
const ViewDetailsBtn = ({ price, detailUrl }) => {
return (
<div className="flex items-center justify-between px-5 py-3.5 border-t border-gray-100">
<p className="text-sm text-dark-700">
Starting from{" "}
<span className="font-semibold text-dark-900">{price}</span>
</p>
<a
href="#hero"
className="inline-flex items-center gap-1 text-sm font-semibold text-primary-600 hover:text-primary-700 transition-colors duration-200"
>
View Details
<svg
xmlns="http://www.w3.org/2000/svg"
className="w-4 h-4 transition-transform duration-200 group-hover:translate-x-0.5"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2.5"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M5 12h14" />
<path d="m12 5 7 7-7 7" />
</svg>
</a>
</div>
);
};
export default ViewDetailsBtn;

View file

@ -0,0 +1,61 @@
import { useState } from "react";
import VehicleCard from "./VehicleCard/VehicleCard";
const VehicleGrid = ({ vehicles }) => {
const [showAll, setShowAll] = useState(false);
const visibleVehicles = showAll ? vehicles : vehicles.slice(0, 6);
if (vehicles.length === 0) {
return (
<div className="text-center py-16">
<div className="text-6xl mb-4">🚗</div>
<p className="text-lg text-badge-text font-medium">
No vehicles found in this category.
</p>
<p className="text-sm text-gray-400 mt-1">
Try selecting a different filter above.
</p>
</div>
);
}
return (
<>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
{visibleVehicles.map((vehicle, index) => (
<div
key={vehicle.id}
className="opacity-0 animate-fadeInUp"
style={{ animationDelay: `${index * 60}ms`, animationFillMode: "forwards" }}
>
<VehicleCard vehicle={vehicle} />
</div>
))}
</div>
{vehicles.length > 6 && (
<div className="flex justify-center mt-12">
<button
onClick={() => setShowAll(!showAll)}
className="inline-flex items-center gap-2 px-8 py-3.5 border-2 border-primary-600 text-primary-600 font-bold text-sm rounded-xl hover:bg-primary-600 hover:text-white transition-all duration-300 shadow-sm"
>
{showAll ? "Show Less" : "Show More"}
<svg
xmlns="http://www.w3.org/2000/svg"
className={`w-4 h-4 transition-transform duration-300 ${showAll ? "rotate-180" : ""}`}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2.5"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="m6 9 6 6 6-6"/>
</svg>
</button>
</div>
)}
</>
);
};
export default VehicleGrid;

View file

@ -0,0 +1,90 @@
import Logo from "../Logo/Logo";
const Footer = () => {
const scrollToHero = (e) => {
e.preventDefault();
document.getElementById("hero")?.scrollIntoView({ behavior: "smooth" });
};
return (
<footer id="contact" className="bg-[#01172f] text-white relative border-t border-dark-900 overflow-hidden">
{/* Cityscape Silhouette Background */}
<div
className="absolute bottom-0 left-0 w-full h-[300px] pointer-events-none bg-[length:100%_100%] bg-bottom bg-no-repeat opacity-90"
style={{ backgroundImage: "url('https://ik.imagekit.io/fofu0hrhb/footerbgimage.png')" }}
/>
<div className="relative z-10 max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 pt-16 pb-8">
{/* Middle Links Grid */}
<div className="grid grid-cols-2 lg:grid-cols-5 gap-8 mb-16 relative z-10">
<div>
<h4 className="font-bold text-lg mb-6 text-white">Our Fleet</h4>
<ul className="space-y-4">
{["Sedan Cabs", "SUV Cabs", "Premium SUVs", "Luxury Rides"].map(item => (
<li key={item}><a href="#" onClick={scrollToHero} className="text-[#93c5fd] hover:text-white text-sm transition-colors">{item}</a></li>
))}
</ul>
</div>
<div>
<h4 className="font-bold text-lg mb-6 text-white">Services</h4>
<ul className="space-y-4">
{["Corporate Travel", "Airport Transfers", "Family Trips", "City Tours", "Outstation Travel", "Wedding Cabs", "Holiday Packages"].map(item => (
<li key={item}><a href="#" onClick={scrollToHero} className="text-[#93c5fd] hover:text-white text-sm transition-colors">{item}</a></li>
))}
</ul>
</div>
<div>
<h4 className="font-bold text-lg mb-6 text-white">Luxury Cabs</h4>
<ul className="space-y-4">
{["Luxury Sedan", "Luxury SUV", "Premium Executive Cabs"].map(item => (
<li key={item}><a href="#" onClick={scrollToHero} className="text-[#93c5fd] hover:text-white text-sm transition-colors">{item}</a></li>
))}
</ul>
</div>
<div>
<h4 className="font-bold text-lg mb-6 text-white">Other Links</h4>
<ul className="space-y-4">
{["About Us", "Contact Us", "Blogs", "Cancellation Policy", "Terms & Conditions", "Privacy Policy", "Pricing Policy", "Partner With Us"].map(item => (
<li key={item}><a href="#" onClick={scrollToHero} className="text-[#93c5fd] hover:text-white text-sm transition-colors">{item}</a></li>
))}
</ul>
</div>
<div className="col-span-2 lg:col-span-1">
<Logo lightText={true} className="mb-6" />
<p className="text-[#93c5fd] text-sm leading-relaxed mb-6">
We are a popular and leading Cab and Premium Tours company based out of Bangalore. road.rentals has been trusted by hundreds of happy travellers for several decades now as a renowned and acclaimed as the best travel service provider based out of Bangalore.
</p>
{/* Social Icons Placeholder */}
<div className="flex gap-3">
<a href="#" className="w-9 h-9 rounded-full bg-[#1e293b] flex items-center justify-center text-[#93c5fd] hover:bg-primary-500 hover:text-white transition-colors">
<svg className="w-4 h-4 fill-current" viewBox="0 0 24 24"><path d="M21.582 6.186a2.684 2.684 0 0 0-1.884-1.889C18.035 3.8 12 3.8 12 3.8s-6.035 0-7.698.497a2.684 2.684 0 0 0-1.884 1.889C1.922 7.85 1.922 12 1.922 12s0 4.15.5 5.814a2.685 2.685 0 0 0 1.884 1.889c1.664.497 7.698.497 7.698.497s6.035 0 7.698-.497a2.685 2.685 0 0 0 1.884-1.889c.496-1.664.496-5.814.496-5.814s0-4.15-.496-5.814zm-14.28 9.38v-7.13L13.71 12l-6.408 3.566z" /></svg>
</a>
<a href="#" className="w-9 h-9 rounded-full bg-[#1e293b] flex items-center justify-center text-[#93c5fd] hover:bg-primary-500 hover:text-white transition-colors">
<svg className="w-4 h-4 fill-current" viewBox="0 0 24 24"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z" /></svg>
</a>
<a href="#" className="w-9 h-9 rounded-full bg-[#1e293b] flex items-center justify-center text-[#93c5fd] hover:bg-primary-500 hover:text-white transition-colors">
<svg className="w-4 h-4 fill-current" viewBox="0 0 24 24"><path d="M12 2C6.477 2 2 6.477 2 12c0 4.991 3.657 9.128 8.438 9.879V14.89h-2.54V12h2.54V9.797c0-2.506 1.492-3.89 3.777-3.89 1.094 0 2.238.195 2.238.195v2.46h-1.26c-1.243 0-1.63.771-1.63 1.562V12h2.773l-.443 2.89h-2.33v6.989C18.343 21.129 22 16.99 22 12c0-5.523-4.477-10-10-10z" /></svg>
</a>
<a href="#" className="w-9 h-9 rounded-full bg-[#1e293b] flex items-center justify-center text-[#93c5fd] hover:bg-primary-500 hover:text-white transition-colors">
<svg className="w-4 h-4 fill-current" viewBox="0 0 24 24"><path d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zm0-2.163c-3.259 0-3.667.014-4.947.072-4.358.2-6.78 2.618-6.98 6.98-.059 1.281-.073 1.689-.073 4.948 0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98 1.281.058 1.689.072 4.948.072 3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98C15.668.014 15.259 0 12 0zm0 5.838a6.162 6.162 0 1 0 0 12.324 6.162 6.162 0 0 0 0-12.324zM12 16a4 4 0 1 1 0-8 4 4 0 0 1 0 8zm7.846-10.405a1.44 1.44 0 1 1-2.88 0 1.44 1.44 0 0 1 2.88 0z" /></svg>
</a>
</div>
</div>
</div>
{/* Bottom Banner */}
<div className="border-t border-dark-800 pt-8 flex flex-col md:flex-row justify-between items-center gap-4">
<p className="text-gray-500 text-sm">
Copyright © 2018-2025 road.rentals Tours & Travels. All Rights Reserved.
</p>
</div>
</div>
</footer>
);
};
export default Footer;

View file

@ -0,0 +1,212 @@
import { useState } from "react";
import heroBg from "../../assets/hero_bg.png";
const tripTypes = [
{
key: "round",
label: "Round Trip",
icon: (
<svg xmlns="http://www.w3.org/2000/svg" className="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M17 2l4 4-4 4"/><path d="M3 11v-1a4 4 0 0 1 4-4h14"/><path d="M7 22l-4-4 4-4"/><path d="M21 13v1a4 4 0 0 1-4 4H3"/></svg>
),
},
{
key: "oneway",
label: "One Way",
icon: (
<svg xmlns="http://www.w3.org/2000/svg" className="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M5 12h14"/><path d="m12 5 7 7-7 7"/></svg>
),
},
{
key: "local",
label: "Local Trip",
icon: (
<svg xmlns="http://www.w3.org/2000/svg" className="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="10"/><path d="M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20"/><path d="M2 12h20"/></svg>
),
},
{
key: "airport",
label: "Airport Trip",
icon: (
<svg xmlns="http://www.w3.org/2000/svg" className="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M17.8 19.2 16 11l3.5-3.5C21 6 21.5 4 21 3c-1-.5-3 0-4.5 1.5L13 8 4.8 6.2c-.5-.1-.9.1-1.1.5l-.3.5c-.2.5-.1 1 .3 1.3L9 12l-2 3H4l-1 1 3 2 2 3 1-1v-3l3-2 3.5 5.3c.3.4.8.5 1.3.3l.5-.2c.4-.3.6-.7.5-1.2z"/></svg>
),
},
];
const localPackages = [
"4Hr 40Km",
"8Hr 80Km",
"12Hr 120Km",
];
const HeroSection = () => {
const [activeTrip, setActiveTrip] = useState("round");
const [selectedPackage, setSelectedPackage] = useState(localPackages[0]);
const getFormFields = () => {
switch (activeTrip) {
case "round":
return (
<>
<FormField icon="location" label="From City" placeholder="Enter Pickup City" />
<Divider />
<FormField icon="destination" label="To City" placeholder="Enter Drop Location" />
<Divider />
<FormField icon="calendar" label="Pickup date & time" placeholder="Select Date" />
<Divider />
<FormField icon="calendar" label="Return date & time" placeholder="Select Date" />
</>
);
case "oneway":
return (
<>
<FormField icon="location" label="From City" placeholder="Enter Pickup City" />
<Divider />
<FormField icon="destination" label="To City" placeholder="Enter Drop Location" />
<Divider />
<FormField icon="calendar" label="Pickup date & time" placeholder="Select Date" />
</>
);
case "local":
return (
<>
<FormField icon="location" label="From City" placeholder="Enter Pickup City" />
<Divider />
<div className="flex-1 min-w-[160px] px-4 py-3">
<div className="flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" className="w-4 h-4 text-primary-600 flex-shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><rect width="20" height="14" x="2" y="7" rx="2" ry="2"/><path d="M16 21V5a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v16"/></svg>
<div>
<p className="text-[11px] text-badge-text font-medium leading-none mb-1">Select Package</p>
<select
value={selectedPackage}
onChange={(e) => setSelectedPackage(e.target.value)}
className="text-sm font-semibold text-dark-900 bg-transparent border-none outline-none cursor-pointer appearance-none"
>
{localPackages.map((pkg) => (
<option key={pkg} value={pkg}>{pkg}</option>
))}
</select>
</div>
</div>
</div>
<Divider />
<FormField icon="calendar" label="Pickup date & time" placeholder="Select Date" />
</>
);
case "airport":
return (
<>
<FormField icon="location" label="From" placeholder="Enter Pickup Location" />
<Divider />
<FormField icon="destination" label="Airport" placeholder="Enter Airport Name" />
<Divider />
<FormField icon="calendar" label="Pickup date & time" placeholder="Select Date" />
</>
);
default:
return null;
}
};
return (
<section id="hero" className="relative min-h-[520px] md:min-h-[580px] flex flex-col items-center justify-center overflow-hidden">
{/* Background */}
<div className="absolute inset-0">
<img
src={heroBg}
alt=""
className="w-full h-full object-cover"
/>
<div className="absolute inset-0 bg-gradient-to-b from-dark-900/70 via-dark-900/50 to-dark-900/70" />
</div>
{/* Content */}
<div className="relative z-10 w-full max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12 md:py-16">
{/* Heading */}
<h1 className="text-center text-3xl sm:text-4xl md:text-5xl font-bold text-white leading-tight mb-10 md:mb-14 drop-shadow-lg">
Your Memorable Travel<br className="hidden sm:block" /> Experience begins here
</h1>
{/* Booking widget */}
<div className="max-w-5xl mx-auto">
{/* Trip tabs */}
<div className="flex justify-center mb-4">
<div className="inline-flex gap-1 p-1.5 bg-white/90 backdrop-blur-sm rounded-2xl shadow-lg border border-white/20">
{tripTypes.map((trip) => {
const isActive = trip.key === activeTrip;
return (
<button
key={trip.key}
onClick={() => setActiveTrip(trip.key)}
className={`
flex items-center gap-2 px-4 sm:px-5 py-2.5 text-sm font-semibold rounded-xl
transition-all duration-250 ease-out cursor-pointer
${
isActive
? "bg-primary-600 text-white shadow-md shadow-primary-600/30"
: "text-dark-700 hover:bg-gray-50 hover:text-primary-600"
}
`}
>
{trip.icon}
<span className="hidden sm:inline">{trip.label}</span>
</button>
);
})}
</div>
</div>
{/* Form */}
<div className="bg-white/95 backdrop-blur-sm rounded-2xl shadow-xl p-2">
<div className="flex flex-col sm:flex-row items-stretch sm:items-center gap-2 sm:gap-0">
{getFormFields()}
{/* CTA Button */}
<button className="flex-shrink-0 inline-flex items-center justify-center gap-2 bg-primary-600 hover:bg-primary-700 text-white font-semibold text-sm px-6 py-3.5 rounded-xl transition-all duration-200 shadow-md shadow-primary-600/20 hover:shadow-lg hover:shadow-primary-600/30 cursor-pointer mx-2 sm:mx-0 sm:ml-auto">
Start Booking Now
<svg xmlns="http://www.w3.org/2000/svg" className="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"><path d="M5 12h14"/><path d="m12 5 7 7-7 7"/></svg>
</button>
</div>
</div>
</div>
</div>
</section>
);
};
/* --- Sub-components --- */
const FormField = ({ icon, label, placeholder }) => {
const icons = {
location: (
<svg xmlns="http://www.w3.org/2000/svg" className="w-4 h-4 text-red-500 flex-shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M20 10c0 6-8 12-8 12s-8-6-8-12a8 8 0 0 1 16 0Z"/><circle cx="12" cy="10" r="3"/></svg>
),
destination: (
<svg xmlns="http://www.w3.org/2000/svg" className="w-4 h-4 text-primary-600 flex-shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M20 10c0 6-8 12-8 12s-8-6-8-12a8 8 0 0 1 16 0Z"/><circle cx="12" cy="10" r="3"/></svg>
),
calendar: (
<svg xmlns="http://www.w3.org/2000/svg" className="w-4 h-4 text-dark-700 flex-shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><rect width="18" height="18" x="3" y="4" rx="2" ry="2"/><line x1="16" x2="16" y1="2" y2="6"/><line x1="8" x2="8" y1="2" y2="6"/><line x1="3" x2="21" y1="10" y2="10"/></svg>
),
};
return (
<div className="flex-1 min-w-[160px] px-4 py-3">
<div className="flex items-center gap-2">
{icons[icon]}
<div>
<p className="text-[11px] text-badge-text font-medium leading-none mb-1">{label}</p>
<input
type="text"
placeholder={placeholder}
className="text-sm font-semibold text-dark-900 placeholder:text-gray-400 bg-transparent border-none outline-none w-full"
/>
</div>
</div>
</div>
);
};
const Divider = () => (
<div className="hidden sm:block w-px h-10 bg-gray-200 self-center flex-shrink-0" />
);
export default HeroSection;

View file

@ -0,0 +1,35 @@
import { highlightsData } from "../../data/homeData";
const WhyChooseUs = () => {
return (
<section id="about" className="py-16 bg-white px-4 sm:px-6 lg:px-8">
<div className="max-w-7xl mx-auto">
<h2 className="text-3xl font-bold text-center text-dark-900 mb-10">
Why Choose road.rentals Cabs & Travels?
</h2>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
{highlightsData.map((highlight) => (
<div key={highlight.id} className="flex flex-col text-center group cursor-pointer">
<div className="rounded-xl overflow-hidden h-40 mb-4 shadow-sm group-hover:shadow-md transition-all">
<img
src={highlight.image}
alt={highlight.title}
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-500"
/>
</div>
<h3 className="font-bold text-lg text-dark-900 mb-2 group-hover:text-primary-600 transition-colors">
{highlight.title}
</h3>
<p className="text-dark-700 text-sm leading-relaxed">
{highlight.description}
</p>
</div>
))}
</div>
</div>
</section>
);
};
export default WhyChooseUs;

View file

@ -0,0 +1,22 @@
import React from 'react';
const Logo = ({ className = "", lightText = false }) => {
return (
<div className={`flex items-center gap-2 ${className}`}>
<div className="bg-primary-600 text-white rounded-lg p-1.5 flex items-center justify-center shadow-sm">
{/* Car Icon SVG */}
<svg xmlns="http://www.w3.org/2000/svg" className="w-5 h-5 sm:w-6 sm:h-6" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
<path d="M19 17h2c.6 0 1-.4 1-1v-3c0-.9-.7-1.7-1.5-1.9C18.7 10.6 16 10 16 10s-1.3-1.4-2.2-2.3c-.5-.4-1.1-.7-1.8-.7H5c-.6 0-1.1.4-1.4.9l-1.4 2.9A3.7 3.7 0 0 0 2 12v4c0 .6.4 1 1 1h2" />
<circle cx="7" cy="17" r="2" />
<path d="M9 17h6" />
<circle cx="17" cy="17" r="2" />
</svg>
</div>
<span className={`text-xl sm:text-2xl font-extrabold tracking-tight ${lightText ? 'text-white' : 'text-dark-900'}`}>
road.<span className="text-primary-600">rentals</span>
</span>
</div>
);
};
export default Logo;

View file

@ -0,0 +1,128 @@
import { useState } from "react";
import Logo from "../Logo/Logo";
const Navbar = ({ onLoginClick, user }) => {
const [mobileOpen, setMobileOpen] = useState(false);
const navLinks = [
{ label: "Home", href: "#hero" },
{ label: "Our Fleet", href: "#fleet" },
{ label: "About", href: "#about" },
{ label: "Contact", href: "#contact" },
];
return (
<>
{/* Top info bar */}
<div className="bg-dark-900 text-white text-xs">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 flex items-center justify-between h-9">
<div className="flex items-center gap-5">
<a
href="mailto:info@road.rentals"
className="flex items-center gap-1.5 hover:text-primary-200 transition-colors"
>
<svg xmlns="http://www.w3.org/2000/svg" className="w-3.5 h-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><rect width="20" height="16" x="2" y="4" rx="2" /><path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7" /></svg>
info@road.rentals
</a>
<a
href="tel:+919980277773"
className="flex items-center gap-1.5 hover:text-primary-200 transition-colors"
>
<svg xmlns="http://www.w3.org/2000/svg" className="w-3.5 h-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z" /></svg>
+91 7975942520
</a>
</div>
<button onClick={onLoginClick} className="hidden sm:flex items-center gap-1.5 hover:text-primary-200 transition-colors font-semibold">
{user ? (
<>
<img src={user.picture} alt={user.name} className="w-5 h-5 rounded-full object-cover" />
Hi, {user.name.split(' ')[0]}
</>
) : (
<>
<svg xmlns="http://www.w3.org/2000/svg" className="w-3.5 h-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2" /><circle cx="12" cy="7" r="4" /></svg>
Login / Register
</>
)}
</button>
</div>
</div>
{/* Main navbar */}
<nav className="sticky top-0 z-50 bg-white/95 backdrop-blur-md border-b border-gray-100 shadow-sm">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex items-center justify-between h-16 md:h-[72px]">
{/* Logo */}
<a href="#" className="flex-shrink-0 hover:opacity-90 transition-opacity">
<Logo />
</a>
{/* Desktop links */}
<div className="hidden md:flex items-center gap-8">
{navLinks.map((link) => (
<a
key={link.label}
href={link.href}
className="text-sm font-semibold text-dark-900 hover:text-primary-600 transition-colors duration-200"
>
{link.label}
</a>
))}
</div>
{/* Book Now CTA */}
<div className="hidden md:flex items-center">
<a
href="#hero"
className="inline-flex items-center gap-2 px-5 py-2.5 border-2 border-primary-600 text-primary-600 font-semibold text-sm rounded-xl hover:bg-primary-600 hover:text-white transition-all duration-200"
>
<svg xmlns="http://www.w3.org/2000/svg" className="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><rect width="18" height="18" x="3" y="4" rx="2" ry="2" /><line x1="16" x2="16" y1="2" y2="6" /><line x1="8" x2="8" y1="2" y2="6" /><line x1="3" x2="21" y1="10" y2="10" /></svg>
Book Now
</a>
</div>
{/* Mobile hamburger */}
<button
onClick={() => setMobileOpen(!mobileOpen)}
className="md:hidden p-2 rounded-lg text-dark-900 hover:bg-gray-100 transition-colors"
>
{mobileOpen ? (
<svg xmlns="http://www.w3.org/2000/svg" className="w-6 h-6" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M18 6 6 18" /><path d="m6 6 12 12" /></svg>
) : (
<svg xmlns="http://www.w3.org/2000/svg" className="w-6 h-6" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><line x1="4" x2="20" y1="12" y2="12" /><line x1="4" x2="20" y1="6" y2="6" /><line x1="4" x2="20" y1="18" y2="18" /></svg>
)}
</button>
</div>
</div>
{/* Mobile menu */}
<div
className={`md:hidden overflow-hidden transition-all duration-300 ease-out ${mobileOpen ? "max-h-80 border-t border-gray-100" : "max-h-0"
}`}
>
<div className="px-4 py-4 space-y-1 bg-white">
{navLinks.map((link) => (
<a
key={link.label}
href={link.href}
className="block px-4 py-2.5 rounded-lg text-sm font-semibold text-dark-900 hover:bg-primary-50 hover:text-primary-600 transition-colors"
onClick={() => setMobileOpen(false)}
>
{link.label}
</a>
))}
<a
href="#hero"
className="block mx-4 mt-3 px-4 py-2.5 text-center bg-primary-600 text-white font-semibold text-sm rounded-xl"
onClick={() => setMobileOpen(false)}
>
Book Now
</a>
</div>
</div>
</nav>
</>
);
};
export default Navbar;

View file

@ -0,0 +1,36 @@
import { newsData } from "../../data/homeData";
const NewsSection = () => {
return (
<section className="py-16 bg-gray-50 px-4 sm:px-6 lg:px-8">
<div className="max-w-7xl mx-auto">
<h2 className="text-3xl font-bold text-center text-dark-900 mb-10">Latest News</h2>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
{newsData.map((news) => (
<div key={news.id} className="bg-white rounded-2xl overflow-hidden shadow-sm hover:shadow-md transition-shadow cursor-pointer group">
<div className="h-56 overflow-hidden">
<img
src={news.image}
alt={news.title}
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-500"
/>
</div>
<div className="p-5">
<h3 className="font-bold text-dark-900 text-base mb-4 group-hover:text-primary-600 transition-colors line-clamp-2">
{news.title}
</h3>
<div className="text-primary-600 text-sm font-semibold flex items-center justify-center gap-1">
Read More
<svg xmlns="http://www.w3.org/2000/svg" className="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="m9 18 6-6-6-6"/></svg>
</div>
</div>
</div>
))}
</div>
</div>
</section>
);
};
export default NewsSection;

View file

@ -0,0 +1,65 @@
const PromoBanners = () => {
return (
<section className="py-12 bg-gray-50 px-4 sm:px-6 lg:px-8">
<div className="max-w-7xl mx-auto">
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
{/* Banner 1: 10% OFF */}
<div className="relative rounded-2xl overflow-hidden h-48 md:h-56 group shadow-md cursor-pointer">
<img
src="https://images.unsplash.com/photo-1469854523086-cc02fe5d8800?auto=format&fit=crop&q=80&w=800"
alt="10% OFF"
className="absolute inset-0 w-full h-full object-cover group-hover:scale-105 transition-transform duration-500"
/>
<div className="absolute inset-0 bg-gradient-to-r from-green-900/80 to-transparent p-6 sm:p-8 flex flex-col justify-center">
<h3 className="text-white text-2xl font-bold mb-2">Get 10% OFF</h3>
<p className="text-green-50 text-sm mb-4">on your first booking, use code</p>
<div className="inline-flex w-fit px-4 py-2 border border-white/50 rounded-lg text-white font-semibold text-sm backdrop-blur-sm">
FIRSTRIP
</div>
</div>
</div>
{/* Banner 2: Beach Getaway */}
<div className="relative rounded-2xl overflow-hidden h-48 md:h-56 group shadow-md cursor-pointer">
<img
src="https://images.unsplash.com/photo-1507525428034-b723cf961d3e?auto=format&fit=crop&q=80&w=800"
alt="Beach Getaway"
className="absolute inset-0 w-full h-full object-cover group-hover:scale-105 transition-transform duration-500"
/>
<div className="absolute inset-0 bg-gradient-to-r from-primary-900/80 to-transparent p-6 sm:p-8 flex flex-col justify-center">
<h3 className="text-white text-xl md:text-2xl font-bold mb-2">Your Dream Beach Getaway Awaits!</h3>
<p className="text-blue-50 text-sm mb-4">7 days of sun, sand & pure relaxation!</p>
<button
onClick={() => document.getElementById("hero")?.scrollIntoView({ behavior: "smooth" })}
className="w-fit px-5 py-2 bg-white text-primary-900 font-bold text-sm rounded-lg hover:bg-gray-100 transition-colors"
>
Learn More
</button>
</div>
</div>
{/* Banner 3: Mysore */}
<div className="relative rounded-2xl overflow-hidden h-48 md:h-56 group shadow-md cursor-pointer">
<img
src="https://images.unsplash.com/photo-1524492412937-b28074a5d7da?auto=format&fit=crop&q=80&w=800"
alt="Mysore Getaway"
className="absolute inset-0 w-full h-full object-cover group-hover:scale-105 transition-transform duration-500"
/>
<div className="absolute inset-0 bg-gradient-to-r from-dark-900/80 to-transparent p-6 sm:p-8 flex flex-col justify-center">
<h3 className="text-white text-xl md:text-2xl font-bold mb-2">Royal Mysore Getaway!</h3>
<p className="text-gray-200 text-sm mb-4 line-clamp-2">Explore palaces, gardens & rich heritage in the City of Royals!</p>
<button
onClick={() => document.getElementById("hero")?.scrollIntoView({ behavior: "smooth" })}
className="w-fit px-5 py-2 bg-white text-dark-900 font-bold text-sm rounded-lg hover:bg-gray-100 transition-colors"
>
Learn More
</button>
</div>
</div>
</div>
</div>
</section>
);
};
export default PromoBanners;

View file

@ -0,0 +1,46 @@
import { reviewsData } from "../../data/homeData";
const ReviewsSection = () => {
return (
<section id="reviews" className="py-16 bg-white px-4 sm:px-6 lg:px-8">
<div className="max-w-7xl mx-auto">
<h2 className="text-3xl font-bold text-center text-dark-900 mb-10">Recent Reviews</h2>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
{reviewsData.map((review) => (
<div key={review.id} className="bg-white border border-gray-100 rounded-2xl p-6 shadow-sm hover:shadow-md transition-shadow">
{/* Header */}
<div className="flex justify-between items-start mb-4">
<div className="flex items-center gap-3">
<img src={review.avatar} alt={review.name} className="w-10 h-10 rounded-full" />
<div>
<h4 className="font-bold text-dark-900 text-sm">{review.name}</h4>
<span className="text-xs text-dark-400">{review.date}</span>
</div>
</div>
<div className="flex items-center gap-1 bg-primary-500 text-white px-2 py-1 rounded-md text-xs font-bold">
<svg xmlns="http://www.w3.org/2000/svg" className="w-3 h-3 fill-current" viewBox="0 0 24 24"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg>
{review.rating}/5
</div>
</div>
{/* Content */}
<h5 className="font-bold text-sm text-dark-900 mb-2">{review.title}</h5>
<p className="text-sm text-dark-700 leading-relaxed mb-3 line-clamp-4">
{review.text}
</p>
{/* Footer */}
<button className="text-primary-600 text-sm font-semibold flex items-center gap-1 hover:text-primary-700">
Read More
<svg xmlns="http://www.w3.org/2000/svg" className="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="m6 9 6 6 6-6"/></svg>
</button>
</div>
))}
</div>
</div>
</section>
);
};
export default ReviewsSection;

View file

@ -0,0 +1,29 @@
import { servicesData } from "../../data/homeData";
const ServicesSection = () => {
return (
<section id="services" className="py-16 bg-white px-4 sm:px-6 lg:px-8">
<div className="max-w-7xl mx-auto">
<h2 className="text-3xl font-bold text-center text-dark-900 mb-10">Vehicle Services</h2>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
{servicesData.map((service) => (
<div key={service.id} className="group cursor-pointer">
<div className="overflow-hidden rounded-xl h-64 sm:h-72 mb-4 shadow-sm group-hover:shadow-md transition-shadow">
<img
src={service.image}
alt={service.title}
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-500"
/>
</div>
<h3 className="text-lg font-bold text-dark-900 text-center group-hover:text-primary-600 transition-colors">
{service.title}
</h3>
</div>
))}
</div>
</div>
</section>
);
};
export default ServicesSection;

View file

@ -0,0 +1,6 @@
export const filterTabs = [
{ key: "driver-rentals", label: "driver-rentals", disabled: false },
{ key: "self-driving", label: "self driving rental", disabled: true },
{ key: "driver-hiring", label: "driver-hiring", disabled: true },
{ key: "bike-rentals", label: "bike rentals", disabled: true },
];

157
src/data/cabsData.js Normal file
View file

@ -0,0 +1,157 @@
import etios from "../assets/vehicles/etios.png";
import innova from "../assets/vehicles/innova.png";
import dzire from "../assets/vehicles/dzire.png";
import innovaCrysta from "../assets/vehicles/innova_crysta.png";
import audiQ7 from "../assets/vehicles/audi_q7.png";
import camry from "../assets/vehicles/camry.png";
import fortuner from "../assets/vehicles/fortuner.png";
import corollaAltis from "../assets/vehicles/corolla_altis.png";
import bmw7Series from "../assets/vehicles/bmw_7_series.png";
import mercedesSClass from "../assets/vehicles/mercedes_s_class.png";
import toyotaCommuter from "../assets/vehicles/toyota_commuter.png";
import audiA6 from "../assets/vehicles/audi_a6.png";
import bmw5Series from "../assets/vehicles/bmw_5_series.png";
import mercedesEClass from "../assets/vehicles/mercedes_e_class.png";
export const cabsData = [
{
id: "etios-ac",
name: "Etios A/C",
category: "cabs",
seats: 4,
features: ["Air Conditioner", "Audio System", "Spacious Leg Space"],
price: "₹1,500",
image: etios,
detailUrl: "/cab-rent-bangalore",
},
{
id: "innova-6-ac",
name: "Innova 6 A/C",
category: "cabs",
seats: 6,
features: ["Air Conditioner", "Audio System", "Spacious Leg Space"],
price: "₹3,500",
image: innova,
detailUrl: "/innova-car-rent-bangalore",
},
{
id: "dzire-swift-ac",
name: "Dzire Swift A/C",
category: "cabs",
seats: 4,
features: ["Air Conditioner", "Audio System", "Spacious Leg Space"],
price: "₹1,500",
image: dzire,
detailUrl: "/dzire-car-rent-bangalore",
},
{
id: "innova-crysta-6-ac",
name: "Innova Crysta 6 A/C",
category: "cabs",
seats: 6,
features: ["Air Conditioner", "Audio System", "Spacious Leg Space"],
price: "₹2,500",
image: innovaCrysta,
detailUrl: "/innova-crysta-car-rent-bangalore",
},
{
id: "audi-q7-ac",
name: "Audi-Q7 A/C",
category: "cabs",
seats: 4,
features: ["Air Conditioner", "Audio System", "Spacious Leg Space"],
price: "₹12,500",
image: audiQ7,
detailUrl: "/audi-q7-car-rent-bangalore",
},
{
id: "camry-ac",
name: "Camry A/C",
category: "cabs",
seats: 4,
features: ["Air Conditioner", "Audio System", "Spacious Leg Space"],
price: "₹6,000",
image: camry,
detailUrl: "/camry-car-rent-bangalore",
},
{
id: "fortuner-ac",
name: "Toyota Fortuner A/C",
category: "cabs",
seats: 5,
features: ["Air Conditioner", "Audio System", "Spacious Leg Space"],
price: "₹6,500",
image: fortuner,
detailUrl: "/fortuner-car-rent-bangalore",
},
{
id: "corolla-altis-ac",
name: "Corolla-Altis A/C",
category: "cabs",
seats: 4,
features: ["Air Conditioner", "Audio System", "Spacious Leg Space"],
price: "₹4,000",
image: corollaAltis,
detailUrl: "/corolla-altis-car-rent-bangalore",
},
{
id: "bmw-7-series-ac",
name: "BMW 7 Series A/C",
category: "cabs",
seats: 4,
features: ["Air Conditioner", "Audio System", "Spacious Leg Space"],
price: "₹15,000",
image: bmw7Series,
detailUrl: "/bmw-7-series-car-rent-bangalore",
},
{
id: "mercedes-s-class-ac",
name: "Mercedes-Benz S-Class A/C",
category: "cabs",
seats: 4,
features: ["Air Conditioner", "Audio System", "Spacious Leg Space"],
price: "₹18,000",
image: mercedesSClass,
detailUrl: "/mercedes-benz-s-class-car-rent-bangalore",
},
{
id: "toyota-commuter-ac",
name: "Toyota Commuter A/C",
category: "cabs",
seats: 10,
features: ["Air Conditioner", "Audio System", "Spacious Leg Space"],
price: "₹8,500",
image: toyotaCommuter,
detailUrl: "/toyota-commuter-car-rent-bangalore",
},
{
id: "audi-a6",
name: "Audi A6",
category: "cabs",
seats: 4,
features: ["Air Conditioner", "Audio System", "Spacious Leg Space"],
price: "₹8,000",
image: audiA6,
detailUrl: "/audi-a6-car-rent-bangalore",
},
{
id: "bmw-5-series-ac",
name: "BMW 5 Series A/C",
category: "cabs",
seats: 4,
features: ["Air Conditioner", "Audio System", "Spacious Leg Space"],
price: "₹8,000",
image: bmw5Series,
detailUrl: "/bmw-5-series-car-rent-bangalore",
},
{
id: "mercedes-e-class",
name: "Mercedes Benz E Class",
category: "cabs",
seats: 4,
features: ["Air Conditioner", "Audio System", "Spacious Leg Space"],
price: "₹14,500",
image: mercedesEClass,
detailUrl: "/mercedes-benz-e-class-car-rent-bangalore",
},
];

106
src/data/homeData.js Normal file
View file

@ -0,0 +1,106 @@
export const servicesData = [
{
id: "corporate",
title: "Corporate Travel",
image: "https://images.unsplash.com/photo-1554469384-e58fac16e23a?auto=format&fit=crop&q=80&w=800",
},
{
id: "airport",
title: "Airport Transfers",
image: "https://images.unsplash.com/photo-1436491865332-7a61a109cc05?auto=format&fit=crop&q=80&w=800",
},
{
id: "commute",
title: "Family Trips",
image: "https://images.unsplash.com/photo-1502307100811-6bdc0981a85b?auto=format&fit=crop&q=80&w=800",
},
{
id: "wedding",
title: "Wedding Cabs & Tours",
image: "https://images.unsplash.com/photo-1519225421980-715cb0215aed?auto=format&fit=crop&q=80&w=800",
},
];
export const reviewsData = [
{
id: 1,
name: "Sagar",
date: "7 Jan 2025",
rating: 5,
title: "Travelled to Bangalore",
text: "The giraffe terrace and kangaroo camp were amazing, the orangutan show was hilarious. Safari by car was normal, dolphin and sea lions were below expectations but may be because the...",
avatar: "https://ui-avatars.com/api/?name=Sagar&background=random"
},
{
id: 2,
name: "Anish Ranjan",
date: "24 May 2025",
rating: 5,
title: "Travelled to mysore",
text: "Had an amazing team trip to Chikmagalur! We were a group of 25 and the cabs provided were super premium and well-maintained. A special shout-out to our driver, Mr. Arun Kumar. While h...",
avatar: "https://ui-avatars.com/api/?name=Anish+Ranjan&background=random"
},
{
id: 3,
name: "dheeraj gali",
date: "19/02/24",
rating: 5,
title: "Travel to Coorg",
text: "Hired a premium luxury cab for coorg trip. The vehicle is simply superb - seats, ac, condition of the vehicle everything was top notch!! Driver was very polite and helpful for us. Special thanks...",
avatar: "https://ui-avatars.com/api/?name=dheeraj+gali&background=random"
},
{
id: 4,
name: "Srinu Gonapa",
date: "12/03/24",
rating: 5,
title: "Travel to ooty",
text: "Very good and comfortable journey. 1.Excellent service and punctual timing. 2.Staff was polite and professional. 3.Cab was clean and well-maintained. 4.Highly satisfied with travel Experience",
avatar: "https://ui-avatars.com/api/?name=Srinu+Gonapa&background=random"
}
];
export const highlightsData = [
{
id: 1,
title: "Professional Travel Solutions",
description: "Streamlined travel services tailored for Bangalore travelers.",
image: "https://images.unsplash.com/photo-1507525428034-b723cf961d3e?auto=format&fit=crop&q=80&w=1000",
},
{
id: 2,
title: "Extensive Vehicle Network",
description: "Large fleet of premium cabs for all your needs.",
image: "https://images.unsplash.com/photo-1570125909232-eb263c188f7e?auto=format&fit=crop&q=80&w=1000",
},
{
id: 3,
title: "Transparent Pricing",
description: "Clear costs and availability with no hidden fees.",
image: "https://images.unsplash.com/photo-1501183638710-841dd1904471?auto=format&fit=crop&q=80&w=1000",
},
{
id: 4,
title: "Easy Booking Options",
description: "Simple reservations for regional and outstation trips.",
image: "https://images.unsplash.com/photo-1522202176988-66273c2fd55f?auto=format&fit=crop&q=80&w=1000",
}
];
export const newsData = [
{
id: 1,
title: "Wedding Cab Booking in Bangalore | AC & Non AC",
image: "https://images.unsplash.com/photo-1544620347-c4fd4a3d5957?auto=format&fit=crop&q=80&w=800",
},
{
id: 2,
title: "Premium SUV Cabs in Bangalore Perfect...",
image: "https://images.unsplash.com/photo-1464219789935-c2d9d9aba644?auto=format&fit=crop&q=80&w=800",
},
{
id: 3,
title: "Luxury Cabs for Outstation Trips Comfort...",
image: "https://images.unsplash.com/photo-1486406146926-c627a92ad1ab?auto=format&fit=crop&q=80&w=800",
}
];

38
src/index.css Normal file
View file

@ -0,0 +1,38 @@
@import url('https://fonts.googleapis.com/css2?family=Figtree:wght@400;500;600;700&display=swap');
@tailwind base;
@tailwind components;
@tailwind utilities;
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html {
scroll-behavior: smooth;
}
body {
font-family: 'Figtree', sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
background-color: #F8FAFC;
color: #111827;
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(16px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.animate-fadeInUp {
animation: fadeInUp 0.4s ease-out;
}

44
src/lib/api.js Normal file
View file

@ -0,0 +1,44 @@
// Single source of truth for backend communication
const BASE_URL = import.meta.env.VITE_API_URL || "";
export const api = {
post: async (path, body) => {
// Make sure we form the correct full URL (handling missing slashes if any)
const url = path.startsWith("http") ? path : `${BASE_URL}${path}`;
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(body),
});
let data;
let textResult = await response.text();
try {
data = textResult ? JSON.parse(textResult) : {};
} catch (e) {
data = { detail: "Backend Server is Unreachable (Proxy Error: " + response.status + ")" };
}
if (!response.ok) {
// FastAPI typically throws Error messages under `detail`
let errMsg = "An error occurred during communication";
if (typeof data.detail === "string") {
errMsg = data.detail;
} else if (Array.isArray(data.detail)) {
// FastAPI validation errors
errMsg = data.detail.map(d => d.msg).join(", ");
} else if (data.message) {
errMsg = data.message;
}
throw new Error(errMsg);
}
return data;
},
};

13
src/main.jsx Normal file
View file

@ -0,0 +1,13 @@
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import { GoogleOAuthProvider } from '@react-oauth/google'
import './index.css'
import App from './App.jsx'
createRoot(document.getElementById('root')).render(
<StrictMode>
<GoogleOAuthProvider clientId="725335319736-kglnmjhjppb3563vh9tpj22u5crao0a8.apps.googleusercontent.com">
<App />
</GoogleOAuthProvider>
</StrictMode>,
)