2026-06-08 15:12:13 +00:00
import { useState , useEffect , useRef , type FormEvent } from 'react' ;
import { Loader2 , Send } from 'lucide-react' ;
type MessageType = 'text' | 'audio' | 'image' | 'video' | 'embed' ;
interface MessageConfig {
text : string ;
delay? : number ;
type ? : MessageType ;
}
interface OptionConfig {
text : string ;
nextId? : string ;
actionLink? : string ;
}
interface FunnelNode {
id : string ;
messages : MessageConfig [ ] ;
options? : OptionConfig [ ] ;
requireEmail? : boolean ;
}
interface RenderedMessage {
id : string ;
sender : 'bot' | 'user' ;
type : MessageType ;
content : string ;
}
const funnelData : FunnelNode [ ] = [
{
id : "start" ,
messages : [
{ text : "Oi! 👋 Tudo bem? Sou do atendimento da Festa Mágica IA." , delay : 1000 , type : "text" } ,
{ text : "Posso te mostrar rapidinho como criar a festa do seu filho gastando muito menos?" , delay : 1500 , type : "text" }
] ,
options : [
{ text : "Pode!" , nextId : "step2" } ,
{ text : "Como assim?" , nextId : "step2" } ,
{ text : "Já conheço, quero comprar agora" , nextId : "step4" }
]
} ,
{
id : "step2" ,
messages : [
{ text : "Mandar fazer personalizados é caro e demora, né?" , delay : 1800 , type : "text" } ,
{ text : "Nossa Inteligência Artificial permite que você mesmo faça tudo na hora, do celular." , delay : 2000 , type : "text" } ,
{ text : "Assiste esse vídeo aqui embaixo que mostra a ferramenta funcionando e como é fácil criar as imagens e os kits👇" , delay : 1500 , type : "text" } ,
{ text : "https://s3.seureview.com.br/festamagica/0510(2).mp4" , delay : 2000 , type : "video" }
] ,
options : [
{ text : "Que legal!" , nextId : "step3" }
]
} ,
{
id : "step3" ,
messages : [
{ text : "Legal né? A IA cria um personagem 3D super realista tipo Disney/Pixar com o rostinho dele(a)!" , delay : 1800 , type : "text" } ,
{ text : "Olha essa transformação real que a gente fez aqui — essa é a foto original do Miguel, filho da nossa fundadora:" , delay : 1500 , type : "text" } ,
{ text : "/images/antes-depois/miguel-real.webp" , delay : 1500 , type : "image" } ,
{ text : "E esse foi o personagem 3D que a nossa IA criou com o rostinho dele, pronto pra virar topo de bolo, convite, adesivo... tudo no tema! 🤩" , delay : 1800 , type : "text" } ,
{ text : "/images/antes-depois/miguel-avatar.webp" , delay : 1800 , type : "image" } ,
{ text : "Mágico, né? E ela já gera todos os arquivos em PDF prontinhos pra imprimir." , delay : 1800 , type : "text" }
] ,
options : [
{ text : "Amei! Quanto custa?" , nextId : "step4" }
]
} ,
{
id : "step4" ,
messages : [
{ text : "Bem menos que você imagina! 🥰" , delay : 1200 , type : "text" } ,
{ text : "Liberamos um Pacote Inicial promocional por apenas R$ 9,99 (pagamento único)." , delay : 1500 , type : "text" } ,
{ text : "Você já entra com 10 créditos pra gerar os itens que quiser — e a primeira imagem é por nossa conta, não consome nenhum crédito seu! 🎁" , delay : 1800 , type : "text" } ,
{ text : "Quer garantir o seu agora?" , delay : 1200 , type : "text" }
] ,
options : [
{ text : "Eu quero!" , nextId : "checkout_step" }
]
} ,
{
id : "checkout_step" ,
messages : [
{ text : "Ótimo! Pra gente gerar o seu acesso seguro, qual é o seu melhor e-mail?" , delay : 1000 , type : "text" }
] ,
requireEmail : true
}
] ;
let sharedAudioCtx : AudioContext | null = null ;
function getAudioContext() {
if ( ! sharedAudioCtx ) {
const AudioContextClass = window . AudioContext || ( window as any ) . webkitAudioContext ;
if ( AudioContextClass ) {
sharedAudioCtx = new AudioContextClass ( ) ;
}
}
return sharedAudioCtx ;
}
function playMessageSound() {
try {
const ctx = getAudioContext ( ) ;
if ( ! ctx ) return ;
if ( ctx . state === 'suspended' ) ctx . resume ( ) ;
const osc = ctx . createOscillator ( ) ;
const gain = ctx . createGain ( ) ;
osc . connect ( gain ) ;
gain . connect ( ctx . destination ) ;
osc . type = 'sine' ;
osc . frequency . setValueAtTime ( 800 , ctx . currentTime ) ;
osc . frequency . exponentialRampToValueAtTime ( 300 , ctx . currentTime + 0.1 ) ;
gain . gain . setValueAtTime ( 0.2 , ctx . currentTime ) ;
gain . gain . exponentialRampToValueAtTime ( 0.01 , ctx . currentTime + 0.1 ) ;
osc . start ( ctx . currentTime ) ;
osc . stop ( ctx . currentTime + 0.1 ) ;
} catch ( e ) { }
}
function playTypingSound() {
try {
const ctx = getAudioContext ( ) ;
if ( ! ctx ) return ;
if ( ctx . state === 'suspended' ) ctx . resume ( ) ;
const osc = ctx . createOscillator ( ) ;
const gain = ctx . createGain ( ) ;
osc . connect ( gain ) ;
gain . connect ( ctx . destination ) ;
osc . type = 'triangle' ;
osc . frequency . setValueAtTime ( 200 , ctx . currentTime ) ;
gain . gain . setValueAtTime ( 0.01 , ctx . currentTime ) ;
gain . gain . exponentialRampToValueAtTime ( 0.001 , ctx . currentTime + 0.05 ) ;
osc . start ( ctx . currentTime ) ;
osc . stop ( ctx . currentTime + 0.05 ) ;
} catch ( e ) { }
}
export default function WhatsAppFunnel() {
const [ messages , setMessages ] = useState < RenderedMessage [ ] > ( [ ] ) ;
const [ isTyping , setIsTyping ] = useState ( false ) ;
const [ currentOptions , setCurrentOptions ] = useState < OptionConfig [ ] > ( [ ] ) ;
const [ currentRequireEmail , setCurrentRequireEmail ] = useState ( false ) ;
const [ emailInput , setEmailInput ] = useState ( '' ) ;
const [ isLoadingCheckout , setIsLoadingCheckout ] = useState ( false ) ;
const chatEndRef = useRef < HTMLDivElement > ( null ) ;
const scrollToBottom = ( ) = > {
setTimeout ( ( ) = > {
chatEndRef . current ? . scrollIntoView ( { behavior : 'smooth' } ) ;
} , 100 ) ;
} ;
const playNode = async ( nodeId : string ) = > {
setCurrentOptions ( [ ] ) ;
setCurrentRequireEmail ( false ) ;
const node = funnelData . find ( ( n ) = > n . id === nodeId ) ;
if ( ! node ) return ;
for ( let i = 0 ; i < node . messages . length ; i ++ ) {
const msg = node . messages [ i ] ;
const delayBefore = msg . delay
? msg . delay
: ( ( msg . type && msg . type !== 'text' ) ? 1500 : Math.min ( 400 + ( msg . text . length * 30 ) , 3000 ) ) ;
setIsTyping ( true ) ;
scrollToBottom ( ) ;
const typingInterval = setInterval ( ( ) = > {
if ( Math . random ( ) > 0.3 ) playTypingSound ( ) ;
} , 150 ) ;
await new Promise ( ( r ) = > setTimeout ( r , delayBefore ) ) ;
clearInterval ( typingInterval ) ;
setIsTyping ( false ) ;
playMessageSound ( ) ;
setMessages ( ( prev ) = > [
. . . prev ,
{
id : Math.random ( ) . toString ( ) ,
sender : 'bot' ,
type : msg . type || 'text' ,
content : msg.text ,
} ,
] ) ;
scrollToBottom ( ) ;
await new Promise ( ( r ) = > setTimeout ( r , 400 ) ) ;
}
if ( node . options && node . options . length > 0 ) {
setCurrentOptions ( node . options ) ;
scrollToBottom ( ) ;
}
if ( node . requireEmail ) {
setCurrentRequireEmail ( true ) ;
scrollToBottom ( ) ;
}
} ;
const initialized = useRef ( false ) ;
useEffect ( ( ) = > {
if ( ! initialized . current ) {
initialized . current = true ;
getAudioContext ( ) ;
2026-06-17 22:07:15 +00:00
playNode ( 'start' ) ;
2026-06-08 15:12:13 +00:00
}
} , [ ] ) ;
const handleOptionClick = ( opt : OptionConfig ) = > {
const ctx = getAudioContext ( ) ;
if ( ctx && ctx . state === 'suspended' ) {
ctx . resume ( ) ;
}
setMessages ( ( prev ) = > [
. . . prev ,
{
id : Math.random ( ) . toString ( ) ,
sender : 'user' ,
type : 'text' ,
content : opt.text ,
} ,
] ) ;
setCurrentOptions ( [ ] ) ;
scrollToBottom ( ) ;
if ( opt . actionLink ) {
setTimeout ( ( ) = > {
window . location . href = opt . actionLink ! ;
} , 800 ) ;
} else if ( opt . nextId ) {
setTimeout ( ( ) = > {
playNode ( opt . nextId ! ) ;
} , 600 ) ;
}
} ;
const handleEmailSubmit = async ( e : FormEvent ) = > {
e . preventDefault ( ) ;
if ( ! emailInput ) return ;
// Add user email message and a bot "loading" message
setMessages ( ( prev ) = > [
. . . prev ,
{
id : Math.random ( ) . toString ( ) ,
sender : 'user' ,
type : 'text' ,
content : emailInput ,
} ,
{
id : 'loading-checkout' ,
sender : 'bot' ,
type : 'text' ,
2026-06-16 01:19:31 +00:00
content : 'Perfeito! Abrindo o checkout seguro pra você 🎉' ,
2026-06-08 15:12:13 +00:00
}
] ) ;
2026-06-16 01:19:31 +00:00
2026-06-08 15:12:13 +00:00
setCurrentRequireEmail ( false ) ;
setIsLoadingCheckout ( true ) ;
scrollToBottom ( ) ;
if ( typeof window !== 'undefined' && 'fbq' in window ) {
( window as any ) . fbq ( 'track' , 'InitiateCheckout' ) ;
}
2026-06-16 01:19:31 +00:00
setTimeout ( ( ) = > {
2026-06-17 22:07:15 +00:00
window . location . href = ` https://festamagicaia.com.br/checkout?plan=starter&source=funil&email= ${ encodeURIComponent ( emailInput ) } ` ;
2026-06-16 01:19:31 +00:00
} , 800 ) ;
2026-06-08 15:12:13 +00:00
} ;
const renderMessageContent = ( m : RenderedMessage ) = > {
if ( m . type === 'image' ) {
return (
< img
src = { m . content }
className = "max-w-full sm:max-w-[240px] rounded-lg object-cover mt-1"
alt = "Media"
/ >
) ;
} else if ( m . type === 'video' ) {
return (
< video
controls
playsInline
className = "max-w-full sm:max-w-[240px] rounded-lg mt-1"
src = { m . content }
/ >
) ;
} else if ( m . type === 'audio' ) {
return (
< audio
controls
className = "w-[200px] h-[40px] rounded-full mt-1"
src = { m . content }
/ >
) ;
} else if ( m . type === 'embed' ) {
return (
< div
dangerouslySetInnerHTML = { { __html : m.content } }
className = "w-[260px] max-w-full sm:max-w-[300px] rounded-lg overflow-hidden mt-1 [&_iframe]:w-full [&_iframe]:aspect-video [&_iframe]:rounded-lg"
/ >
) ;
}
// Simple text handling including line breaks if necessary
return < span className = "whitespace-pre-wrap word-break" > { m . content } < / span > ;
} ;
return (
< div className = "h-[100dvh] bg-black w-full flex justify-center font-sans overflow-hidden" >
< div className = "w-full max-w-[450px] bg-[#e5ddd5] flex flex-col relative shadow-[0_0_50px_rgba(0,0,0,0.5)] overflow-hidden h-full" >
{ /* Header */ }
< div className = "bg-[#075e54] text-white p-2.5 px-4 flex items-center gap-3 sticky top-0 z-10 shadow-md" >
< div className = "relative" >
< img
src = "https://s3.seureview.com.br/festamagica/6a591904-cf2a-4a51-8581-1891373eea29/805441e0-925e-4674-b7b3-ba640431eeb1/convite-digital.webp"
className = "w-10 h-10 rounded-full object-cover bg-[#075e54] border border-white/20"
alt = "Avatar"
/ >
{ /* Online indicator */ }
< div className = "absolute bottom-0 right-0 w-3 h-3 bg-[#25d366] border-2 border-[#075e54] rounded-full" > < / div >
< / div >
< div className = "flex flex-col flex-1 leading-tight" >
< span className = "font-semibold text-[16px] truncate" > Atendimento Festa Mágica < / span >
< span className = "text-[12px] font-normal opacity-90 h-4" >
{ isTyping ? 'digitando...' : 'online' }
< / span >
< / div >
< / div >
{ /* Chat Area */ }
< div
className = "flex-1 p-4 flex flex-col gap-2.5 overflow-y-auto pb-8 scroll-smooth"
style = { {
backgroundImage : "url('https://web.whatsapp.com/img/bg-chat-tile-light_04fcacde539c58cca6745483d4858c52.png')" ,
opacity : 1 ,
backgroundSize : 'contain' ,
backgroundRepeat : 'repeat' ,
scrollbarWidth : 'none' ,
msOverflowStyle : 'none'
} }
>
< style > { `
. flex - 1 : : - webkit - scrollbar {
display : none ;
}
` }</style>
{ messages . map ( ( m ) = > (
< div
key = { m . id }
className = { ` max-w-[85%] sm:max-w-[75%] p-2 px-3 rounded-lg text-[15px] leading-snug shadow-sm transform transition-all animate-in fade-in slide-in-from-bottom-2
$ {
m . sender === 'bot'
? 'bg-white text-[#111111] self-start rounded-tl-sm'
: 'bg-[#dcf8c6] text-[#111111] self-end rounded-tr-sm'
} ` }
>
{ renderMessageContent ( m ) }
{ /* Fake timestamp */ }
< div className = "flex justify-end mt-1 opacity-60" >
< span className = "text-[10px]" > { new Date ( ) . toLocaleTimeString ( [ ] , { hour : '2-digit' , minute : '2-digit' } ) } < / span >
{ m . sender === 'user' && (
< svg viewBox = "0 0 16 15" width = "16" height = "15" className = "ml-1 text-[#4fc3f7]" >
< path fill = "currentColor" d = "M15.01 3.316l-.478-.372a.365.365 0 0 0-.51.063L8.666 9.879a.32.32 0 0 1-.484.033l-.358-.325a.319.319 0 0 0-.484.032l-.378.483a.418.418 0 0 0 .036.541l1.32 1.266c.143.14.361.125.484-.033l6.272-8.048a.366.366 0 0 0-.064-.512zm-4.1 0l-.478-.372a.365.365 0 0 0-.51.063L4.566 9.879a.32.32 0 0 1-.484.033L1.891 7.769a.366.366 0 0 0-.515.006l-.423.433a.364.364 0 0 0 .006.514l3.258 3.185c.143.14.361.125.484-.033l6.272-8.048a.365.365 0 0 0-.063-.51z" > < / path >
< / svg >
) }
< / div >
< / div >
) ) }
{ isTyping && (
< div className = "bg-white text-gray-500 text-[14px] self-start rounded-lg rounded-tl-sm p-2.5 px-4 shadow-sm italic animate-pulse" >
digitando . . .
< / div >
) }
{ currentOptions . length > 0 && (
< div className = "flex flex-col gap-2 mt-3 self-end w-full max-w-[300px]" >
{ currentOptions . map ( ( opt , i ) = > (
< button
key = { i }
onClick = { ( ) = > handleOptionClick ( opt ) }
className = "w-full bg-[#128c7e] text-white font-bold py-3.5 px-4 rounded-full shadow hover:bg-[#075e54] transition-colors text-[15px]"
>
{ opt . text }
< / button >
) ) }
< / div >
) }
< div ref = { chatEndRef } className = "h-4" / >
< / div >
{ /* Email Input Area */ }
{ currentRequireEmail && (
< div className = "bg-[#f0f0f0] p-2 sm:p-3 pb-8 sm:pb-3 w-full border-t border-gray-300" >
< form onSubmit = { handleEmailSubmit } className = "flex items-center gap-2" >
< input
type = "email"
value = { emailInput }
onChange = { ( e ) = > setEmailInput ( e . target . value ) }
placeholder = "Digite seu melhor e-mail..."
className = "flex-1 bg-white rounded-full px-4 py-3 outline-none text-[15px] shadow-sm border border-gray-200 focus:border-[#128c7e]"
required
disabled = { isLoadingCheckout }
/ >
< button
type = "submit"
disabled = { isLoadingCheckout }
className = "bg-[#00a884] hover:bg-[#128c7e] text-white w-12 h-12 rounded-full flex items-center justify-center shrink-0 shadow-sm transition-colors disabled:opacity-70"
>
{ isLoadingCheckout ? < Loader2 className = "w-6 h-6 animate-spin" / > : < Send className = "w-5 h-5 ml-1" / > }
< / button >
< / form >
< / div >
) }
< / div >
< / div >
) ;
}