import { createClient } from "https://esm.sh/@supabase/supabase-js@2.45.4"; import { translations, REVIEWS, getLang, setLang, t, applyI18n } from "./i18n.js"; const SUPA_URL = window.MCCARS_CONFIG?.SUPABASE_URL ?? ""; const SUPA_KEY = window.MCCARS_CONFIG?.SUPABASE_ANON_KEY || ""; export const supabase = createClient(SUPA_URL, SUPA_KEY, { auth: { persistSession: false, storageKey: "mccars.public" }, }); // ---------------- State ---------------- const state = { vehicles: [], filtered: [], brand: "all", sort: "sort_order", maxPrice: null, reviewIdx: 0, }; // ---------------- Elements ---------------- const grid = document.querySelector("#vehicleGrid"); const emptyState = document.querySelector("#emptyState"); const brandFilter = document.querySelector("#brandFilter"); const sortFilter = document.querySelector("#sortFilter"); const priceFilter = document.querySelector("#priceFilter"); const bookingCar = document.querySelector("#bookingCar"); const bookingForm = document.querySelector("#bookingForm"); const bookingFeedback = document.querySelector("#bookingFeedback"); const langToggle = document.querySelector(".lang-toggle"); const menuToggle = document.querySelector(".menu-toggle"); const mainNav = document.querySelector(".main-nav"); const dialog = document.querySelector("#carDialog"); const dialogTitle = document.querySelector("#dialogTitle"); const dialogBody = document.querySelector("#dialogBody"); const dialogClose = document.querySelector("#dialogClose"); const reviewStrip = document.querySelector("#reviewStrip"); const reviewDots = document.querySelector("#reviewDots"); const statCarsCount = document.querySelector("#statCarsCount"); document.querySelector("#year").textContent = new Date().getFullYear(); // ---------------- Vehicles ---------------- async function loadVehicles() { const { data, error } = await supabase .from("vehicles") .select("*") .eq("is_active", true) .order("sort_order", { ascending: true }); if (error) { console.error("Failed to load vehicles", error); grid.innerHTML = `
Unable to load vehicles: ${error.message}
`; return; } state.vehicles = data || []; statCarsCount.textContent = state.vehicles.length; const brands = [...new Set(state.vehicles.map(v => v.brand))].sort(); brandFilter.innerHTML = `` + brands.map(b => ``).join(""); bookingCar.innerHTML = state.vehicles .map(v => ``) .join(""); applyFilters(); } function applyFilters() { let rows = [...state.vehicles]; if (state.brand !== "all") rows = rows.filter(v => v.brand === state.brand); if (state.maxPrice) rows = rows.filter(v => v.daily_price_eur <= state.maxPrice); switch (state.sort) { case "priceAsc": rows.sort((a, b) => a.daily_price_eur - b.daily_price_eur); break; case "priceDesc": rows.sort((a, b) => b.daily_price_eur - a.daily_price_eur); break; case "powerDesc": rows.sort((a, b) => b.power_hp - a.power_hp); break; default: rows.sort((a, b) => a.sort_order - b.sort_order); } state.filtered = rows; renderGrid(); } function renderGrid() { grid.innerHTML = ""; emptyState.style.display = state.filtered.length ? "none" : "block"; for (const v of state.filtered) { const card = document.createElement("article"); card.className = "vehicle-card"; card.innerHTML = `${escapeHtml(v.brand)}
${escapeHtml(desc || "")}
"${escapeHtml(r.quote)}"
`; reviewDots.innerHTML = list.map((_, i) => `` ).join(""); reviewDots.querySelectorAll("button").forEach(b => { b.addEventListener("click", () => { state.reviewIdx = +b.dataset.rev; renderReviews(); }); }); } setInterval(() => { state.reviewIdx++; renderReviews(); }, 6000); // ---------------- Booking -> LEADS ---------------- bookingForm.addEventListener("submit", async (e) => { e.preventDefault(); const fd = new FormData(bookingForm); const data = Object.fromEntries(fd.entries()); if (!data.from || !data.to || new Date(data.to) <= new Date(data.from)) { bookingFeedback.textContent = t("invalidDates"); bookingFeedback.className = "form-feedback error"; return; } const vehicle = state.vehicles.find(v => v.id === data.vehicle); const payload = { name: data.name, email: data.email, phone: data.phone || "", vehicle_id: data.vehicle || null, vehicle_label: vehicle ? `${vehicle.brand} ${vehicle.model}` : "", date_from: data.from || null, date_to: data.to || null, message: data.message || "", source: "website", }; bookingFeedback.className = "form-feedback"; bookingFeedback.textContent = "..."; const { error } = await supabase.from("leads").insert(payload); if (error) { console.error(error); bookingFeedback.className = "form-feedback error"; bookingFeedback.textContent = t("bookingFailed"); return; } bookingFeedback.textContent = t("bookingSuccess"); bookingForm.reset(); }); // ---------------- Events ---------------- brandFilter.addEventListener("change", e => { state.brand = e.target.value; applyFilters(); }); sortFilter.addEventListener("change", e => { state.sort = e.target.value; applyFilters(); }); priceFilter.addEventListener("input", e => { state.maxPrice = e.target.value ? +e.target.value : null; applyFilters(); }); dialogClose.addEventListener("click", () => dialog.close()); menuToggle.addEventListener("click", () => mainNav.classList.toggle("open")); mainNav.addEventListener("click", e => { if (e.target.tagName === "A") mainNav.classList.remove("open"); }); langToggle.addEventListener("click", () => { const next = getLang() === "de" ? "en" : "de"; setLang(next); langToggle.textContent = next === "de" ? "EN" : "DE"; applyI18n(); renderReviews(); applyFilters(); }); // ---------------- Helpers ---------------- function escapeHtml(s) { return String(s ?? "").replace(/[&<>"']/g, c => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]); } function escapeAttr(s) { return escapeHtml(s); } // ---------------- Boot ---------------- langToggle.textContent = getLang() === "de" ? "EN" : "DE"; applyI18n(); renderReviews(); loadVehicles();