// dashboard.jsx — logged-in app shell (routes tab screens from dash-tabs.jsx) const { useState: useStateD, useRef: useRefD, useEffect: useEffectD } = React; const NAV = [ ["Overview", "grid"], ["Viewer Bot", "signal"], ["Chat Bot", "chat"], ["Ai Chatbot", "spark"], ["Poll Bot", "poll"], ["Follow Bot", "users"], ["Clip Viewbot", "scissors"], ["Chat Scraper", "chat"], ["KCIP Fix", "chart"], ["Billing", "card"], ["Settings", "gauge"], ]; const NAV_GROUPS = [ ["", ["Overview"]], ["BOTS", ["Viewer Bot", "Chat Bot", "Ai Chatbot", "Poll Bot", "Follow Bot", "Clip Viewbot"]], ["SERVICES", ["Chat Scraper", "KCIP Fix"]], ["ACCOUNT", ["Billing", "Settings"]], ]; const ICON_OF = Object.fromEntries(NAV); // Nav items that should wear a glowing "NEW!" pill. Remove the entry once it stops being new. const NEW_NAV = { "Chat Scraper": true }; // Styling is inline (not styles.css) on purpose: styles.css is served with no Cache-Control, so a // returning customer can hold a stale copy and the badge would render as bare text. The bundle is // content-hashed, so shipping the look in here guarantees it matches the markup. const NEW_BADGE = { flexShrink: 0, fontSize: 9, fontWeight: 800, letterSpacing: "0.06em", lineHeight: 1, padding: "3px 6px", borderRadius: 999, color: "var(--accent-ink)", background: "linear-gradient(160deg, var(--accent), var(--accent-deep))", animation: "kvbNewGlow 1.9s ease-in-out infinite", }; // Keyframes have to live in a stylesheet, so inject them once from the bundle itself. // prefers-reduced-motion is honoured - the pill stays, only the pulsing stops. if (typeof document !== "undefined" && !document.getElementById("kvb-new-badge-css")) { const kvbNewCss = document.createElement("style"); kvbNewCss.id = "kvb-new-badge-css"; kvbNewCss.textContent = "@keyframes kvbNewGlow{" + "0%,100%{box-shadow:0 0 4px 0 var(--accent-glow);transform:scale(1)}" + "50%{box-shadow:0 0 13px 3px var(--accent-glow);transform:scale(1.07)}" + "}" + "@media (prefers-reduced-motion:reduce){" + "[data-kvb-new]{animation:none !important}" + "}"; document.head.appendChild(kvbNewCss); } function Dashboard({ onNav, preview }) { const [active, setActive] = useStateD(() => { // Deep-link the initial tab via ?go= (e.g. a KCIP marketing CTA → ?go=KCIP%20Fix). try { var g = new URLSearchParams(window.location.search).get("go"); if (g && window.SCREENS && window.SCREENS[g]) return g; } catch (e) {} return "Overview"; }); const [profileOpen, setProfileOpen] = useStateD(false); // topbar avatar dropdown const Screen = (window.SCREENS && window.SCREENS[active]) || (() => null); // Announcement banner for a newly shipped feature. Dismissal is remembered per browser. const [showNewBanner, setShowNewBanner] = useStateD(function () { try { return localStorage.getItem("kvb_banner_scraper") !== "1"; } catch (e) { return true; } }); function dismissNewBanner() { setShowNewBanner(false); try { localStorage.setItem("kvb_banner_scraper", "1"); } catch (e) {} } // Topbar avatar: real uploaded picture, else gravatar (same rule as Settings + the // original dashboard.php), else the "K" initial as a last resort. const KVB_D = window.__KVB || {}; const avatarUrl = KVB_D.profilePicture || (KVB_D.emailHash ? ("https://www.gravatar.com/avatar/" + KVB_D.emailHash + "?d=mp") : null); return (
{/* Sidebar */} {/* Main */}
{/* Mobile tab nav (sidebar is hidden on small screens) */} {!preview && ( )} {/* Topbar */}
{active}
Engine online
setProfileOpen(o => !o)} style={{ width: 36, height: 36, borderRadius: "50%", overflow: "hidden", background: "linear-gradient(150deg, var(--accent), var(--accent-deep))", display: "grid", placeItems: "center", color: "var(--accent-ink)", fontWeight: 700, fontSize: 14, cursor: "pointer", flexShrink: 0, boxShadow: profileOpen ? "0 0 0 3px var(--accent-glow)" : "none", transition: "box-shadow .2s" }}> {avatarUrl ? { e.currentTarget.style.display = "none"; e.currentTarget.parentNode.appendChild(document.createTextNode("K")); }} /> : "K"}
{profileOpen && ( {/* click-catcher to close on outside click */}
setProfileOpen(false)} style={{ position: "fixed", inset: 0, zIndex: 60 }} />
{avatarUrl ? : "K"}
{KVB_D.email || "Your account"}
{KVB_D.plan ? KVB_D.plan + " plan" : "Free plan"}
{/* one REAL stat (the designer's mock stats are intentionally dropped) */}
Total credits {Number(KVB_D.balSum || 0).toLocaleString()}
{/* REAL logout (kills the session) — NOT the designer's fake onNav("landing") */}
)}
{!preview && showNewBanner && active !== "Chat Scraper" && (
New: Chat Scraper FREE
Collect real chat from any live channel into a downloadable list - or send it straight to your Chat Bot.
)}
); } /* Scaled, non-interactive render of the real dashboard for the hero window */ function DashboardPreview({ designW = 1240, designH = 760 }) { const wrap = useRefD(null); const [scale, setScale] = useStateD(0.5); useEffectD(() => { const fit = () => { if (wrap.current) setScale(wrap.current.clientWidth / designW); }; fit(); const ro = new ResizeObserver(fit); if (wrap.current) ro.observe(wrap.current); return () => ro.disconnect(); }, [designW]); return (
{}} preview />
); } Object.assign(window, { DashboardPreview });