// Agente de Contas — animated chat flow with typing, selection, error, success. const { useState, useEffect, useRef, useCallback } = React; const ITEMS = [ { id: "plano", title: "Atualização do plano", desc: "Reajuste aplicado ao seu plano atual", value: "+ R$ 20,00" }, { id: "juros", title: "Juros e multa", desc: "Aplicados devido ao pagamento após o vencimento", value: "+ R$ 28,99" }, ]; // **bold** + \n aware text renderer (TIM agent messages are plain text on white). function Rich({ text, size = 16 }) { return text.split("\n").map((line, li) => ( {line.split("**").map((part, i) => i % 2 ? {part} : {part})} )); } function UserBubble({ text }) { return (
{text}
); } function Typing() { return (
{[0,1,2].map(i => )}
); } function Feedback() { const [v, setV] = useState(null); return (
); } // The two compared bills: Em atraso ⇄ A vencer. function CompareCards() { const Card = ({ flag, flagColor, flagBg, icon, value, month, due }) => (
{icon} {flag} {value} {month}
); return (
{Ic.refresh("#000", 22)}
); } function ItemsList({ selected, onToggle, locked }) { return (
{ITEMS.map(it => { const on = selected.includes(it.id); return ( ); })}
); } function ProtocolCard() { const [copied, setCopied] = useState(false); return (
Protocolo 123178127389
); } // Pill buttons for the action bar. // Button - Pill (Figma node 1:4922): radius 12, inset 1px border for outline, // 14px/500 label, h48 medium. Not a full pill — rounded rectangle. function Pill({ children, variant = "outline-blue", onClick, disabled, grow, icon }) { const base = { height: 48, borderRadius: 12, fontSize: 14, fontWeight: 500, lineHeight: "20px", cursor: disabled ? "default" : "pointer", padding: "14px 16px", display: "inline-flex", alignItems: "center", justifyContent: "center", gap: 8, fontFamily: "'Hanken Grotesk',system-ui", flex: grow ? 1 : undefined, whiteSpace: "nowrap", border: "none" }; const styles = { "outline-blue": { ...base, boxShadow: "inset 0 0 0 1px " + TIM.blue, background: "#fff", color: TIM.blue }, "outline-red": { ...base, boxShadow: "inset 0 0 0 1px " + TIM.red, background: "#fff", color: TIM.red }, "contained": { ...base, background: TIM.blue, color: "#fff" }, "disabled": { ...base, boxShadow: "inset 0 0 0 1px " + TIM.stroke, background: "#fff", color: TIM.stroke }, }; return ; } function AgenteScreen({ onClose }) { const [msgs, setMsgs] = useState([]); const [typing, setTyping] = useState(false); const [stage, setStage] = useState("loading"); // loading | intro | review | done const [selected, setSelected] = useState([]); const [error, setError] = useState(false); const scrollRef = useRef(null); const idRef = useRef(0); const speed = (window.__protoSpeed || 1); const nid = () => ++idRef.current; const scrollDown = useCallback(() => { requestAnimationFrame(() => { const s = scrollRef.current; if (s) s.scrollTop = s.scrollHeight; }); }, []); // generic: append user bubble, show typing, then append an agent turn. const sequence = useCallback((userText, agentTurn, nextStage) => { setMsgs(m => [...m, { id: nid(), role: "user", text: userText }]); scrollDown(); setTimeout(() => { setTyping(true); scrollDown(); }, 450 / speed); setTimeout(() => { setTyping(false); setMsgs(m => [...m, { id: nid(), role: "agent", ...agentTurn, feedback: true }]); setStage(nextStage); scrollDown(); }, (450 + 1350) / speed); }, [scrollDown, speed]); // intro runs on mount useEffect(() => { sequence( "Entenda seus valores da fatura", { blocks: [ { kind: "text", text: "Identificamos que você teve uma variação no valor na sua fatura e vamos te explicar agora:" }, { kind: "compare" }, { kind: "text", text: "Seu plano foi atualizado e teve um acréscimo de **R$ 20,00**. Também houve a incidência de Juros e Multas devido ao atraso de **(XX) dias** no pagamento da fatura anterior." }, ]}, "intro" ); }, []); // eslint-disable-line const onRevisar = () => { setStage("loading"); sequence( "Revisão dos valores da fatura", { blocks: [ { kind: "text", text: "Estes são os itens que aumentaram o valor da sua conta. Aqui você pode ver mais detalhes ou escolher quais deseja contestar:" }, { kind: "items" }, ]}, "review" ); }; const toggle = (id) => { setError(false); setSelected(s => s.includes(id) ? s.filter(x => x !== id) : [...s, id]); }; const onConfirmar = () => { if (!selected.length) { setError(true); setTimeout(() => setError(false), 3500); scrollDown(); return; } setStage("loading"); const titles = ITEMS.filter(i => selected.includes(i.id)).map(i => i.title); const list = titles.length > 1 ? titles.slice(0, -1).join(", ") + " e " + titles[titles.length - 1] : titles[0]; sequence( `Enviar os itens ${list} para análise um de nossos especialistas.`, { blocks: [ { kind: "text", text: "✅ **Sua solicitação foi registrada com sucesso!**\n\nEntraremos em contato pelo **WhatsApp** em até 24 horas.\n\nFique atento para garantir que receba todas as informações com segurança.\n\nAguarde que um de nossos especialistas vai entrar em contato com você." }, { kind: "protocol" }, ]}, "done" ); }; const renderBlock = (b, locked) => { if (b.kind === "text") return
; if (b.kind === "compare") return ; if (b.kind === "items") return ; if (b.kind === "protocol") return ; return null; }; return (
{/* header */}
{/* error toast */} {error && (
{Ic.alertTri(TIM.red, 22)} Nenhum item selecionado. Por favor, escolha um item antes de continuar.
)} {/* chat */}
{msgs.map(m => m.role === "user" ?
: (
{m.blocks.map((b, i) => {renderBlock(b, stage === "done" || (stage !== "review"))})} {m.feedback && }
) )} {typing && }
{/* action bar */}
{stage === "intro" && (<> Revisar valores }>Encerrar )} {stage === "review" && (<> Confirmar seleção }>Encerrar )} {stage === "done" && ( Continuar via whatsapp )}
); } Object.assign(window, { AgenteScreen });