Biz Admin Leads Dashboard — CRM-style /admin para Supabase
Scaffold da página /admin estilo 1CRM em projeto Next.js (App Router). 3 abas — Visão Geral (KPI + gráficos), Clientes (tabela + filtros), Email Marketing (criação + envio em massa + histórico).
Filosofia: Mais simples que o 1CRM (1 senha compartilhada, sem multi-usuário), mas com gráficos completos, delta e email marketing para proprietários de empresas com 1-2 usuários. Todo o estado salvo no Supabase, arquivos scaffold independentes do projeto existente.
/admin → popup "Digite a senha de administrador" → abas chamam /api/admin/{leads|dashboard|campaigns}
→ cada requisição envia o cabeçalho x-admin-pass
→ servidor faz comparação timing-safe com a variável ADMIN_PASSWORD
Senha salva no estado React — recarregar a página = popup aparece novamente (intencional, simples, sem necessidade de cookie de sessão).
Apenas Supabase — a variante KV foi removida. Motivo: campanhas + envio de campanhas precisam de SQL join + service_role bypass RLS; KV não suporta nativamente.
Quando usar esta skill
- Projeto já tem pagamento Sepay + armazenamento de leads via
lib/leads-supabase.ts(dobiz-setup-sepay-paymentescolhendo Supabase) → precisa de camada de visualização + dashboard + email marketing. - Usuário deseja uma página no estilo CRM: cartões KPI com delta, gráfico de linhas por dia, gráfico de rosca de conversão, enviar e-mail promocional para clientes cadastrados.
NÃO usar quando:
- Projeto ainda não tem
lib/leads-supabase.ts→ instrua o usuário a executar/biz-setup-sepay-payment(escolhendo Supabase) primeiro. - Projeto usa Vercel KV em vez de Supabase → esta skill NÃO suporta mais KV. O usuário tem apenas 2 opções: migrar para Supabase ou fazer port manual.
- Precisa de multi-administrador com funções/permissões → skill usa 1 senha compartilhada, não adequada.
- Precisa de rastreamento detalhado de e-mail (abertura/clique por destinatário) → skill só rastreia enviado/com falha. Usuário precisa de Resend/SendGrid/Brevo em massa com rastreamento.
Fluxo de trabalho (6 fases)
Fase 0: DETECTAR Next.js App Router + verificar lib/leads-supabase.ts + verificar variáveis SMTP
Fase 1: PERGUNTAR senha do usuário (padrão 123456) — BLOQUEANTE
Fase 2: INSTALAR dependência npm `recharts`
Fase 3: EXECUTAR supabase-migration-admin.sql no SQL Editor do Supabase — BLOQUEANTE
Fase 4: ESCREVER ADMIN_PASSWORD no .env.local
Fase 5: CRIAR SCAFFOLD de 9 arquivos (5 lib + 4 route + 1 page)
Fase 6: TESTAR em 3 níveis (curl dashboard, UI smoke, enviar campanha de teste)
Fase 0 — Detect + verificar dependências
# Verificar Next.js + App Router
test -f next.config.js -o -f next.config.mjs -o -f next.config.ts && echo "Next.js: ✓"
test -d app && echo "App Router: ✓" || { echo "✗ Precisa do App Router. Esta skill não suporta Pages Router."; exit 1; }
# Verificar armazenamento de leads no Supabase
test -f lib/leads-supabase.ts && echo "Supabase store: ✓" || { echo "✗ Faltando lib/leads-supabase.ts — execute /biz-setup-sepay-payment escolhendo Supabase primeiro"; exit 1; }
test -f lib/leads-store.ts && echo "Wrapper agnóstico de provider: ✓"
test -f lib/supabase-admin.ts && echo "Cliente service-role: ✓"
# Verificar variáveis SMTP (do biz-email-setup)
grep -E "^SMTP_HOST=" .env.local && echo "SMTP_HOST: ✓" || { echo "✗ Faltando variáveis SMTP — execute /biz-email-setup primeiro"; exit 1; }
grep -E "^SMTP_USER=" .env.local
grep -E "^SMTP_PASS=" .env.local
grep -E "^MAIL_FROM=" .env.local
# Verificar pacotes
grep '"nodemailer"' package.json && echo "nodemailer: ✓"
grep '"@supabase/supabase-js"' package.json && echo "@supabase: ✓"
Se faltar lib/leads-supabase.ts:
⚠ Eu vejo que o projeto ainda não tem o armazenamento de leads no Supabase. Por favor, execute
/biz-setup-sepay-paymentprimeiro (escolhendo backend Supabase) para configurar pagamento + tabelaleads+ RLS. Depois volte que eu crio o painel administrativo.
Se faltar variáveis SMTP:
⚠ Eu vejo que o SMTP ainda não está configurado. A aba Email Marketing precisa do SMTP para enviar. Por favor, execute
/biz-email-setupprimeiro para escolher um provedor (Gmail/Resend SMTP/Brevo/SendGrid/Zoho...) e depois volte que eu crio o admin. Posso continuar pulando a aba de email — você confirma?
Resumo para o usuário:
Eu detectei: Next.js App Router ✓, Supabase store ✓, SMTP configurado ✓. Eu vou:
- Perguntar a senha para o /admin
- Instalar
recharts(para gráfico de linhas + rosca)- Executar migração SQL criando as tabelas
campaigns+campaign_sendsno Supabase- Gravar
ADMIN_PASSWORDno .env.local- Criar scaffold de 9 arquivos (5 lib + 4 route + 1 page)
- Orientar testes
Pronto para ir para a Fase 1?
Fase 1 — Perguntar senha (BLOQUEANTE)
Pergunte exatamente:
Qual senha você gostaria de usar para a página /admin?
- Padrão sugerido:
123456- Ou cole uma senha mais forte para produção (≥12 caracteres, com letras + números + caracteres especiais)
Cole a senha (ou digite "123456" para usar o padrão):
Aguardar resposta do usuário. Pegar a senha que o usuário colar. Se vazio → usar 123456.
NÃO vá para a Fase 2 até o usuário confirmar a senha.
Fase 2 — Instalar recharts
# Detectar gerenciador de pacotes
test -f pnpm-lock.yaml && PM=pnpm
test -f yarn.lock && PM=yarn
test -z "$PM" && PM=npm
$PM add recharts # ou: pnpm add recharts / yarn add recharts
nodemailer já foi instalado pelo /biz-email-setup. Não reinstalar.
Fase 3 — Executar migração SQL (BLOQUEANTE)
Instrua o usuário:
Abra o Supabase Dashboard → SQL Editor → New query → cole todo o conteúdo do arquivo abaixo → clique em Run:
[cole o conteúdo de templates/supabase-migration-admin.sql aqui]Este SQL cria 2 novas tabelas:
campaigns(cabeçalho da campanha) +campaign_sends(log por destinatário). NÃO mexe na tabelaleadsjá existente.Após executar, verifique no final do SQL — deve ver
campaigns: 0ecampaign_sends: 0.
Aguardar o usuário confirmar "SQL executado" e então ir para a Fase 4.
Fase 4 — Escrever .env.local
Adicione ao .env.local:
# Senha da página /admin
ADMIN_PASSWORD=<senha-coletada-na-fase-1>
Se ADMIN_PASSWORD já existir → perguntar ao usuário se deseja sobrescrever (padrão NÃO).
Garantir que .env.local esteja no .gitignore:
grep -qxF ".env.local" .gitignore 2>/dev/null || echo ".env.local" >> .gitignore
Fase 5 — Criar scaffold de 9 arquivos
lib/admin-auth.ts ← templates/lib-admin-auth.ts
lib/admin-stats.ts ← templates/lib-admin-stats.ts
lib/campaigns.ts ← templates/lib-campaigns.ts
lib/email-render.ts ← templates/lib-email-render.ts
lib/mailer.ts ← templates/lib-mailer.ts
app/admin/page.tsx ← templates/app-admin-page.tsx
app/api/admin/leads/route.ts ← templates/app-api-admin-leads-route.ts
app/api/admin/dashboard/route.ts ← templates/app-api-admin-dashboard-route.ts
app/api/admin/campaigns/route.ts ← templates/app-api-admin-campaigns-route.ts
Módulo de renderização de e-mail: lib/email-render.ts é um módulo PURO (não importa nodemailer) — contém textToHtml / wrapEmailHtml / personalize / renderCampaignEmail. lib/mailer.ts re-exporta estas funções + adiciona transporte SMTP. Separado assim para que app/admin/page.tsx (componente client) possa importar o helper de renderização para o botão Visualizar sem puxar nodemailer para o bundle do cliente.
Nota se o projeto já tem lib/mailer.ts do biz-email-setup: NÃO sobrescrever — renomear o mailer desta skill para lib/admin-mailer.ts e alterar a importação em app/api/admin/campaigns/route.ts para @/lib/admin-mailer. lib/email-render.ts é sempre seguro (nome único, não conflita).
Nota sobre alias de importação: todos os templates usam @/lib/.... Verificar se o projeto tem paths "@/*": ["./*"] no tsconfig.json (padrão Next.js). Se não tiver, alterar para imports relativos.
Nota sobre lib/leads-store.ts: A API route de leads importa listLeads de @/lib/leads-store (wrapper agnóstico de provider do biz-setup-sepay-payment). Se o usuário configurou KV antes e renomeou → verificar se o wrapper aponta para Supabase.
Diagrama de fluxo de dados
Navegador (admin/page.tsx)
│ fetch (cabeçalho x-admin-pass)
├──► GET /api/admin/dashboard?days=30 → lib/admin-stats.ts → tabela leads → { kpi, timeseries, deltas }
├──► GET /api/admin/leads?status=... → lib/leads-store.ts → tabela leads → { leads, stats }
├──► GET /api/admin/campaigns → lib/campaigns.ts → tabela campaigns → { campaigns: [...] }
└──► POST /api/admin/campaigns (corpo com bodyFormat: 'text' | 'html')
└─► lib/campaigns.queryAudience(leads.where status/days)
└─► lib/email-render.renderCampaignEmail(format, body) → { html, text } (renderiza 1 vez)
└─► lib/campaigns.createCampaign (linha campaigns)
└─► loop (throttle 200ms):
lib/mailer.personalize(html/text por destinatário)
lib/mailer.sendOne(para, assunto, html)
lib/campaigns.logCampaignSend (linha campaign_sends)
└─► lib/campaigns.setCampaignStatus (contagens finais + sentAt)
return { successCount, failCount }
Visualização no cliente (NÃO chama servidor): admin/page.tsx importa lib/email-render diretamente,
botão "Visualizar" renderiza { html } + personaliza(modelo) → exibe em <iframe srcDoc sandbox>.
Fase 6 — Testar em 3 níveis
Teste 1 — Curl no endpoint dashboard
PASS="<senha-da-fase-1>"
# Senha errada → 401
curl -i http://localhost:3000/api/admin/dashboard -H "x-admin-pass: wrong"
# Esperado: 401, {"error":"invalid_password"}
# Senha correta → 200 com KPI + timeseries
curl -s http://localhost:3000/api/admin/dashboard?days=7 -H "x-admin-pass: $PASS" | jq '.period, .deltas'
# Esperado: period {days, leads, paid, revenue, conversionRate}, deltas {leadsPct, ...}
Teste 2 — UI smoke no navegador
1. pnpm dev (ou yarn/npm)
2. Acessar http://localhost:3000/admin
3. Ver popup "Página de administração" → digitar senha errada → "Senha incorreta, tente novamente"
4. Digitar senha correta → acessar dashboard
5. Aba Visão Geral: ver 4 cartões KPI, gráfico de linhas, gráfico de rosca, seletor de período 7d/30d/90d funcionando
6. Aba Clientes: ver tabela + filtros + exportar CSV
7. Aba Email Marketing: ver formulário de criação + histórico (inicialmente vazio)
8. Aba Email Marketing: digitar conteúdo → clicar "👁 Visualizar" → modal exibe e-mail
renderizado (alternar entre Texto simples ↔ HTML para confirmar ambos os modos)
Teste 3 — Enviar campanha de teste para o e-mail do proprietário
IMPORTANTE: Escolher público
last_days= 7 com a senha já configurada, corpo com{{name}}. Enviar teste para um único lead no banco de dados (lead de teste do proprietário) para confirmar o fluxo antes de enviar em massa real.
1. Acessar aba Email Marketing
2. Nome da campanha: "Teste de envio"
3. Assunto: "E-mail de teste do /admin — você recebeu?"
4. Público: "Por data mais recente" → 7 dias
5. Conteúdo:
Olá {{name}},
Este é um e-mail de teste da página admin para confirmar que o SMTP está funcionando.
Se você recebeu, começarei a enviar as campanhas reais.
6. Clicar "👁 Visualizar" → confirmar que o e-mail é exibido corretamente → fechar modal
7. Clicar "Enviar agora" → confirmar
8. Aguardar 10-30s (200ms × número de destinatários)
9. Verificar:
- Banner verde "Enviado X/X e-mails (0 erros)"
- Tabela de histórico com nova linha status "Enviado"
- Proprietário verificar caixa de entrada → ver e-mail com nome substituindo {{name}}
Saída final
✓ Scaffold da página /admin CRM-style com Supabase + recharts + email marketing
📁 Novos arquivos (9):
lib/
admin-auth.ts (verificação de senha timing-safe)
admin-stats.ts (KPI + série temporal + deltas)
campaigns.ts (CRUD + consulta de público)
email-render.ts (puro: textToHtml + wrapEmailHtml + renderCampaignEmail — usado tanto no servidor quanto na visualização do cliente)
mailer.ts (transportador nodemailer + re-export email-render + rodapé de cancelamento)
app/admin/page.tsx (UI 1CRM com 3 abas — Visão Geral / Clientes / Email Marketing — corpo texto/HTML + Visualizar)
app/api/admin/
leads/route.ts (GET listar + filtrar)
dashboard/route.ts (GET KPI + série temporal)
campaigns/route.ts (GET histórico + POST enviar em massa)
🗄 Migração Supabase:
- Tabelas campaigns + campaign_sends (RLS negar tudo, bypass service_role)
- Você já executou templates/supabase-migration-admin.sql ✓
📦 Dependência npm:
- recharts (receém instalado)
- nodemailer (já existente do biz-email-setup)
🔐 Variáveis de ambiente:
- ADMIN_PASSWORD gravado no .env.local
- .gitignore inclui .env.local
- SMTP_* + MAIL_FROM reutilizados do biz-email-setup
🧪 Testes:
1. ✓ Curl no endpoint dashboard (Teste 1)
2. ✓ UI smoke no navegador (Teste 2)
3. ✓ Enviado campanha de teste para o e-mail do proprietário (Teste 3)
🔧 Tarefas pendentes para você:
1. (Se for fazer deploy em produção) Adicionar ADMIN_PASSWORD nas variáveis de ambiente do Vercel:
vercel env add ADMIN_PRODUCTION production
2. Re-deploy: vercel --prod
3. Acessar https://<dominio>/admin → digitar senha → confirmar 3 abas funcionando
4. Primeiro envio de campanha real: escolher público pequeno (last_days 7) para verificar
limite de taxa SMTP do provedor antes de escalar para 'all'
Templates
| Arquivo | Descrição |
|---|---|
templates/supabase-migration-admin.sql | Migração criando campaigns + campaign_sends |
templates/lib-admin-auth.ts | checkAdminPass() comparação timing-safe |
templates/lib-admin-stats.ts | getDashboardData(dias) — KPI + série temporal + deltas |
templates/lib-campaigns.ts | CRUD de campanhas + queryAudience() |
templates/lib-email-render.ts | Módulo puro — textToHtml() + wrapEmailHtml() + personalize() + renderCampaignEmail() (texto/HTML). Não importa nodemailer → componente cliente pode usar para visualização |
templates/lib-mailer.ts | Transportador nodemailer + sendOne() + sleep() + re-exporta helpers de renderização de email-render |
templates/app-admin-page.tsx | Página estilo 1CRM com sidebar + 3 abas + recharts. Aba de e-mail com alternância de formato texto/HTML + botão Visualizar (modal iframe) |
templates/app-api-admin-leads-route.ts | API da tabela de leads (chama listLeads()) |
templates/app-api-admin-dashboard-route.ts | API de análise do dashboard |
templates/app-api-admin-campaigns-route.ts | GET listar + POST criar & enviar |
Anti-padrão (não fazer)
- ❌ Comparar senha com
===→ ataque de timing. O template usacrypto.timingSafeEqualvialib/admin-auth.ts. - ❌ Codificar senha no código fonte → esquecer de alterar → deploy em produção com senha de teste. Sempre usar
process.env.ADMIN_PASSWORD. - ❌ Enviar senha pela query string da URL (
?pass=123456) → vaza para o histórico do navegador + logs de acesso. Usar o cabeçalhox-admin-pass. - ❌ Criar página de login separada + cookie de sessão + JWT quando o usuário só precisa de um admin simples. Popup inline é suficiente.
- ❌ Salvar senha no localStorage → JS consegue ler, vazamento por XSS. O template salva no estado React (perde ao recarregar, intencional).
- ❌ Pular verificação de dependências → erro no meio do processo. Executar Fase 0 sempre.
- ❌ Enviar campanha real sem testar → pode queimar o limite de taxa SMTP. Sempre fazer Teste 3 primeiro.
- ❌ Manter configuração KV — skill não suporta mais. Usuário deve migrar para Supabase.
- ❌ "Vou adaptar manualmente" do template
mailer.tspara usar outro provedor de e-mail sem testar → quebrar o flow de visualização no cliente. O móduloemail-render.tsé separado justamente para isolar a lógica de renderização. Se o usuário quiser outro provider (Resend API em vez de SMTP), deve alterar apenaslib/mailer.ts, mantendolib/email-render.tsintacto.