/* eslint-disable */ /* ============================================================ PETICOTA — Cart (shared between index.html & tasarla.html) Uses localStorage, exposes useCart() + ============================================================ */ const CART_KEY = "peticota.cart.v1"; /* ============================================================ GOOGLE SHEETS BRIDGE ---------------------------------------------------------- Apps Script Web App URL'sini buraya yapıştırın. Boş kalsa bile site çalışmaya devam eder; sadece Sheet'e yazılmaz. Kurulum talimatı için: apps-script.gs dosyası. ============================================================ */ const SHEETS_ENDPOINT = "https://script.google.com/macros/s/AKfycbxiTXKQvaLLTOGa10zN-zfskqlDugOrCyy_4Cw9afALEilAR6NBHrxdJRvwaUL_MshBVQ/exec"; /* ============================================================ CLOUDFLARE TURNSTILE (CAPTCHA) Site key public — JS'de görünür olması normal/gerekli. Secret key sadece Apps Script tarafında. ============================================================ */ const TURNSTILE_SITE_KEY = "0x4AAAAAADSSg_EtwZNGsoqS"; function TurnstileWidget({ onToken, theme = "auto", size = "normal" }) { const ref = React.useRef(null); const idRef = React.useRef(null); React.useEffect(() => { let cancelled = false; const tryRender = () => { if (cancelled) return; if (!window.turnstile || !ref.current) { setTimeout(tryRender, 200); return; } if (idRef.current) return; try { idRef.current = window.turnstile.render(ref.current, { sitekey: TURNSTILE_SITE_KEY, theme, size, callback: (token) => onToken(token), "error-callback": () => onToken(""), "expired-callback": () => onToken(""), "timeout-callback": () => onToken("") }); } catch (e) { console.error("Turnstile render failed:", e); } }; tryRender(); return () => { cancelled = true; if (idRef.current && window.turnstile) { try { window.turnstile.remove(idRef.current); } catch (e) {} idRef.current = null; } }; }, []); return
; } async function pushToSheet(payload) { if (!SHEETS_ENDPOINT) { console.warn("[pushToSheet] SHEETS_ENDPOINT boş, atlanıyor"); return { ok: false, skipped: true }; } try { // Apps Script tarafı JSON.parse(e.postData.contents) ile okuyor → // gövdeyi düz JSON string olarak gönderiyoruz. // Content-Type: text/plain CORS-safelisted olduğu için preflight // tetiklenmez ve Apps Script /exec endpoint'i cevapla beraber // Access-Control-Allow-Origin: * gönderdiği için cevabı OKUYABİLİRİZ. // Eskisi gibi no-cors KULLANMIYORUZ — yoksa sunucu hatası geldiğinde // sessizce yutuluyordu. const res = await fetch(SHEETS_ENDPOINT, { method: "POST", headers: { "Content-Type": "text/plain;charset=utf-8" }, body: JSON.stringify(payload), redirect: "follow" }); const text = await res.text(); let data = null; try { data = JSON.parse(text); } catch (_) {} if (!res.ok) { console.error("[pushToSheet] HTTP", res.status, text.slice(0, 500)); return { ok: false, status: res.status, body: text }; } if (data && data.ok === false) { console.error("[pushToSheet] sunucu hatası:", data); return { ok: false, server: data }; } console.log("[pushToSheet] OK", data || text.slice(0, 200)); return { ok: true, data }; } catch (e) { console.error("[pushToSheet] network/CORS hatası:", e); return { ok: false, error: String(e) }; } } // Konsoldan elle test etmek için: // pushToSheet({type:"whitelist", email:"deneme@x.com", lang:"tr", source:"console"}) Object.assign(window, { SHEETS_ENDPOINT, pushToSheet, TURNSTILE_SITE_KEY, TurnstileWidget }); /* ============================================================ THEME TOGGLE (light ↔ dark) Lives in the nav next to the lang toggle. Persists to localStorage.peticota.theme. System preference is the default — set by the inline init script in each HTML file, before paint. ============================================================ */ function ThemeToggle() { const [theme, setTheme] = React.useState(() => { if (typeof document !== "undefined") { return document.documentElement.getAttribute("data-theme") || "light"; } return "light"; }); React.useEffect(() => { document.documentElement.setAttribute("data-theme", theme); try { localStorage.setItem("peticota.theme", theme); } catch (e) {} }, [theme]); const isDark = theme === "dark"; const toggle = () => setTheme(isDark ? "light" : "dark"); return ( ); } Object.assign(window, { ThemeToggle }); const cartRead = () => { try { const raw = localStorage.getItem(CART_KEY); if (!raw) return []; const arr = JSON.parse(raw); return Array.isArray(arr) ? arr : []; } catch (e) { return []; } }; const cartWrite = (arr) => { try { localStorage.setItem(CART_KEY, JSON.stringify(arr)); } catch (e) {} window.dispatchEvent(new CustomEvent("peticota:cart-changed")); }; function useCart() { const [items, setItems] = React.useState(() => cartRead()); React.useEffect(() => { const onChange = () => setItems(cartRead()); window.addEventListener("peticota:cart-changed", onChange); window.addEventListener("storage", onChange); return () => { window.removeEventListener("peticota:cart-changed", onChange); window.removeEventListener("storage", onChange); }; }, []); const add = (item) => { const cur = cartRead(); cur.push({ ...item, id: item.id || `it_${Date.now()}_${Math.random().toString(36).slice(2,6)}` }); cartWrite(cur); }; const remove = (id) => cartWrite(cartRead().filter((x) => x.id !== id)); const setQty = (id, q) => cartWrite( cartRead().map((x) => (x.id === id ? { ...x, qty: Math.max(1, q) } : x)) ); const clear = () => cartWrite([]); const count = items.reduce((s, x) => s + (x.qty || 1), 0); const subtotal = items.reduce((s, x) => s + (x.price || 0) * (x.qty || 1), 0); return { items, add, remove, setQty, clear, count, subtotal }; } /* ============================================================ CART BUTTON & DRAWER ============================================================ */ function CartButton({ lang }) { const { count } = useCart(); const open = () => window.dispatchEvent(new CustomEvent("peticota:cart-open")); const label = lang === "en" ? "Cart" : "Sepet"; return ( ); } function CartDrawer({ lang = "tr" }) { const { items, remove, setQty, subtotal, clear } = useCart(); const [open, setOpen] = React.useState(false); React.useEffect(() => { const onOpen = () => setOpen(true); window.addEventListener("peticota:cart-open", onOpen); return () => window.removeEventListener("peticota:cart-open", onOpen); }, []); React.useEffect(() => { document.body.style.overflow = open ? "hidden" : ""; return () => { document.body.style.overflow = ""; }; }, [open]); const T = lang === "en" ? { title: "Cart", empty: "Your cart is empty.", emptyCta: "Browse products", subtotal: "Subtotal", checkout: "Checkout", clear: "Clear", remove: "Remove", note: "Production: 7 — 10 working days · Free shipping within Türkiye" } : { title: "Sepet", empty: "Sepetin boş.", emptyCta: "Ürünleri keşfet", subtotal: "Ara toplam", checkout: "Siparişi tamamla", clear: "Temizle", remove: "Sil", note: "Üretim: 7 — 10 iş günü · Türkiye içi kargo ücretsiz" }; return ( <>
setOpen(false)}>
); } /* ============================================================ Toast — used after add to cart ============================================================ */ function showCartToast(name, lang = "tr") { const wrap = document.createElement("div"); wrap.className = "cart-toast"; wrap.innerHTML = ` ${lang === "en" ? "Added" : "Sepete eklendi"} ${name} `; document.body.appendChild(wrap); requestAnimationFrame(() => wrap.classList.add("in")); const btn = wrap.querySelector("button"); btn.onclick = () => { window.dispatchEvent(new CustomEvent("peticota:cart-open")); wrap.classList.remove("in"); setTimeout(() => wrap.remove(), 400); }; setTimeout(() => { wrap.classList.remove("in"); setTimeout(() => wrap.remove(), 400); }, 3200); } Object.assign(window, { useCart, CartButton, CartDrawer, showCartToast });