// 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 (
);
}
function Typing() {
return (
);
}
function Feedback() {
const [v, setV] = useState(null);
return (
setV(v === "up" ? null : "up")} style={{ background: "none", border: "none", cursor: "pointer", padding: 0 }}>
{Ic.thumbUp(v === "up" ? TIM.blue : TIM.txtGray, 20, v === "up" ? TIM.blue : "none")}
setV(v === "down" ? null : "down")} style={{ background: "none", border: "none", cursor: "pointer", padding: 0 }}>
{Ic.thumbDown(v === "down" ? TIM.red : TIM.txtGray, 20, v === "down" ? TIM.red : "none")}
);
}
// 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 (
onToggle(it.id)} style={{
textAlign: "left", display: "flex", gap: 12, alignItems: "center", padding: "16px 16px",
background: "#fff", borderRadius: 14, cursor: locked ? "default" : "pointer",
border: "1.5px solid " + (on ? TIM.blue : TIM.border), fontFamily: "'Hanken Grotesk',system-ui",
opacity: locked && !on ? 0.55 : 1, transition: "border-color .15s",
}}>
{it.title}
{it.desc}
{it.value}
{on && }
);
})}
);
}
function ProtocolCard() {
const [copied, setCopied] = useState(false);
return (
Protocolo
123178127389
{ setCopied(true); setTimeout(() => setCopied(false), 1600); }} style={{ background: "none", border: "none", cursor: "pointer", color: TIM.blue, fontSize: 17, fontWeight: 700, fontFamily: "'Hanken Grotesk',system-ui" }}>
{copied ? "Copiado" : "Copiar"}
);
}
// 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 {icon}{children} ;
}
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.
setError(false)} style={{ background: "none", border: "none", cursor: "pointer", padding: 2 }}>{Ic.close("#000", 20)}
)}
{/* 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 });