Start
import React, { useMemo, useState, useEffect } from "react";
import { motion } from "framer-motion";
import {
Plane,
Calendar as CalendarIcon,
Users,
IndianRupee,
Bike,
Car,
Bus,
Train,
PlaneTakeoff,
MapPin,
Hotel,
UtensilsCrossed,
Wallet,
Cog,
Search,
CreditCard,
CheckCircle,
} from "lucide-react";
/**
* SMART TRAVEL PLANNER – One Page App
* -----------------------------------------------------------
* - Fully responsive (mobile-first), clean + subtle 3D (neumorphic) look
* - Form: Destination, Start/End date (with native calendar), Travellers, Budget (optional), Transport (dropdown w/ icons)
* - Currency selector (defaults to INR), conversion across UI; user can change currency
* - Generate Itinerary button → shows results sections: Itinerary, Hotels, Places, Vehicle, Cost, Restaurants
* - Booking block with mock live search + payment (demo). Replace adapters below with real APIs when ready.
* - NOTE: This demo uses mocked generators so it works without keys. API adapter placeholders are included.
* -----------------------------------------------------------
* HOW TO DEPLOY QUICKLY
* - Copy this file into a Vite/Next.js React project and set it as a page/component.
* - TailwindCSS is assumed. If not installed, you can still get decent styling with the base class names,
* or replace classes with your CSS.
*/
// -------------------- Utility: Date helpers --------------------
const dayDiff = (a, b) => {
const ms = Math.max(0, new Date(b).setHours(0, 0, 0, 0) - new Date(a).setHours(0, 0, 0, 0));
return Math.floor(ms / (1000 * 60 * 60 * 24)) + 1; // inclusive
};
const enumerateDates = (start, end) => {
const days = [];
let d = new Date(start);
const last = new Date(end);
while (d <= last) {
days.push(new Date(d));
d.setDate(d.getDate() + 1);
}
return days;
};
// -------------------- Currency: simple store + adapter hooks --------------------
const DEFAULT_CCY = "INR";
// Base: INR; rough sample rates; you should refresh these with a live FX API (see refreshRates function).
const DEFAULT_RATES = {
INR: 1,
USD: 0.012,
EUR: 0.011,
GBP: 0.0095,
JPY: 1.9,
AED: 0.044,
AUD: 0.018,
CAD: 0.017,
SGD: 0.016,
};
const CURRENCIES = Object.keys(DEFAULT_RATES);
function useCurrency() {
const [currency, setCurrency] = useState(DEFAULT_CCY);
const [rates, setRates] = useState(DEFAULT_RATES);
const [refreshing, setRefreshing] = useState(false);
async function refreshRates() {
// Replace with your live FX API (e.g., exchangerate.host, currencyapi, Fixer, etc.)
// Keep base as INR for consistency.
try {
setRefreshing(true);
// const res = await fetch("/api/fx?base=INR");
// const json = await res.json();
// setRates(json.rates);
await new Promise((r) => setTimeout(r, 600));
} catch (e) {
console.warn("FX refresh failed; using fallback rates.", e);
} finally {
setRefreshing(false);
}
}
function convertINR(amountInINR, to = currency) {
const rate = rates[to] ?? 1;
return amountInINR * rate;
}
return { currency, setCurrency, rates, setRates, convertINR, refreshRates, refreshing };
}
// -------------------- Mock Data Generators (deterministic-ish) --------------------
function seededRandom(seed) {
// xorshift32-ish for deterministic mocks
let x = 0;
for (let i = 0; i < seed.length; i++) x ^= seed.charCodeAt(i) << ((i % 4) * 8);
return () => {
x ^= x << 13;
x ^= x >>> 17;
x ^= x << 5;
return (x >>> 0) / 4294967296;
};
}
function guessRegionCostFactor(destination) {
const d = destination.toLowerCase();
if (/switzerland|iceland|norway|japan|singapore|uae|uk|london|paris|new york|sydney/.test(d)) return 3.0; // expensive
if (/india|vietnam|nepal|indonesia|thailand|turkey|mexico|portugal/.test(d)) return 1.0; // budget
return 2.0; // mid
}
function createHotels(dest, count = 6) {
const rnd = seededRandom(dest + "hotels");
return Array.from({ length: count }).map((_, i) => {
const price = 1500 + Math.floor(rnd() * 7000);
return {
id: `h${i}`,
name: `${dest} Grand ${i + 1}`,
rating: (3 + rnd() * 2).toFixed(1),
distanceKm: (0.5 + rnd() * 8).toFixed(1),
pricePerNightINR: price,
amenities: ["Wi‑Fi", "Breakfast", rnd() > 0.5 ? "Pool" : "Gym"],
};
});
}
function createPlaces(dest, count = 6) {
const rnd = seededRandom(dest + "places");
const samples = [
"Historic Fort",
"City Museum",
"Botanical Garden",
"Scenic Viewpoint",
"Art District",
"Riverside Walk",
"Night Market",
"National Park",
];
return Array.from({ length: count }).map((_, i) => ({
id: `p${i}`,
name: `${dest} ${samples[Math.floor(rnd() * samples.length)]}`,
bestTime: rnd() > 0.5 ? "Morning" : "Evening",
ticketINR: Math.floor(100 + rnd() * 800),
note: rnd() > 0.6 ? "Guided tours available" : "Free entry on Sundays",
}));
}
function createRestaurants(dest, count = 6) {
const rnd = seededRandom(dest + "food");
const cuisines = ["Indian", "Italian", "Asian", "Mexican", "Seafood", "Vegan", "BBQ", "Bakery"];
return Array.from({ length: count }).map((_, i) => ({
id: `r${i}`,
name: `${dest} ${cuisines[Math.floor(rnd() * cuisines.length)]} Kitchen`,
rating: (3 + rnd() * 2).toFixed(1),
avgMealINR: Math.floor(250 + rnd() * 1200),
popular: rnd() > 0.5 ? "Bestseller" : "Family friendly",
}));
}
function createVehicles(dest, mode = "flight") {
const rnd = seededRandom(dest + mode);
const rows = [];
const n = 5;
for (let i = 0; i < n; i++) {
const base = 900 + rnd() * 4500;
rows.push({
id: `${mode}-${i}`,
name:
mode === "flight"
? `${dest} Airways ${100 + i}`
: mode === "train"
? `${dest} Express ${200 + i}`
: mode === "bus"
? `${dest} Coach ${300 + i}`
: mode === "car"
? `Self‑Drive Hatchback ${i + 1}`
: `Touring Bike ${i + 1}`,
depart: `${8 + i}:00`,
arrive: `${10 + i}:30`,
pricePerPersonINR: Math.floor(base * (mode === "flight" ? 2.2 : mode === "train" ? 0.6 : mode === "bus" ? 0.5 : 0.4)),
refundable: rnd() > 0.5,
});
}
return rows;
}
function estimateCosts({ destination, startDate, endDate, travellers, mode }) {
const nights = Math.max(1, dayDiff(startDate, endDate) - 1);
const factor = guessRegionCostFactor(destination);
const hotelPerNightINR = 2500 * factor; // per room per night (assume 2 per room)
const rooms = Math.ceil(travellers / 2);
const hotelTotal = hotelPerNightINR * rooms * nights;
const foodPerPersonPerDayINR = 900 * factor;
const localTransportPerDayINR = 600 * factor;
const sightseeingPerPersonINR = 800 * factor;
const vehicleOptions = createVehicles(destination, mode);
const transportPerPersonINR = vehicleOptions[1]?.pricePerPersonINR ?? 2000;
const persons = travellers;
const stayFoodLocal = nights * (foodPerPersonPerDayINR * persons + localTransportPerDayINR);
const sightseeing = sightseeingPerPersonINR * persons;
const toFrom = transportPerPersonINR * persons; // one‑way approx. (simplified)
const subtotal = hotelTotal + stayFoodLocal + sightseeing + toFrom;
const contingency = 0.1 * subtotal;
const total = Math.round(subtotal + contingency);
return { nights, rooms, hotelTotal: Math.round(hotelTotal), stayFoodLocal: Math.round(stayFoodLocal), sightseeing: Math.round(sightseeing), toFrom: Math.round(toFrom), contingency: Math.round(contingency), total };
}
// -------------------- Icons for mode --------------------
const modeConfig = {
bike: { label: "Bike", Icon: Bike },
car: { label: "Car", Icon: Car },
bus: { label: "Bus", Icon: Bus },
train: { label: "Train", Icon: Train },
flight: { label: "Flight", Icon: PlaneTakeoff },
};
// -------------------- UI Helpers --------------------
function Card({ children, className = "" }) {
return (
{children}
);
}
function SectionTitle({ icon: Icon, title, right }) {
return (
);
}
// -------------------- Main Component --------------------
export default function SmartTravelPlanner() {
const [destination, setDestination] = useState("");
const today = new Date().toISOString().slice(0, 10);
const [startDate, setStartDate] = useState(today);
const [endDate, setEndDate] = useState(today);
const [travellers, setTravellers] = useState(1);
const [budget, setBudget] = useState("");
const [mode, setMode] = useState("flight");
const [generated, setGenerated] = useState(false);
const [activeTab, setActiveTab] = useState("Itinerary");
const [booking, setBooking] = useState(null); // {type, item}
const { currency, setCurrency, convertINR, refreshRates, refreshing } = useCurrency();
const hotels = useMemo(() => (destination ? createHotels(destination) : []), [destination]);
const places = useMemo(() => (destination ? createPlaces(destination) : []), [destination]);
const restaurants = useMemo(() => (destination ? createRestaurants(destination) : []), [destination]);
const vehicles = useMemo(() => (destination ? createVehicles(destination, mode) : []), [destination, mode]);
const costs = useMemo(
() => (destination ? estimateCosts({ destination, startDate, endDate, travellers, mode }) : null),
[destination, startDate, endDate, travellers, mode]
);
// Save last inputs
useEffect(() => {
const saved = localStorage.getItem("stp_state");
if (saved) {
try {
const j = JSON.parse(saved);
setDestination(j.destination || "");
setStartDate(j.startDate || today);
setEndDate(j.endDate || today);
setTravellers(j.travellers || 1);
setBudget(j.budget || "");
setMode(j.mode || "flight");
setCurrency(j.currency || DEFAULT_CCY);
} catch {}
}
// eslint-disable-next-line
}, []);
useEffect(() => {
const state = { destination, startDate, endDate, travellers, budget, mode, currency };
localStorage.setItem("stp_state", JSON.stringify(state));
}, [destination, startDate, endDate, travellers, budget, mode, currency]);
const days = useMemo(() => (destination ? enumerateDates(startDate, endDate) : []), [destination, startDate, endDate]);
function handleGenerate() {
setGenerated(true);
setActiveTab("Itinerary");
}
function currencyFmtINRToCurrent(inr) {
const value = convertINR(inr);
return new Intl.NumberFormat(undefined, { style: "currency", currency }).format(value);
}
function Badge({ children }) {
return {children};
}
function ModeDropdown() {
return (
);
}
return (
{Icon && }
{right}
{title}
{React.createElement(modeConfig[mode].Icon, { className: "w-5 h-5" })}
{/* Header */}
{/* Currency */}
{/* Hero + Form */}
Clean, professional UI • Subtle 3D design • Fully responsive
Generate Itinerary
{!destination && (
{/* Results */}
{generated && destination && (
{travellers} travellers} />
)}
{activeTab === "Hotels" && (
)}
{activeTab === "Places" && (
)}
{activeTab === "Vehicle" && (
)}
{activeTab === "Cost" && costs && (
{costs.nights} nights} />
)}
{activeTab === "Restaurants" && (
)}
{activeTab === "Booking" && (
)}
)}
{/* Booking modal (demo) */}
{booking && (
)}
{/* Footer */}
);
}
Smart Travel Planner
AI‑powered trip planning made easy
Plan your trip
setDestination(e.target.value)}
/>
setStartDate(e.target.value)}
/>
setEndDate(e.target.value)}
/>
setTravellers(Math.max(1, Number(e.target.value || 1)))}
/>
setBudget(e.target.value)}
/>
{Object.entries(modeConfig).map(([key, { Icon }]) => (
{key}
))}
Enter a destination to continue
)}
{[
"Itinerary",
"Hotels",
"Places",
"Vehicle",
"Cost",
"Restaurants",
"Booking",
].map((tab) => (
))}
{/* Tab content */}
{activeTab === "Itinerary" && (
{days.map((d, i) => (
Day {i + 1}
))}
{d.toDateString()}
Explore {destination} • Suggested: {places[i % places.length]?.name || "City walk"}
{hotels.map((h) => (
))}
{h.name}
{h.distanceKm} km from center • ⭐{h.rating}
Amenities: {h.amenities.join(", ")}
{currencyFmtINRToCurrent(h.pricePerNightINR)} / night
{places.map((p) => (
))}
{p.name}
Best time: {p.bestTime}
Ticket: {currencyFmtINRToCurrent(p.ticketINR)}
{p.note}
{vehicles.map((v) => (
{v.refundable ? "Refundable" : "Non‑refundable"}
))}
{v.name}
Depart {v.depart} • Arrive {v.arrive}
{currencyFmtINRToCurrent(v.pricePerPersonINR)} / person
Hotel ({costs.rooms} room{costs.rooms>1?"s":""})
{currencyFmtINRToCurrent(costs.hotelTotal)}
Food + Local transport
{currencyFmtINRToCurrent(costs.stayFoodLocal)}
Sightseeing
{currencyFmtINRToCurrent(costs.sightseeing)}
To/From ({travellers} ppl)
{currencyFmtINRToCurrent(costs.toFrom)}
Contingency (10%)
{currencyFmtINRToCurrent(costs.contingency)}
Total (all travellers)
{currencyFmtINRToCurrent(costs.total)}
{budget && (
Budget: {currencyFmtINRToCurrent(Number(budget))} • {Number(budget) >= costs.total ? (
Within budget
) : (
Over budget
)}
)}
{restaurants.map((r) => (
))}
{r.name}
⭐{r.rating} • {r.popular}
Avg meal: {currencyFmtINRToCurrent(r.avgMealINR)}
Hotels
Connect to Booking, Expedia, or your OTA provider via API.
Trains / Flights / Buses
Integrate Amadeus, Skyscanner, Kiwi, or rail APIs for live fares.
Payments
Hook this to Razorpay/Stripe/Paytm/etc. in production.
Booking preview
Type: {booking.type}
{JSON.stringify(booking.item, null, 2)}
Comments
Post a Comment