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:
parent
300179279a
commit
1bc08d3a5f
10 changed files with 1258 additions and 166 deletions
188
scripts/test-all-emails.tsx
Normal file
188
scripts/test-all-emails.tsx
Normal 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)
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue