'use client'; /** * RichMessageComposer — modal completo para compor e enviar mensagens ricas. * * Suporta 6 tipos de mensagem WhatsApp via InfiniteAPI/Baileys: * TEXT — texto simples (padrão atual) * BUTTONS — até 3 botões de resposta rápida (nativeButtons reply) * INTERACTIVE — botões CTA: URL / Copiar / Ligar (nativeButtons url/copy/call) * LIST — lista dropdown com seções e itens (nativeList) * POLL — enquete nativa do WhatsApp (poll) * CAROUSEL — cards deslizáveis com imagem e botões (nativeCarousel) * * Props: * isOpen — controla visibilidade * onClose — callback ao fechar * instanceId — instância WhatsApp ativa * jid — JID destino (pré-preenchido quando aberto do chat) * onSent — callback após envio bem-sucedido * initialPayload — pré-preenche o form (ao usar um template) * initialType — tipo inicial selecionado */ import React, { useState, useCallback, useEffect, useId } from 'react' import { createPortal } from 'react-dom' import { motion, AnimatePresence } from 'framer-motion' import { X, Send, Plus, Trash2, ChevronDown, ChevronUp, Type, MousePointerClick, List, BarChart2, LayoutGrid, Link, BookmarkPlus, Calendar, } from 'lucide-react' import { richMessageApi, templateApi, type TemplateType, type RichPayload } from '../services/chatApiService' // ─── Tipos locais ──────────────────────────────────────────────────────────── type MessageType = TemplateType interface BtnItem { id: string; text: string } interface CtaItem { type: 'url' | 'copy' | 'call'; text: string; extra: string } interface ListRow { id: string; title: string; description: string } interface ListSection { title: string; rows: ListRow[] } interface CarouselBtn { id: string; text: string } interface CarouselCard { title: string; body: string; footer: string; imageUrl: string; buttons: CarouselBtn[] } interface FormState { // TEXT text: string // BUTTONS btnText: string btnFooter: string buttons: BtnItem[] // INTERACTIVE (CTA) ctaText: string ctaFooter: string ctaButtons: CtaItem[] // LIST listText: string listButtonText: string listFooter: string listSections: ListSection[] // POLL pollName: string pollSelectable: number pollOptions: string[] // CAROUSEL carouselText: string carouselFooter: string cards: CarouselCard[] } const defaultForm: FormState = { text: '', btnText: '', btnFooter: '', buttons: [{ id: 'btn1', text: '' }], ctaText: '', ctaFooter: '', ctaButtons: [{ type: 'url', text: '', extra: '' }], listText: '', listButtonText: 'Ver opções', listFooter: '', listSections: [{ title: 'Seção 1', rows: [{ id: 'r1', title: '', description: '' }] }], pollName: '', pollSelectable: 1, pollOptions: ['', ''], carouselText: '', carouselFooter: '', cards: [{ title: '', body: '', footer: '', imageUrl: '', buttons: [{ id: 'cb1', text: '' }] }], } // ─── Tipo selector ──────────────────────────────────────────────────────────── const TYPE_TABS: Array<{ type: MessageType; label: string; icon: React.ReactNode; color: string }> = [ { type: 'TEXT', label: 'Texto', icon: , color: '#667781' }, { type: 'BUTTONS', label: 'Botões', icon: , color: '#0ea5e9' }, { type: 'INTERACTIVE', label: 'CTA', icon: , color: '#8b5cf6' }, { type: 'LIST', label: 'Lista', icon: , color: '#f59e0b' }, { type: 'POLL', label: 'Enquete', icon: , color: '#10b981' }, { type: 'CAROUSEL', label: 'Carrossel', icon: , color: '#ef4444' }, ] // ─── Helpers ────────────────────────────────────────────────────────────────── function uid() { return `id_${Date.now()}_${Math.random().toString(36).slice(2, 7)}` } function buildPayload(type: MessageType, form: FormState): RichPayload { switch (type) { case 'TEXT': return { text: form.text } case 'BUTTONS': return { text: form.btnText || undefined, footer: form.btnFooter || undefined, nativeButtons: form.buttons.filter(b => b.id && b.text).map(b => ({ type: 'reply' as const, id: b.id, text: b.text, })), } case 'INTERACTIVE': return { text: form.ctaText || undefined, nativeButtons: form.ctaButtons.filter(b => b.text && b.extra).map(b => { if (b.type === 'url') return { type: 'url' as const, text: b.text, url: b.extra } if (b.type === 'copy') return { type: 'copy' as const, text: b.text, copyText: b.extra } return { type: 'call' as const, text: b.text, phoneNumber: b.extra } }), } case 'LIST': { const sections = form.listSections .map(s => ({ title: s.title, rows: s.rows.filter(r => r.id && r.title) })) .filter(s => s.rows.length > 0) return { text: form.listText || undefined, nativeList: { buttonText: form.listButtonText || 'Ver opções', sections, }, } } case 'POLL': return { poll: { name: form.pollName || 'Enquete', values: form.pollOptions.filter(Boolean), selectableCount: form.pollSelectable, }, } case 'CAROUSEL': { const cards = form.cards.map(c => ({ title: c.title || undefined, body: c.body || undefined, footer: c.footer || undefined, image: c.imageUrl ? { url: c.imageUrl } : undefined, buttons: c.buttons.filter(b => b.id && b.text).map(b => ({ type: 'reply' as const, id: b.id, text: b.text })), })) return { text: form.carouselText || undefined, nativeCarousel: { cards }, } } } } // ─── Sub-forms ──────────────────────────────────────────────────────────────── function TextForm({ form, set }: { form: FormState; set: (p: Partial) => void }) { return (