feat: adiciona fluxo de redefinição de senha e melhora página de configurações

- Adiciona página /recuperar para solicitar redefinição de senha
- Adiciona página /redefinir-senha para definir nova senha com token
- Cria APIs /api/auth/forgot-password e /api/auth/reset-password
- Adiciona notificação por e-mail quando ticket é criado
- Repagina página de configurações removendo informações técnicas
- Adiciona script de teste para todos os tipos de e-mail
- Corrige acentuações em templates de e-mail

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
rever-tecnologia 2025-12-15 10:42:08 -03:00
parent 300179279a
commit 1bc08d3a5f
10 changed files with 1258 additions and 166 deletions

188
scripts/test-all-emails.tsx Normal file
View file

@ -0,0 +1,188 @@
import * as React from "react"
import dotenv from "dotenv"
import { render } from "@react-email/render"
import { sendSmtpMail } from "@/server/email-smtp"
import AutomationEmail, { type AutomationEmailProps } from "../emails/automation-email"
import SimpleNotificationEmail, { type SimpleNotificationEmailProps } from "../emails/simple-notification-email"
dotenv.config({ path: ".env.local" })
dotenv.config({ path: ".env" })
function getSmtpConfig() {
const host = process.env.SMTP_HOST
const port = process.env.SMTP_PORT
const username = process.env.SMTP_USER
const password = process.env.SMTP_PASS
const fromEmail = process.env.SMTP_FROM_EMAIL
const fromName = process.env.SMTP_FROM_NAME ?? "Raven"
if (!host || !port || !username || !password || !fromEmail) return null
return {
host,
port: Number(port),
username,
password,
from: `"${fromName}" <${fromEmail}>`,
tls: process.env.SMTP_SECURE === "true",
rejectUnauthorized: false,
timeoutMs: 15000,
}
}
type EmailScenario = {
name: string
subject: string
render: () => Promise<string>
}
const baseUrl = process.env.NEXT_PUBLIC_APP_URL || "https://tickets.esdrasrenan.com.br"
const scenarios: EmailScenario[] = [
{
name: "Ticket Criado",
subject: "[TESTE] Novo chamado #41025 aberto",
render: async () => {
const props: SimpleNotificationEmailProps = {
title: "Novo chamado #41025 aberto",
message: "Seu chamado foi registrado com sucesso. Nossa equipe irá analisá-lo em breve.\n\nAssunto: Computador reiniciando sozinho\nPrioridade: Alta\nStatus: Pendente",
ctaLabel: "Ver chamado",
ctaUrl: `${baseUrl}/portal/tickets/test123`,
}
return render(<SimpleNotificationEmail {...props} />, { pretty: true })
},
},
{
name: "Ticket Resolvido",
subject: "[TESTE] Chamado #41025 foi encerrado",
render: async () => {
const props: SimpleNotificationEmailProps = {
title: "Chamado #41025 encerrado",
message: "O chamado 'Computador reiniciando sozinho' foi marcado como concluído.\n\nCaso necessário, você pode responder pelo portal para reabrir dentro do prazo.",
ctaLabel: "Ver detalhes",
ctaUrl: `${baseUrl}/portal/tickets/test123`,
}
return render(<SimpleNotificationEmail {...props} />, { pretty: true })
},
},
{
name: "Novo Comentário",
subject: "[TESTE] Atualização no chamado #41025",
render: async () => {
const props: SimpleNotificationEmailProps = {
title: "Nova atualização no seu chamado #41025",
message: "Um novo comentário foi adicionado ao chamado 'Computador reiniciando sozinho'.\n\nClique abaixo para visualizar e responder pelo portal.",
ctaLabel: "Abrir e responder",
ctaUrl: `${baseUrl}/portal/tickets/test123`,
}
return render(<SimpleNotificationEmail {...props} />, { pretty: true })
},
},
{
name: "Automação - Mudança de Prioridade",
subject: "[TESTE] Prioridade alterada no chamado #41025",
render: async () => {
const props: AutomationEmailProps = {
title: "Prioridade alterada para Urgente",
message: "A prioridade do seu chamado foi alterada automaticamente pelo sistema.\n\nIsso pode ter ocorrido devido a regras de SLA ou categorização automática.",
ticket: {
reference: 41025,
subject: "Computador reiniciando sozinho",
companyName: "Paulicon Contabil",
status: "AWAITING_ATTENDANCE",
priority: "URGENT",
requesterName: "Renan",
assigneeName: "Administrador",
},
ctaLabel: "Ver chamado",
ctaUrl: `${baseUrl}/portal/tickets/test123`,
}
return render(<AutomationEmail {...props} />, { pretty: true })
},
},
{
name: "Automação - Atribuição de Agente",
subject: "[TESTE] Agente atribuído ao chamado #41025",
render: async () => {
const props: AutomationEmailProps = {
title: "Agente atribuído ao seu chamado",
message: "O agente Administrador foi automaticamente atribuído ao seu chamado e entrará em contato em breve.",
ticket: {
reference: 41025,
subject: "Computador reiniciando sozinho",
companyName: "Paulicon Contabil",
status: "AWAITING_ATTENDANCE",
priority: "HIGH",
requesterName: "Renan",
assigneeName: "Administrador",
},
ctaLabel: "Ver chamado",
ctaUrl: `${baseUrl}/portal/tickets/test123`,
}
return render(<AutomationEmail {...props} />, { pretty: true })
},
},
{
name: "Redefinição de Senha",
subject: "[TESTE] Redefinição de senha - Raven",
render: async () => {
const props: SimpleNotificationEmailProps = {
title: "Redefinição de Senha",
message: "Recebemos uma solicitação para redefinir a senha da sua conta.\n\nSe você não fez essa solicitação, pode ignorar este e-mail.\n\nEste link expira em 1 hora.",
ctaLabel: "Redefinir Senha",
ctaUrl: `${baseUrl}/redefinir-senha?token=abc123def456`,
}
return render(<SimpleNotificationEmail {...props} />, { pretty: true })
},
},
]
async function main() {
const targetEmail = process.argv[2] ?? "renan.pac@paulicon.com.br"
const smtp = getSmtpConfig()
if (!smtp) {
console.error("SMTP não configurado. Defina as variáveis SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASS, SMTP_FROM_EMAIL")
process.exit(1)
}
console.log("=".repeat(60))
console.log("Teste de E-mails - Sistema de Chamados Raven")
console.log("=".repeat(60))
console.log(`\nDestinatario: ${targetEmail}`)
console.log(`SMTP: ${smtp.host}:${smtp.port}`)
console.log(`De: ${smtp.from}`)
console.log(`\nEnviando ${scenarios.length} e-mails de teste...\n`)
let success = 0
let failed = 0
for (const scenario of scenarios) {
try {
process.stdout.write(` ${scenario.name}... `)
const html = await scenario.render()
await sendSmtpMail(smtp, targetEmail, scenario.subject, html)
console.log("OK")
success++
// Pequeno delay entre envios para evitar rate limit
await new Promise((resolve) => setTimeout(resolve, 500))
} catch (error) {
console.log(`ERRO: ${error instanceof Error ? error.message : error}`)
failed++
}
}
console.log("\n" + "=".repeat(60))
console.log(`Resultado: ${success} enviados, ${failed} falharam`)
console.log("=".repeat(60))
if (failed > 0) {
process.exit(1)
}
}
main().catch((error) => {
console.error("Erro fatal:", error)
process.exit(1)
})