/* 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 });