style(newwhats): /wa-sessions com sidebar do scoreodonto + tema branco

- wa-sessions sai do isStandaloneView → renderiza com a sidebar (área de
  administração); wa-inbox/wa-secretaria seguem em tela cheia.
- SessionsView convertida do dark para o tema branco/profissional (bg-gray-50/
  white, text-gray-*, badges claros, botão Conectar em brand); backdrops de
  modal e botões coloridos preservados.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
VPS 4 Builder
2026-07-02 03:55:16 +02:00
parent 821ae591d4
commit 3e1001b7c7
2 changed files with 58 additions and 56 deletions
+3 -1
View File
@@ -563,7 +563,9 @@ const App: React.FC = () => {
// Views do plugin NewWhats rodam em tela cheia (sem sidebar/barra do
// scoreodonto) — cada uma tem seu próprio "Voltar" (onNavigate) para sair.
const isStandaloneView = ['landing', 'login', 'public', 'update', 'cadastro-dentista', 'criar-clinica', 'aguardando-convite', 'wa-inbox', 'wa-sessions', 'wa-secretaria'].includes(currentView);
// /wa-sessions (gestão de sessões WhatsApp) roda COM a sidebar do scoreodonto
// (é área de administração). wa-inbox/wa-secretaria seguem em tela cheia.
const isStandaloneView = ['landing', 'login', 'public', 'update', 'cadastro-dentista', 'criar-clinica', 'aguardando-convite', 'wa-inbox', 'wa-secretaria'].includes(currentView);
return (
<>
+55 -55
View File
@@ -37,8 +37,8 @@ const STATUS_CONFIG = {
},
DISCONNECTED: {
label: 'Desconectado',
dot: 'bg-slate-500',
badge: 'bg-slate-500/10 text-slate-400 border-slate-500/20',
dot: 'bg-gray-400',
badge: 'bg-gray-100 text-gray-600 border-gray-300',
icon: <WifiOff className="w-4 h-4" />,
},
BANNED: {
@@ -72,7 +72,7 @@ const EVENT_COLOR: Record<string, string> = {
};
function eventColor(event: string): string {
return EVENT_COLOR[event] ?? 'text-slate-400 bg-slate-500/15';
return EVENT_COLOR[event] ?? 'text-gray-600 bg-gray-100';
}
function logSummary(entry: BaileysLogEntry): string {
@@ -107,15 +107,15 @@ function LogRow({ entry, index }: { entry: BaileysLogEntry; index: number }) {
<div className="border-b border-white/[0.04] last:border-0">
<button
onClick={() => setOpen(o => !o)}
className="w-full flex items-center gap-2 px-3 py-1.5 hover:bg-white/[0.03] text-left group transition-colors"
className="w-full flex items-center gap-2 px-3 py-1.5 hover:bg-gray-100 text-left group transition-colors"
>
<span className="text-[9px] font-mono text-slate-700 w-5 shrink-0 text-right">{index + 1}</span>
<span className="text-[9px] font-mono text-gray-400 w-5 shrink-0 text-right">{index + 1}</span>
<span className={`text-[9px] font-black uppercase tracking-wider px-1.5 py-0.5 rounded-full shrink-0 whitespace-nowrap ${eventColor(entry.event)}`}>
{entry.event}
</span>
<span className="text-[11px] text-slate-400 truncate flex-1 font-mono">{logSummary(entry)}</span>
<span className="text-[9px] text-slate-700 shrink-0 font-mono">{time}</span>
<span className="shrink-0 text-slate-700 group-hover:text-slate-500">
<span className="text-[11px] text-gray-600 truncate flex-1 font-mono">{logSummary(entry)}</span>
<span className="text-[9px] text-gray-400 shrink-0 font-mono">{time}</span>
<span className="shrink-0 text-gray-400 group-hover:text-gray-500">
{open ? <ChevronDown size={11} /> : <ChevronRight size={11} />}
</span>
</button>
@@ -129,7 +129,7 @@ function LogRow({ entry, index }: { entry: BaileysLogEntry; index: number }) {
transition={{ duration: 0.15 }}
className="overflow-hidden"
>
<pre className="mx-3 mb-2 p-2 bg-black/60 rounded-lg text-[10px] font-mono text-slate-300 overflow-x-auto whitespace-pre-wrap max-h-48 border border-white/5">
<pre className="mx-3 mb-2 p-2 bg-gray-100 rounded-lg text-[10px] font-mono text-gray-700 overflow-x-auto whitespace-pre-wrap max-h-48 border border-gray-100">
{JSON.stringify(entry.data, null, 2)}
</pre>
</motion.div>
@@ -207,26 +207,26 @@ function QrModal({
initial={{ opacity: 0, scale: 0.9, y: 20 }}
animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.9, y: 20 }}
className="relative bg-[#0d0f18] border border-white/10 rounded-[2rem] shadow-2xl w-full max-w-3xl max-h-[90vh] flex flex-col overflow-hidden"
className="relative bg-white border border-gray-200 rounded-[2rem] shadow-2xl w-full max-w-3xl max-h-[90vh] flex flex-col overflow-hidden"
onClick={e => e.stopPropagation()}
>
<div className="flex items-center justify-between px-6 py-4 border-b border-white/[0.06] shrink-0">
<div className="flex items-center justify-between px-6 py-4 border-b border-gray-100 shrink-0">
<div>
<h2 className="text-lg font-black text-white">Conectar WhatsApp</h2>
<p className="text-slate-500 text-xs mt-0.5">{instanceName}</p>
<h2 className="text-lg font-black text-gray-800">Conectar WhatsApp</h2>
<p className="text-gray-500 text-xs mt-0.5">{instanceName}</p>
</div>
<button onClick={onClose} className="p-2 text-slate-500 hover:text-white bg-white/5 rounded-xl transition-colors">
<button onClick={onClose} className="p-2 text-gray-500 hover:text-gray-800 bg-gray-50 rounded-xl transition-colors">
<X className="w-4 h-4" />
</button>
</div>
<div className="flex flex-1 min-h-0">
<div className="w-72 shrink-0 flex flex-col items-center justify-center gap-5 p-6 border-r border-white/[0.06]">
<div className="bg-white/[0.02] border border-white/5 p-5 rounded-3xl w-full flex flex-col items-center justify-center min-h-[260px]">
<div className="w-72 shrink-0 flex flex-col items-center justify-center gap-5 p-6 border-r border-gray-100">
<div className="bg-gray-50 border border-gray-100 p-5 rounded-3xl w-full flex flex-col items-center justify-center min-h-[260px]">
{expired ? (
<div className="flex flex-col items-center gap-4">
<Clock className="w-10 h-10 text-slate-500" />
<p className="font-bold text-white text-sm">QR expirado</p>
<Clock className="w-10 h-10 text-gray-500" />
<p className="font-bold text-gray-800 text-sm">QR expirado</p>
<Button variant="primary" size="sm" onClick={async () => {
setExpired(false);
await onNewQr(instanceId);
@@ -241,7 +241,7 @@ function QrModal({
) : (
<div className="flex flex-col items-center gap-3">
<Loader2 className="w-10 h-10 animate-spin text-emerald-500" />
<p className="text-xs text-slate-500 font-semibold uppercase tracking-widest">Gerando QR...</p>
<p className="text-xs text-gray-500 font-semibold uppercase tracking-widest">Gerando QR...</p>
</div>
)}
</div>
@@ -252,22 +252,22 @@ function QrModal({
</div>
)}
<button onClick={onClose} className="w-full py-2.5 text-slate-500 hover:text-white font-bold transition-colors text-sm">
<button onClick={onClose} className="w-full py-2.5 text-gray-500 hover:text-gray-800 font-bold transition-colors text-sm">
Fechar
</button>
</div>
<div className="flex-1 flex flex-col min-w-0">
<div className="flex items-center gap-2 px-4 py-2.5 border-b border-white/[0.06] bg-black/20 shrink-0">
<Terminal className="w-3.5 h-3.5 text-slate-500" />
<span className="text-[10px] font-black uppercase tracking-widest text-slate-500">Console Baileys</span>
<span className="ml-1 text-[9px] font-mono text-slate-700 bg-white/5 px-1.5 py-0.5 rounded-full">
<div className="flex items-center gap-2 px-4 py-2.5 border-b border-gray-100 bg-gray-50 shrink-0">
<Terminal className="w-3.5 h-3.5 text-gray-500" />
<span className="text-[10px] font-black uppercase tracking-widest text-gray-500">Console Baileys</span>
<span className="ml-1 text-[9px] font-mono text-gray-400 bg-gray-50 px-1.5 py-0.5 rounded-full">
{logs.length} eventos
</span>
<div className="ml-auto flex items-center gap-2">
<button
onClick={() => setLogs([])}
className="text-[9px] font-bold uppercase tracking-wider text-slate-600 hover:text-slate-400 transition-colors px-2 py-0.5 rounded hover:bg-white/5"
className="text-[9px] font-bold uppercase tracking-wider text-gray-500 hover:text-gray-600 transition-colors px-2 py-0.5 rounded hover:bg-gray-50"
>
Limpar
</button>
@@ -283,7 +283,7 @@ function QrModal({
<div className="flex-1 overflow-y-auto">
{logs.length === 0 ? (
<div className="flex flex-col items-center justify-center h-full text-slate-700 gap-2 py-16">
<div className="flex flex-col items-center justify-center h-full text-gray-400 gap-2 py-16">
<Terminal size={28} className="opacity-30" />
<p className="text-xs">Aguardando eventos do Baileys...</p>
</div>
@@ -297,10 +297,10 @@ function QrModal({
)}
</div>
<div className="px-4 py-2 border-t border-white/[0.06] bg-black/20 shrink-0">
<div className="px-4 py-2 border-t border-gray-100 bg-gray-50 shrink-0">
<div className="flex items-center gap-1.5">
<span className="w-1.5 h-1.5 rounded-full bg-emerald-500 animate-pulse" />
<span className="text-[9px] font-mono text-slate-600">
<span className="text-[9px] font-mono text-gray-500">
{instanceId.slice(0, 8)}... · live
</span>
</div>
@@ -335,13 +335,13 @@ const SessionCard = React.forwardRef<HTMLDivElement, {
<motion.div
ref={ref}
initial={{ opacity: 0, y: 15 }} animate={{ opacity: 1, y: 0 }}
className="bg-white/[0.03] backdrop-blur-xl p-6 rounded-[2rem] shadow-2xl border border-white/5 flex flex-col gap-5 hover:border-white/10 transition-all group relative overflow-hidden"
className="bg-gray-100 backdrop-blur-xl p-6 rounded-[2rem] shadow-2xl border border-gray-100 flex flex-col gap-5 hover:border-gray-200 transition-all group relative overflow-hidden"
>
<div className="absolute top-0 right-0 w-32 h-32 bg-brand-500/5 blur-[60px] rounded-full translate-x-1/2 -translate-y-1/2 pointer-events-none" />
<div className="flex items-start justify-between relative z-10">
<div className="flex items-center gap-4">
<div className={`w-14 h-14 rounded-2xl overflow-hidden flex items-center justify-center border-2 shadow-lg ${isConnected ? 'bg-emerald-500/10 border-emerald-500/30 text-emerald-400' : 'bg-white/5 border-white/10 text-slate-500'}`}>
<div className={`w-14 h-14 rounded-2xl overflow-hidden flex items-center justify-center border-2 shadow-lg ${isConnected ? 'bg-emerald-500/10 border-emerald-500/30 text-emerald-400' : 'bg-gray-50 border-gray-200 text-gray-500'}`}>
{inst.avatar
? <img src={inst.avatar} alt={inst.name} className="w-full h-full object-cover" onError={(e) => { (e.currentTarget as HTMLImageElement).style.display = 'none'; (e.currentTarget.nextSibling as HTMLElement)?.style.setProperty('display', 'flex') }} />
: null}
@@ -350,10 +350,10 @@ const SessionCard = React.forwardRef<HTMLDivElement, {
</span>
</div>
<div>
<h3 className="font-black text-white text-lg tracking-tight">{inst.name}</h3>
<h3 className="font-black text-gray-800 text-lg tracking-tight">{inst.name}</h3>
<div className="flex items-center gap-2 mt-1 flex-wrap">
{inst.phone && (
<span className="text-xs font-mono text-slate-500 bg-white/5 px-2 py-0.5 rounded border border-white/5">{inst.phone}</span>
<span className="text-xs font-mono text-gray-500 bg-gray-50 px-2 py-0.5 rounded border border-gray-100">{inst.phone}</span>
)}
<span className={`inline-flex items-center gap-1.5 text-[10px] font-black uppercase tracking-wider px-2 py-0.5 rounded-full border ${cfg.badge}`}>
<span className={`w-1.5 h-1.5 rounded-full ${cfg.dot}`} />
@@ -364,7 +364,7 @@ const SessionCard = React.forwardRef<HTMLDivElement, {
</div>
<button onClick={onDelete} title="Deletar instância"
className="p-2 text-slate-600 hover:text-red-400 hover:bg-red-500/10 rounded-xl transition-colors shrink-0"
className="p-2 text-gray-500 hover:text-red-400 hover:bg-red-500/10 rounded-xl transition-colors shrink-0"
>
<Trash2 className="w-4 h-4" />
</button>
@@ -379,21 +379,21 @@ const SessionCard = React.forwardRef<HTMLDivElement, {
</button>
)}
<div className="flex items-center gap-2 mt-auto relative z-10 pt-2 border-t border-white/5">
<div className="flex items-center gap-2 mt-auto relative z-10 pt-2 border-t border-gray-100">
{isConnected ? (
<>
<div className="flex items-center gap-2 text-emerald-400 text-xs font-bold uppercase tracking-widest flex-1">
<RefreshCw className="w-3.5 h-3.5 animate-spin" /> Sessão ativa
</div>
<button onClick={onDisconnect}
className="flex items-center gap-1.5 px-3 py-2 text-xs font-semibold text-slate-400 hover:text-white bg-white/5 hover:bg-white/10 rounded-xl transition-colors border border-white/5"
className="flex items-center gap-1.5 px-3 py-2 text-xs font-semibold text-gray-600 hover:text-gray-800 bg-gray-50 hover:bg-gray-100 rounded-xl transition-colors border border-gray-100"
>
<LogOut className="w-3.5 h-3.5" /> Desconectar
</button>
</>
) : (
<button onClick={onConnect}
className="flex-1 flex items-center justify-center gap-2 bg-white text-black font-black py-3 rounded-xl hover:bg-slate-200 transition-all text-[11px] uppercase tracking-widest"
className="flex-1 flex items-center justify-center gap-2 bg-brand-500 text-white font-black py-3 rounded-xl hover:bg-brand-600 transition-all text-[11px] uppercase tracking-widest"
>
<Zap className="w-3.5 h-3.5" fill="currentColor" /> Conectar
</button>
@@ -421,18 +421,18 @@ function ConfirmDeleteModal({
<motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }}
onClick={onClose} className="absolute inset-0 bg-black/80 backdrop-blur-sm cursor-pointer" />
<motion.div initial={{ opacity: 0, scale: 0.92, y: 20 }} animate={{ opacity: 1, scale: 1, y: 0 }} exit={{ opacity: 0, scale: 0.9 }}
className="relative bg-black/40 backdrop-blur-2xl border border-red-500/20 border-t-[3px] border-t-red-500 rounded-[28px] p-7 w-full max-w-sm shadow-2xl"
className="relative bg-white backdrop-blur-2xl border border-red-500/20 border-t-[3px] border-t-red-500 rounded-[28px] p-7 w-full max-w-sm shadow-2xl"
>
<div className="flex items-center gap-3 mb-4 mt-2">
<div className="w-11 h-11 rounded-xl bg-red-500/15 border border-red-500/30 flex items-center justify-center">
<Trash2 className="w-5 h-5 text-red-500" />
</div>
<div>
<h3 className="font-bold text-white">Deletar instância</h3>
<p className="text-xs text-slate-500">{inst.name}</p>
<h3 className="font-bold text-gray-800">Deletar instância</h3>
<p className="text-xs text-gray-500">{inst.name}</p>
</div>
</div>
<p className="text-sm text-slate-400 mb-6 leading-relaxed">
<p className="text-sm text-gray-600 mb-6 leading-relaxed">
Isso removerá permanentemente a instância, seus chats e sessão Baileys.<br />
<strong className="text-red-400">Esta ação é irreversível.</strong>
</p>
@@ -483,7 +483,7 @@ export function SessionsView({ onNavigate }: { onNavigate?: (view: string) => vo
const hasActiveSession = instances.some((i) => i.status === 'CONNECTED');
return (
<div className="bg-[#070b14] min-h-full text-slate-200">
<div className="bg-gray-50 min-h-full text-gray-800">
<div className="max-w-5xl mx-auto p-6 lg:p-10 pb-20 font-sans space-y-10">
<AnimatePresence>
@@ -501,7 +501,7 @@ export function SessionsView({ onNavigate }: { onNavigate?: (view: string) => vo
<header className="flex flex-col sm:flex-row sm:items-center justify-between gap-6 relative">
<div className="absolute -top-16 -left-16 w-56 h-56 bg-brand-500/10 blur-[100px] rounded-full pointer-events-none" />
<div className="relative z-10 flex items-start gap-4">
<button onClick={() => onNavigate?.('dashboard')} className="p-2 hover:bg-white/5 rounded-xl transition-colors text-slate-400 hover:text-white mt-1">
<button onClick={() => onNavigate?.('dashboard')} className="p-2 hover:bg-gray-50 rounded-xl transition-colors text-gray-600 hover:text-gray-800 mt-1">
<ArrowLeft className="w-5 h-5" />
</button>
<div className="flex-1">
@@ -509,14 +509,14 @@ export function SessionsView({ onNavigate }: { onNavigate?: (view: string) => vo
<div className="w-12 h-12 bg-gradient-to-br from-brand-500 to-brand-700 rounded-xl flex items-center justify-center text-white shadow-glow-brand shrink-0">
<Wifi className="w-6 h-6" />
</div>
<h1 className="text-3xl font-black text-white tracking-tight">Instâncias</h1>
<h1 className="text-3xl font-black text-gray-800 tracking-tight">Instâncias</h1>
</div>
<p className="text-sm text-slate-500 font-medium mt-1 ml-[60px]">WhatsApp Multi-Device · Baileys</p>
<p className="text-sm text-gray-500 font-medium mt-1 ml-[60px]">WhatsApp Multi-Device · Baileys</p>
</div>
</div>
<div className="flex items-center gap-3 relative z-10">
<button onClick={refreshInstances} title="Recarregar"
className="p-3 bg-white/5 border border-white/10 rounded-xl text-slate-400 hover:text-white transition-all hover:bg-white/10"
className="p-3 bg-gray-50 border border-gray-200 rounded-xl text-gray-600 hover:text-gray-800 transition-all hover:bg-gray-100"
>
<RefreshCw className={`w-5 h-5 ${loading ? 'animate-spin' : ''}`} />
</button>
@@ -554,21 +554,21 @@ export function SessionsView({ onNavigate }: { onNavigate?: (view: string) => vo
<AnimatePresence mode="popLayout">
{loading ? (
Array(3).fill(0).map((_, i) => (
<div key={i} className="h-56 rounded-[2rem] animate-pulse bg-white/5" />
<div key={i} className="h-56 rounded-[2rem] animate-pulse bg-gray-50" />
))
) : instances.length === 0 ? (
<motion.div key="empty" initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }}
className="col-span-full py-24 flex flex-col items-center justify-center text-center gap-6"
>
<div className="w-20 h-20 bg-slate-800 rounded-full flex items-center justify-center text-slate-600">
<div className="w-20 h-20 bg-gray-100 rounded-full flex items-center justify-center text-gray-500">
<Phone className="w-10 h-10" />
</div>
<div>
<h2 className="text-xl font-bold text-white">Nenhuma instância</h2>
<p className="text-slate-500 mt-1 text-sm">Crie uma para começar a usar o WhatsApp.</p>
<h2 className="text-xl font-bold text-gray-800">Nenhuma instância</h2>
<p className="text-gray-500 mt-1 text-sm">Crie uma para começar a usar o WhatsApp.</p>
</div>
<button onClick={openAddModal}
className="flex items-center gap-2 px-6 py-3 bg-white/5 hover:bg-white/10 text-white font-bold rounded-2xl border border-white/10 transition-all"
className="flex items-center gap-2 px-6 py-3 bg-gray-50 hover:bg-gray-100 text-gray-800 font-bold rounded-2xl border border-gray-200 transition-all"
>
<Plus className="w-5 h-5" /> Criar instância
</button>
@@ -594,14 +594,14 @@ export function SessionsView({ onNavigate }: { onNavigate?: (view: string) => vo
<motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }}
onClick={closeAddModal} className="absolute inset-0 bg-black/80 backdrop-blur-sm cursor-pointer" />
<motion.div initial={{ opacity: 0, scale: 0.9, y: 20 }} animate={{ opacity: 1, scale: 1, y: 0 }} exit={{ opacity: 0, scale: 0.9, y: 20 }}
className="relative bg-white/[0.03] backdrop-blur-2xl p-8 rounded-[3rem] border border-white/10 max-w-sm w-full space-y-6 shadow-2xl"
className="relative bg-gray-100 backdrop-blur-2xl p-8 rounded-[3rem] border border-gray-200 max-w-sm w-full space-y-6 shadow-2xl"
>
<div>
<h2 className="text-2xl font-black text-white">Nova Instância</h2>
<p className="text-slate-500 text-sm mt-1"> um nome para identificar esta conexão.</p>
<h2 className="text-2xl font-black text-gray-800">Nova Instância</h2>
<p className="text-gray-500 text-sm mt-1"> um nome para identificar esta conexão.</p>
</div>
<div className="space-y-1.5">
<label className="text-[10px] font-bold text-slate-600 uppercase tracking-widest">Nome</label>
<label className="text-[10px] font-bold text-gray-500 uppercase tracking-widest">Nome</label>
<input
type="text"
value={newName}
@@ -609,7 +609,7 @@ export function SessionsView({ onNavigate }: { onNavigate?: (view: string) => vo
onKeyDown={(e) => { if (e.key === 'Enter') handleCreate(); }}
placeholder="ex: principal, comercial, suporte..."
autoFocus
className="w-full bg-black/30 border border-white/10 rounded-2xl px-4 py-3 text-white placeholder:text-slate-600 focus:outline-none focus:ring-2 focus:ring-brand-500/40 text-sm"
className="w-full bg-gray-50 border border-gray-200 rounded-2xl px-4 py-3 text-gray-800 placeholder:text-gray-500 focus:outline-none focus:ring-2 focus:ring-brand-500/40 text-sm"
/>
</div>
<div className="flex gap-3">