// CeyRide — Shared UI Components // ── FX rate constant — update this when the rate changes significantly ── const CR_USD_RATE = 320; // LKR per 1 USD (approx) // Vehicle image — uses real photo if available, striped placeholder otherwise function VehiclePlaceholder({ type = "Car", color = "#E84800", width = "100%", height = 180, imageUrl, style: extraStyle }) { if (imageUrl) { return (
{type} e.currentTarget.style.transform = 'scale(1.04)'} onMouseLeave={e => e.currentTarget.style.transform = 'scale(1)'} onError={e => { e.currentTarget.style.display = 'none'; }} />
); } const icons = { "Car": "🚗", "Van": "🚐", "SUV": "🚙", "Luxury": "🚘", "Bus": "🚌", "Three-Wheeler": "🛺", "Tuk": "🛺", "Mini Bus": "🚌", "Wedding Hire": "💍", "Lorry / Truck": "🚛" }; const icon = icons[type] || "🚗"; return (
{icon} {type} photo
); } // Star rating function Stars({ rating, size = 14 }) { return ( {'★'.repeat(Math.floor(rating))}{'☆'.repeat(5 - Math.floor(rating))} ); } // Badge chip function Badge({ label, color = '#E84800', bg }) { return ( {label} ); } // Vehicle Card function VehicleCard({ v, setPage, setSelectedVehicle }) { const [hovered, setHovered] = React.useState(false); // ── Resolve capability flags — prefer new fields, fall back to legacy ── const hasOwnerDriver = !!(v.has_owner_driver ?? v.hasOwnerDriver ?? v.withDriver ?? false); const hasSelfDrive = !!(v.has_self_drive ?? v.hasSelfDrive ?? v.selfDrive ?? true); // ── From price — lowest applicable rate ────────────────────────────── const selfDrivePrice = (v.self_drive_price || v.selfDrivePrice) > 0 ? (v.self_drive_price || v.selfDrivePrice) : null; const ownerDrvPrice = (v.owner_driver_price || v.ownerDriverPrice) > 0 ? (v.owner_driver_price || v.ownerDriverPrice) : null; const basePrice = v.pricePerDay || v.price_per_day || 0; const prices = [selfDrivePrice || basePrice, ownerDrvPrice || basePrice].filter(Boolean); const fromPrice = prices.length ? Math.min(...prices) : basePrice; const showFromLabel = !!(selfDrivePrice || ownerDrvPrice); return (
setHovered(true)} onMouseLeave={() => setHovered(false)} style={{ background: '#fff', borderRadius: 20, overflow: 'hidden', cursor: 'pointer', boxShadow: hovered ? '0 12px 40px rgba(20,20,19,0.12)' : '0 2px 12px rgba(20,20,19,0.06)', transition: 'all 0.25s ease', transform: hovered ? 'translateY(-4px)' : 'translateY(0)' }}> {/* Image */}
{/* Issue #9 fix — spec-correct badge labels driven by new capability flags */} {hasOwnerDriver && } {hasSelfDrive && }
{/* Content */}
{v.brand} {v.model}
{v.seats} seats · {v.location}
{showFromLabel && from } Rs. {fromPrice.toLocaleString()}
≈ USD {Math.round(fromPrice / CR_USD_RATE)}/day
{v.rating} ({v.reviews} reviews)
{/* Issue #2 fix — route through mode selector, never directly to booking */}
); } // Section title component function SectionTitle({ eyebrow, title, subtitle, center = false }) { return (
{eyebrow && (
{eyebrow}
)}

{title}

{subtitle && (

{subtitle}

)}
); } Object.assign(window, { VehiclePlaceholder, Stars, Badge, VehicleCard, SectionTitle });