// ───────────────────────────────────────────────────────────── // ÁREA DO CRIADOR — Gerador de Tokens (acesso só pelo caminho /admin) // Simples como o original: senha → gerar → lista verde/vermelho. // Conversa com tokens_api.php (senha validada NO SERVIDOR — nunca no app). // Mesmo banco do burn_token.php (tokens_db.txt / usados.txt). // ───────────────────────────────────────────────────────────── const { useState: useStateAdmin } = React; // link bonito que você compartilha: .../comportamentos/analise/TOKEN function linkDoToken(tk) { return `${location.origin}/comportamentos/analise/${encodeURIComponent(tk)}`; } // gerador local (só pra pré-visualização sem servidor) function tokenLocalDemo(prefix) { const b = new Uint8Array(6); (window.crypto || window.msCrypto).getRandomValues(b); const hex = [...b].map((x) => x.toString(16).padStart(2, "0")).join("").toUpperCase(); return (prefix ? prefix + "_" : "") + hex; } function AdminScreen({ t, onBack }) { const [auth, setAuth] = useStateAdmin(false); const [senha, setSenha] = useStateAdmin(""); const [prefix, setPrefix] = useStateAdmin(""); const [qty, setQty] = useStateAdmin(1); const [tokens, setTokens] = useStateAdmin([]); // [{token, used}] const [busy, setBusy] = useStateAdmin(false); const [erro, setErro] = useStateAdmin(null); const [demo, setDemo] = useStateAdmin(false); const [copied, setCopied] = useStateAdmin(null); const [stats, setStats] = useStateAdmin(null); // {totais, por_dia} const [leads, setLeads] = useStateAdmin([]); const API = "tokens_api.php"; // entrar: valida senha no servidor e já traz a lista (verde/vermelho) const entrar = async (e) => { e && e.preventDefault(); if (!senha.trim()) { setErro("Digite a senha."); return; } setBusy(true); setErro(null); try { const r = await fetch(API, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ senha, action: "list" }), }); const data = await r.json(); if (!r.ok || data.status !== "success") { setErro(data.message || "Senha incorreta."); setBusy(false); return; } setTokens(data.tokens || []); setAuth(true); setDemo(false); buscarStats(); buscarLeads(); } catch (err) { // sem servidor (preview): entra em modo demonstração setAuth(true); setDemo(true); setTokens([]); } finally { setBusy(false); } }; const gerar = async () => { const n = Math.max(1, Math.min(100, parseInt(qty, 10) || 1)); setBusy(true); setErro(null); try { const r = await fetch(API, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ senha, action: "generate", prefix: prefix.trim(), quantity: n }), }); const data = await r.json(); if (!r.ok || data.status !== "success") { setErro(data.message || "Erro ao gerar."); setBusy(false); return; } setTokens(data.tokens || []); } catch (err) { // preview: gera localmente só pra demonstração const novos = Array.from({ length: n }, () => ({ token: tokenLocalDemo(prefix.trim()), used: false })); setTokens((prev) => [...novos, ...prev]); setDemo(true); } finally { setBusy(false); } }; const buscarLeads = async () => { try { const r = await fetch("leads.php", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ senha, action: "list" }), }); const data = await r.json(); if (r.ok && data.status === "success") setLeads(data.leads || []); } catch (e) {} }; const buscarStats = async () => { try { const r = await fetch("eventos.php", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ senha, action: "stats" }), }); const data = await r.json(); if (r.ok && data.status === "success") setStats({ totais: data.totais || {}, por_dia: data.por_dia || {} }); } catch (e) {} }; const atualizar = async () => { setBusy(true); setErro(null); buscarStats(); buscarLeads(); try { const r = await fetch(API, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ senha, action: "list" }), }); const data = await r.json(); if (r.ok && data.status === "success") setTokens(data.tokens || []); } catch (err) {} finally { setBusy(false); } }; const copiar = async (texto, id) => { try { await navigator.clipboard.writeText(texto); } catch { const ta = document.createElement("textarea"); ta.value = texto; document.body.appendChild(ta); ta.select(); document.execCommand("copy"); ta.remove(); } setCopied(id); setTimeout(() => setCopied(null), 1500); }; const lbl = { fontFamily: TYPE.sans, fontSize: 9.5, color: t.ink3, letterSpacing: "0.18em", textTransform: "uppercase", fontWeight: 700, marginBottom: 8, }; const inp = { width: "100%", padding: "13px 15px", boxSizing: "border-box", background: t.bg0, border: `1px solid ${t.border}`, borderRadius: RADIUS.md, color: t.ink1, fontFamily: TYPE.sans, fontSize: 14, fontWeight: 600, outline: "none", }; return (
{/* TOPO */}
Área do Criador {auth && ( )}
{/* ── LOGIN ── */} {!auth && (

Acesso
restrito

setSenha(e.target.value)} placeholder="Senha de admin" autoComplete="off" style={{ ...inp, textAlign: "center" }} />
{erro &&
{erro}
}
)} {/* ── GERADOR ── */} {auth && (

Gerador
de tokens

{/* MÉTRICAS — resumo do funil (grátis, do seu servidor) */} {stats && (
Funil (total)
{[ ["Iniciaram", stats.totais.iniciou || 0], ["Concluíram", stats.totais.concluiu || 0], ["Clic. consulta", stats.totais.clicou_consulta || 0], ["Compartilharam", stats.totais.compartilhou || 0], ].map(([nome, val], i) => (
{val}
{nome}
))}
{(() => { const ini = stats.totais.iniciou || 0; const con = stats.totais.clicou_consulta || 0; const tx = ini ? Math.round((con / ini) * 100) : 0; return (
Conversão p/ consulta {tx}%
); })()} {/* GRÁFICO — análises por dia (últimos 14 dias) */}
)} {/* FORM */}
Prefixo (opcional)
setPrefix(e.target.value)} placeholder="Ex: INSTA" style={inp} />
Qtd
setQty(e.target.value)} style={inp} />
{erro &&
{erro}
} {demo &&
Pré-visualização (sem servidor). Na Hostinger os tokens são salvos no banco e o status fica real.
}
{/* LISTA VERDE / VERMELHO */} {tokens.length > 0 && (
{tokens.map((row, i) => { const link = linkDoToken(row.token); const cor = row.used ? "#ef4444" : "#22c55e"; return (
{row.token}{row.used ? " (USADO)" : ""}
{link}
); })}
)} {/* LEADS — contatos deixados no final da análise */} {leads.length > 0 && (
Contatos recebidos ({leads.length})
{leads.map((ld, i) => { const num = (ld.whatsapp || "").replace(/\D/g, "").replace(/^55/, ""); const msg = encodeURIComponent("Olá! Aqui é o Dr. Ulisses Nakagawa. Vi que você fez a Análise de Comportamento. Quer agendar uma consulta para alcançar os seus objetivos?"); return ( {ld.nome} {ld.whatsapp} · {ld.data} ); })}
)}
)}
); } function adminMiniBtn(t, active, ghost) { return { flex: 1, padding: "10px 8px", borderRadius: RADIUS.sm, cursor: "pointer", border: `1px solid ${active ? t.ink1 : t.border}`, background: ghost ? "transparent" : (active ? t.ink1 : t.bg2), color: ghost ? t.ink2 : (active ? t.bg0 : t.ink1), fontFamily: TYPE.sans, fontSize: 11, fontWeight: 700, letterSpacing: "0.06em", textTransform: "uppercase", }; } // ── Gráfico de barras: análises iniciadas por dia (últimos 14 dias) ── function DailyChart({ t, porDia }) { const dias = []; const hoje = new Date(); for (let i = 13; i >= 0; i--) { const d = new Date(hoje); d.setDate(d.getDate() - i); const iso = d.toISOString().slice(0, 10); const v = (porDia && porDia[iso] && porDia[iso].iniciou) || 0; dias.push({ iso, v, dia: iso.slice(8, 10) }); } const max = Math.max(1, ...dias.map((x) => x.v)); return (
Análises por dia · 14 dias
{dias.map((d, i) => (
{d.v || ""}
{d.dia}
))}
); } Object.assign(window, { AdminScreen });