import React, { useEffect, useState, useRef, useCallback } from 'react' import { useRouter } from 'next/router' import { motion, AnimatePresence } from 'framer-motion' import { Settings, Paintbrush, Globe, Bell, Mail, Users, Wrench, Upload, Save, Check, AlertTriangle, Eye, EyeOff, Send, Image as ImageIcon, Smile, RefreshCw, Webhook, } from 'lucide-react' import { useAuth } from '@/context/AuthContext' import { api } from '@/services/chatApiService' import { PageHeader, Card, Badge } from '@/components/ui' // ─── Types ──────────────────────────────────────────────────────────────────── interface SystemSettings { systemName: string timezone: string logoUrl: string | null faviconUrl: string | null accentColor: string sounds: { newMessage: boolean; notification: boolean; connectionStatus: boolean } smtp: { host: string; port: number; user: string; password: string; from: string; secure: boolean } emailTemplates: { welcome: string; passwordReset: string; trialExpiring: string } registration: { mode: 'open' | 'closed' | 'invite'; defaultPlanId: string | null } maintenance: { enabled: boolean; message: string } webhook: { url: string; secret: string } } const TABS = [ { id: 'branding', label: 'Branding', icon: Paintbrush }, { id: 'regional', label: 'Regional', icon: Globe }, { id: 'sounds', label: 'Sons', icon: Bell }, { id: 'email', label: 'E-mail', icon: Mail }, { id: 'registration', label: 'Registro', icon: Users }, { id: 'system', label: 'Sistema', icon: Wrench }, { id: 'deploys', label: 'Versões', icon: RefreshCw }, ] as const type Tab = typeof TABS[number]['id'] const TIMEZONES = [ 'America/Sao_Paulo','America/Manaus','America/Belem','America/Fortaleza', 'America/Recife','America/Maceio','America/Bahia','America/Cuiaba', 'America/Porto_Velho','America/Boa_Vista','America/Rio_Branco','America/Noronha', 'America/New_York','America/Chicago','America/Denver','America/Los_Angeles', 'Europe/London','Europe/Paris','Europe/Berlin','Europe/Lisbon', 'Asia/Tokyo','Asia/Shanghai','Asia/Kolkata','Australia/Sydney','UTC', ] // ─── Sub-components ─────────────────────────────────────────────────────────── function SectionTitle({ children }: { children: React.ReactNode }) { return

{children}

} function Field({ label, hint, children }: { label: string; hint?: string; children: React.ReactNode }) { return (
{children} {hint &&

{hint}

}
) } function TextInput({ value, onChange, placeholder, type = 'text', disabled }: { value: string; onChange: (v: string) => void; placeholder?: string; type?: string; disabled?: boolean }) { return ( onChange(e.target.value)} placeholder={placeholder} disabled={disabled} className="w-full bg-white/[0.03] border border-white/5 rounded-xl px-4 py-2.5 text-sm text-slate-200 placeholder-slate-600 focus:outline-none focus:border-brand-500/40 focus:bg-white/[0.05] transition-all disabled:opacity-40" /> ) } function Toggle({ checked, onChange, label, hint }: { checked: boolean; onChange: (v: boolean) => void; label: string; hint?: string }) { return (

{label}

{hint &&

{hint}

}
) } function AssetUpload({ label, current, onUpload, accept = 'image/*' }: { label: string; current: string | null; onUpload: (f: File) => Promise; accept?: string }) { const [dragging, setDragging] = useState(false) const [loading, setLoading] = useState(false) const inputRef = useRef(null) const API = process.env.NEXT_PUBLIC_API_URL ?? '' const handle = async (file: File) => { setLoading(true) try { await onUpload(file) } finally { setLoading(false) } } return (
{ e.preventDefault(); setDragging(true) }} onDragLeave={() => setDragging(false)} onDrop={e => { e.preventDefault(); setDragging(false); const f = e.dataTransfer.files[0]; if (f) handle(f) }} onClick={() => inputRef.current?.click()} className={`relative h-28 rounded-2xl border-2 border-dashed flex flex-col items-center justify-center gap-2 cursor-pointer transition-all ${dragging ? 'border-brand-500 bg-brand-500/5' : 'border-white/10 hover:border-white/20 bg-white/[0.02]'}`} > {current ? ( {label} ) : ( <> Arraste ou clique para enviar )} {loading && (
)} { const f = e.target.files?.[0]; if (f) handle(f) }} />
) } function SaveBar({ saving, saved, onSave }: { saving: boolean; saved: boolean; onSave: () => void }) { return (
) } // ─── Main Page ──────────────────────────────────────────────────────────────── export default function AdminSettings() { const { user, loading } = useAuth() const router = useRouter() const [tab, setTab] = useState('branding') const [settings, setSettings] = useState(null) const [saving, setSaving] = useState(false) const [saved, setSaved] = useState(false) const [smtpPassVisible, setSmtpPassVisible] = useState(false) const [sendingTest, setSendingTest] = useState(false) const [plans, setPlans] = useState<{ id: string; name: string }[]>([]) const [engine, setEngine] = useState<'infinite' | 'official'>('infinite') const [switchingEngine, setSwitchingEngine] = useState(false) // ── Estados de Controle de Versões & Deploys ── const [versionData, setVersionData] = useState<{ version: string; buildCount: number; lastDeploy: string; lastCommit?: { hash: string; message: string; author: string }; } | null>(null) const [deploysHistory, setDeploysHistory] = useState([]) const [triggeringDeploy, setTriggeringDeploy] = useState(false) const fetchDeployData = useCallback(async () => { try { const vRes = await fetch('/version.json?t=' + Date.now()) if (vRes.ok) { const vData = await vRes.json() setVersionData(vData) } } catch (e) {} try { const dRes = await fetch('/deploys.json?t=' + Date.now()) if (dRes.ok) { const dData = await dRes.json() setDeploysHistory(dData) } } catch (e) {} }, []) const triggerDeploy = async () => { if (!window.confirm('Deseja realmente iniciar uma nova compilação e deploy em produção agora?\n\nIsso irá atualizar o repositório, reinstalar dependências, compilar o frontend e backend, e reiniciar os serviços.')) return setTriggeringDeploy(true) try { await api.post('/api/admin/deploys/trigger') alert('Deploy automático iniciado em segundo plano! A VPS 4 está compilando os arquivos. O painel monitorará o progresso automaticamente.') let attempts = 0 const interval = setInterval(async () => { attempts++ try { const vRes = await fetch('/version.json?t=' + Date.now()) if (vRes.ok) { const vData = await vRes.json() if (!versionData || vData.buildCount !== versionData.buildCount) { setVersionData(vData) const dRes = await fetch('/deploys.json?t=' + Date.now()) if (dRes.ok) { setDeploysHistory(await dRes.json()) } setTriggeringDeploy(false) clearInterval(interval) alert(`Sucesso! Deploy finalizado. Versão atualizada para ${vData.version}.`) return } } } catch (e) {} if (attempts >= 36) { // 3 minutos clearInterval(interval) setTriggeringDeploy(false) alert('O tempo limite de monitoramento esgotou, mas o deploy pode ainda estar rodando. Por favor, recarregue a página em instantes.') } }, 5000) } catch (err) { alert('Erro ao tentar acionar o deploy automático.') setTriggeringDeploy(false) } } useEffect(() => { if (user?.role === 'admin') { fetchDeployData() } }, [user, fetchDeployData]) useEffect(() => { if (!loading && user?.role !== 'admin') router.replace('/inbox') }, [loading, user, router]) useEffect(() => { if (user?.role !== 'admin') return api.get('/api/admin/settings').then(r => setSettings(r.data)).catch(() => {}) api.get('/api/admin/plans').then(r => setPlans(r.data ?? [])).catch(() => {}) api.get('/api/admin/engine').then(r => setEngine(r.data.engine)).catch(() => {}) }, [user]) const handleEngineSwitch = async (newEngine: 'infinite' | 'official') => { if (newEngine === engine) return const confirmSwitch = window.confirm( `Deseja realmente alternar a Engine para "${newEngine === 'infinite' ? 'InfiniteAPI' : 'Baileys Oficial'}"?\n\nEsta alteração irá reiniciar o backend (~10s) para aplicar as novas configurações.` ) if (!confirmSwitch) return setSwitchingEngine(true) try { await api.post('/api/admin/engine', { engine: newEngine }) setEngine(newEngine) alert('Engine configurada com sucesso! O servidor está reiniciando. Por favor, aguarde cerca de 10 segundos antes de recarregar a página.') } catch { alert('Erro ao tentar alternar a engine.') } finally { setSwitchingEngine(false) } } const set = useCallback((key: K, value: SystemSettings[K]) => { setSettings(prev => prev ? { ...prev, [key]: value } : prev) }, []) const setNested = useCallback((key: K, patch: Partial) => { setSettings(prev => prev ? { ...prev, [key]: { ...(prev[key] as object), ...patch } } : prev) }, []) const save = async () => { if (!settings) return setSaving(true) try { await api.patch('/api/admin/settings', settings) setSaved(true) setTimeout(() => setSaved(false), 3000) } catch { } finally { setSaving(false) } } const uploadAsset = async (type: 'logo' | 'favicon', file: File) => { const form = new FormData() form.append('file', file) const { data } = await api.post(`/api/admin/settings/${type}`, form, { headers: { 'Content-Type': 'multipart/form-data' } }) setSettings(prev => prev ? { ...prev, [`${type}Url`]: data.url } : prev) } const sendTestEmail = async () => { setSendingTest(true) try { await api.post('/api/admin/settings/smtp-test') alert('E-mail de teste enviado!') } catch { alert('Falha ao enviar e-mail de teste. Verifique as configurações SMTP.') } finally { setSendingTest(false) } } if (loading || user?.role !== 'admin' || !settings) return (
) return (
{/* Tabs */}
{TABS.map(t => { const Icon = t.icon const active = tab === t.id return ( ) })}
{/* ── BRANDING ── */} {tab === 'branding' && (
Identidade Visual set('systemName', v)} placeholder="NewWhats" />
set('accentColor', e.target.value)} className="w-12 h-10 rounded-xl border border-white/10 bg-transparent cursor-pointer" /> set('accentColor', v)} placeholder="#3b82f6" />
uploadAsset('logo', f)} /> uploadAsset('favicon', f)} accept=".ico,.png,.svg" />
)} {/* ── REGIONAL ── */} {tab === 'regional' && (
Configurações Regionais
Hora atual no fuso selecionado:{' '} {new Date().toLocaleString('pt-BR', { timeZone: settings.timezone })}
)} {/* ── SONS ── */} {tab === 'sounds' && (
Sons do Sistema setNested('sounds', { newMessage: v })} label="Nova Mensagem" hint="Som ao receber uma nova mensagem no inbox" /> setNested('sounds', { notification: v })} label="Notificações" hint="Som para alertas e notificações do sistema" /> setNested('sounds', { connectionStatus: v })} label="Status de Conexão" hint="Som ao conectar ou desconectar uma instância WhatsApp" />
)} {/* ── EMAIL ── */} {tab === 'email' && (
Servidor SMTP
setNested('smtp', { host: v })} placeholder="smtp.gmail.com" /> setNested('smtp', { port: Number(v) || 587 })} placeholder="587" /> setNested('smtp', { user: v })} placeholder="noreply@empresa.com" />
setNested('smtp', { password: v })} placeholder="••••••••" />
setNested('smtp', { from: v })} placeholder="NewWhats " />
setNested('smtp', { secure: v })} label="TLS/SSL" hint="Porta 465" />
Modelos de E-mail
{[ { key: 'welcome' as const, label: 'Boas-vindas', hint: 'Enviado ao novo usuário após cadastro' }, { key: 'passwordReset' as const, label: 'Redefinição de Senha', hint: 'Link de reset enviado ao usuário' }, { key: 'trialExpiring' as const, label: 'Trial Expirando', hint: 'Aviso enviado 3 dias antes do fim do trial' }, ].map(({ key, label, hint }) => (