fix: corrige hydration, notificacoes e melhora visual
- Corrige hydration mismatch no Toaster (sonner) e ChatWidgetProvider - Corrige API de preferencias de notificacao (typePreferences como string) - Melhora visual do Switch (estado ativo em preto) - Adiciona icones em circulos na pagina de notificacoes - Corrige acentuacao em "Obrigatorio" - Corrige centralizacao das estrelas de avaliacao nos e-mails - Aplica Sentence case nos titulos dos templates de e-mail - Adiciona script de teste 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
7a3791117b
commit
eedd446b36
7 changed files with 302 additions and 55 deletions
209
scripts/test-email.ts
Normal file
209
scripts/test-email.ts
Normal file
|
|
@ -0,0 +1,209 @@
|
||||||
|
/**
|
||||||
|
* Script para testar envio de e-mail
|
||||||
|
* Uso: bun scripts/test-email.ts [destinatario]
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { sendSmtpMail } from "../src/server/email-smtp"
|
||||||
|
import { renderTemplate } from "../src/server/email/email-templates"
|
||||||
|
|
||||||
|
const DESTINATARIO = process.argv[2] || "renan.pac@paulicon.com.br"
|
||||||
|
|
||||||
|
// Credenciais do SMTP (usando as da documentacao)
|
||||||
|
const smtpConfig = {
|
||||||
|
host: "smtp.c.inova.com.br",
|
||||||
|
port: 587,
|
||||||
|
username: "envio@rever.com.br",
|
||||||
|
password: "CAAJQm6ZT6AUdhXRTDYu",
|
||||||
|
from: '"Sistema de Chamados" <envio@rever.com.br>',
|
||||||
|
starttls: true,
|
||||||
|
tls: false,
|
||||||
|
rejectUnauthorized: false,
|
||||||
|
timeoutMs: 15000,
|
||||||
|
}
|
||||||
|
|
||||||
|
async function testEmail() {
|
||||||
|
console.log("=".repeat(50))
|
||||||
|
console.log("TESTE DE ENVIO DE E-MAIL")
|
||||||
|
console.log("=".repeat(50))
|
||||||
|
console.log(`Destinatario: ${DESTINATARIO}`)
|
||||||
|
console.log(`SMTP: ${smtpConfig.host}:${smtpConfig.port}`)
|
||||||
|
console.log("")
|
||||||
|
|
||||||
|
// 1. Teste basico
|
||||||
|
console.log("[1/10] Enviando e-mail de teste basico...")
|
||||||
|
try {
|
||||||
|
const html = renderTemplate("test", {
|
||||||
|
title: "Teste do Sistema de E-mail",
|
||||||
|
message: "Este e-mail confirma que o sistema de notificacoes esta funcionando corretamente.",
|
||||||
|
timestamp: new Date().toLocaleString("pt-BR", { timeZone: "America/Sao_Paulo" }),
|
||||||
|
})
|
||||||
|
|
||||||
|
await sendSmtpMail(smtpConfig, DESTINATARIO, "Teste - Sistema de Chamados Raven", html)
|
||||||
|
console.log(" SUCESSO!")
|
||||||
|
} catch (error) {
|
||||||
|
console.error(" ERRO:", (error as Error).message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Teste de abertura de chamado
|
||||||
|
console.log("\n[2/10] Enviando notificacao de abertura de chamado...")
|
||||||
|
try {
|
||||||
|
const html = renderTemplate("ticket_created", {
|
||||||
|
reference: 12345,
|
||||||
|
subject: "Problema no sistema de vendas",
|
||||||
|
status: "PENDING",
|
||||||
|
priority: "HIGH",
|
||||||
|
createdAt: new Date().toISOString(),
|
||||||
|
viewUrl: "https://tickets.esdrasrenan.com.br/tickets/12345",
|
||||||
|
})
|
||||||
|
|
||||||
|
await sendSmtpMail(smtpConfig, DESTINATARIO, "Chamado #12345 Aberto - Problema no sistema de vendas", html)
|
||||||
|
console.log(" SUCESSO!")
|
||||||
|
} catch (error) {
|
||||||
|
console.error(" ERRO:", (error as Error).message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Teste de resolucao de chamado
|
||||||
|
console.log("\n[3/10] Enviando notificacao de resolucao...")
|
||||||
|
try {
|
||||||
|
const html = renderTemplate("ticket_resolved", {
|
||||||
|
reference: 12345,
|
||||||
|
subject: "Problema no sistema de vendas",
|
||||||
|
assigneeName: "Joao Silva",
|
||||||
|
resolutionSummary: "O problema foi identificado como uma configuracao incorreta no modulo de pagamentos. A configuracao foi corrigida e o sistema esta funcionando normalmente.",
|
||||||
|
viewUrl: "https://tickets.esdrasrenan.com.br/tickets/12345",
|
||||||
|
rateUrl: "https://tickets.esdrasrenan.com.br/rate/12345",
|
||||||
|
})
|
||||||
|
|
||||||
|
await sendSmtpMail(smtpConfig, DESTINATARIO, "Chamado #12345 Resolvido - Problema no sistema de vendas", html)
|
||||||
|
console.log(" SUCESSO!")
|
||||||
|
} catch (error) {
|
||||||
|
console.error(" ERRO:", (error as Error).message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Teste de comentario
|
||||||
|
console.log("\n[4/10] Enviando notificacao de comentario...")
|
||||||
|
try {
|
||||||
|
const html = renderTemplate("ticket_comment", {
|
||||||
|
reference: 12345,
|
||||||
|
subject: "Problema no sistema de vendas",
|
||||||
|
authorName: "Joao Silva",
|
||||||
|
commentBody: "Estou analisando o problema e em breve envio uma atualizacao. Por favor, verifique se o erro persiste apos limpar o cache do navegador.",
|
||||||
|
commentedAt: new Date().toISOString(),
|
||||||
|
viewUrl: "https://tickets.esdrasrenan.com.br/tickets/12345",
|
||||||
|
})
|
||||||
|
|
||||||
|
await sendSmtpMail(smtpConfig, DESTINATARIO, "Nova atualizacao no Chamado #12345", html)
|
||||||
|
console.log(" SUCESSO!")
|
||||||
|
} catch (error) {
|
||||||
|
console.error(" ERRO:", (error as Error).message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Teste de atribuicao de chamado
|
||||||
|
console.log("\n[5/10] Enviando notificacao de atribuicao...")
|
||||||
|
try {
|
||||||
|
const html = renderTemplate("ticket_assigned", {
|
||||||
|
reference: 12345,
|
||||||
|
subject: "Problema no sistema de vendas",
|
||||||
|
status: "AWAITING_ATTENDANCE",
|
||||||
|
priority: "HIGH",
|
||||||
|
requesterName: "Maria Santos",
|
||||||
|
assigneeName: "Joao Silva",
|
||||||
|
isForRequester: false,
|
||||||
|
viewUrl: "https://tickets.esdrasrenan.com.br/tickets/12345",
|
||||||
|
})
|
||||||
|
|
||||||
|
await sendSmtpMail(smtpConfig, DESTINATARIO, "Chamado #12345 Atribuido", html)
|
||||||
|
console.log(" SUCESSO!")
|
||||||
|
} catch (error) {
|
||||||
|
console.error(" ERRO:", (error as Error).message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. Teste de mudanca de status
|
||||||
|
console.log("\n[6/10] Enviando notificacao de mudanca de status...")
|
||||||
|
try {
|
||||||
|
const html = renderTemplate("ticket_status", {
|
||||||
|
reference: 12345,
|
||||||
|
subject: "Problema no sistema de vendas",
|
||||||
|
oldStatus: "PENDING",
|
||||||
|
newStatus: "AWAITING_ATTENDANCE",
|
||||||
|
viewUrl: "https://tickets.esdrasrenan.com.br/tickets/12345",
|
||||||
|
})
|
||||||
|
|
||||||
|
await sendSmtpMail(smtpConfig, DESTINATARIO, "Status do Chamado #12345 Alterado", html)
|
||||||
|
console.log(" SUCESSO!")
|
||||||
|
} catch (error) {
|
||||||
|
console.error(" ERRO:", (error as Error).message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 7. Teste de reset de senha
|
||||||
|
console.log("\n[7/10] Enviando notificacao de reset de senha...")
|
||||||
|
try {
|
||||||
|
const html = renderTemplate("password_reset", {
|
||||||
|
resetUrl: "https://tickets.esdrasrenan.com.br/reset-password?token=abc123",
|
||||||
|
})
|
||||||
|
|
||||||
|
await sendSmtpMail(smtpConfig, DESTINATARIO, "Redefinicao de Senha - Raven", html)
|
||||||
|
console.log(" SUCESSO!")
|
||||||
|
} catch (error) {
|
||||||
|
console.error(" ERRO:", (error as Error).message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 8. Teste de convite
|
||||||
|
console.log("\n[8/10] Enviando notificacao de convite...")
|
||||||
|
try {
|
||||||
|
const html = renderTemplate("invite", {
|
||||||
|
inviterName: "Admin Sistema",
|
||||||
|
roleName: "Agente",
|
||||||
|
companyName: "Empresa Teste",
|
||||||
|
inviteUrl: "https://tickets.esdrasrenan.com.br/invite?token=xyz789",
|
||||||
|
})
|
||||||
|
|
||||||
|
await sendSmtpMail(smtpConfig, DESTINATARIO, "Voce foi convidado - Raven", html)
|
||||||
|
console.log(" SUCESSO!")
|
||||||
|
} catch (error) {
|
||||||
|
console.error(" ERRO:", (error as Error).message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 9. Teste de novo login
|
||||||
|
console.log("\n[9/10] Enviando notificacao de novo login...")
|
||||||
|
try {
|
||||||
|
const html = renderTemplate("new_login", {
|
||||||
|
loginAt: new Date().toISOString(),
|
||||||
|
userAgent: "Chrome 120 no Windows 11",
|
||||||
|
ipAddress: "189.45.123.78",
|
||||||
|
})
|
||||||
|
|
||||||
|
await sendSmtpMail(smtpConfig, DESTINATARIO, "Novo Acesso Detectado - Raven", html)
|
||||||
|
console.log(" SUCESSO!")
|
||||||
|
} catch (error) {
|
||||||
|
console.error(" ERRO:", (error as Error).message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 10. Teste de SLA em risco
|
||||||
|
console.log("\n[10/10] Enviando notificacao de SLA em risco...")
|
||||||
|
try {
|
||||||
|
const html = renderTemplate("sla_warning", {
|
||||||
|
reference: 12345,
|
||||||
|
subject: "Problema no sistema de vendas",
|
||||||
|
status: "AWAITING_ATTENDANCE",
|
||||||
|
priority: "HIGH",
|
||||||
|
requesterName: "Maria Santos",
|
||||||
|
assigneeName: "Joao Silva",
|
||||||
|
timeRemaining: "2 horas",
|
||||||
|
dueAt: new Date(Date.now() + 2 * 60 * 60 * 1000).toISOString(),
|
||||||
|
viewUrl: "https://tickets.esdrasrenan.com.br/tickets/12345",
|
||||||
|
})
|
||||||
|
|
||||||
|
await sendSmtpMail(smtpConfig, DESTINATARIO, "ALERTA: SLA em Risco - Chamado #12345", html)
|
||||||
|
console.log(" SUCESSO!")
|
||||||
|
} catch (error) {
|
||||||
|
console.error(" ERRO:", (error as Error).message)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("\n" + "=".repeat(50))
|
||||||
|
console.log("TESTE CONCLUIDO - 10 TIPOS DE NOTIFICACAO")
|
||||||
|
console.log("=".repeat(50))
|
||||||
|
console.log(`Verifique a caixa de entrada de: ${DESTINATARIO}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
testEmail().catch(console.error)
|
||||||
|
|
@ -69,8 +69,8 @@ export async function GET(_request: NextRequest) {
|
||||||
tenantId: user.tenantId,
|
tenantId: user.tenantId,
|
||||||
emailEnabled: true,
|
emailEnabled: true,
|
||||||
digestFrequency: "immediate",
|
digestFrequency: "immediate",
|
||||||
typePreferences: {},
|
typePreferences: "{}",
|
||||||
categoryPreferences: {},
|
categoryPreferences: "{}",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
|
import { useEffect, useState } from "react"
|
||||||
import dynamic from "next/dynamic"
|
import dynamic from "next/dynamic"
|
||||||
import { api } from "@/convex/_generated/api"
|
import { api } from "@/convex/_generated/api"
|
||||||
import { useAuth } from "@/lib/auth-client"
|
import { useAuth } from "@/lib/auth-client"
|
||||||
|
|
@ -17,12 +18,19 @@ function checkLiveChatApiExists() {
|
||||||
// Importacao dinamica para evitar problemas de SSR
|
// Importacao dinamica para evitar problemas de SSR
|
||||||
const ChatWidget = dynamic(
|
const ChatWidget = dynamic(
|
||||||
() => import("./chat-widget").then((mod) => ({ default: mod.ChatWidget })),
|
() => import("./chat-widget").then((mod) => ({ default: mod.ChatWidget })),
|
||||||
{ ssr: false }
|
{ ssr: false, loading: () => null }
|
||||||
)
|
)
|
||||||
|
|
||||||
export function ChatWidgetProvider() {
|
export function ChatWidgetProvider() {
|
||||||
const { role, isLoading } = useAuth()
|
const { role, isLoading } = useAuth()
|
||||||
|
const [mounted, setMounted] = useState(false)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setMounted(true)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
// Evita hydration mismatch - so renderiza apos montar no cliente
|
||||||
|
if (!mounted) return null
|
||||||
if (isLoading) return null
|
if (isLoading) return null
|
||||||
if (!isAgentOrAdmin(role)) return null
|
if (!isAgentOrAdmin(role)) return null
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@
|
||||||
import { Separator } from "@/components/ui/separator"
|
import { Separator } from "@/components/ui/separator"
|
||||||
import { Badge } from "@/components/ui/badge"
|
import { Badge } from "@/components/ui/badge"
|
||||||
import { Input } from "@/components/ui/input"
|
import { Input } from "@/components/ui/input"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
interface NotificationType {
|
interface NotificationType {
|
||||||
type: string
|
type: string
|
||||||
|
|
@ -304,37 +305,57 @@ export function NotificationPreferencesForm({ isPortal = false }: NotificationPr
|
||||||
<p className="text-sm text-muted-foreground">{group.description}</p>
|
<p className="text-sm text-muted-foreground">{group.description}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{types.map((notifType) => (
|
{types.map((notifType) => {
|
||||||
<div
|
const isEnabled = localTypePrefs[notifType.type] ?? notifType.enabled
|
||||||
key={notifType.type}
|
return (
|
||||||
className="flex items-center justify-between rounded-lg border p-4"
|
<div
|
||||||
>
|
key={notifType.type}
|
||||||
<div className="flex items-center gap-3">
|
className={cn(
|
||||||
{notifType.required ? (
|
"flex items-center justify-between rounded-lg border p-4 transition-colors",
|
||||||
<Lock className="h-4 w-4 text-muted-foreground" />
|
isEnabled ? "border-foreground/20 bg-foreground/[0.02]" : "border-border"
|
||||||
) : localTypePrefs[notifType.type] ? (
|
|
||||||
<Bell className="h-4 w-4 text-primary" />
|
|
||||||
) : (
|
|
||||||
<BellOff className="h-4 w-4 text-muted-foreground" />
|
|
||||||
)}
|
)}
|
||||||
<div>
|
>
|
||||||
<Label className="text-sm font-medium">
|
<div className="flex items-center gap-3">
|
||||||
{notifType.label}
|
<div
|
||||||
</Label>
|
className={cn(
|
||||||
{notifType.required && (
|
"flex h-8 w-8 items-center justify-center rounded-full transition-colors",
|
||||||
<Badge variant="secondary" className="ml-2 text-xs">
|
notifType.required
|
||||||
Obrigatório
|
? "bg-muted text-muted-foreground"
|
||||||
</Badge>
|
: isEnabled
|
||||||
)}
|
? "bg-foreground text-background"
|
||||||
|
: "bg-muted text-muted-foreground"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{notifType.required ? (
|
||||||
|
<Lock className="h-4 w-4" />
|
||||||
|
) : isEnabled ? (
|
||||||
|
<Bell className="h-4 w-4" />
|
||||||
|
) : (
|
||||||
|
<BellOff className="h-4 w-4" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label className={cn(
|
||||||
|
"text-sm font-medium transition-colors",
|
||||||
|
isEnabled ? "text-foreground" : "text-muted-foreground"
|
||||||
|
)}>
|
||||||
|
{notifType.label}
|
||||||
|
</Label>
|
||||||
|
{notifType.required && (
|
||||||
|
<Badge variant="secondary" className="ml-2 text-xs bg-foreground text-background">
|
||||||
|
Obrigatório
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<Switch
|
||||||
|
checked={isEnabled}
|
||||||
|
onCheckedChange={(checked) => toggleType(notifType.type, checked)}
|
||||||
|
disabled={!notifType.canDisable}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Switch
|
)
|
||||||
checked={localTypePrefs[notifType.type] ?? notifType.enabled}
|
})}
|
||||||
onCheckedChange={(checked) => toggleType(notifType.type, checked)}
|
|
||||||
disabled={!notifType.canDisable}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,19 @@
|
||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
|
import { useEffect, useState } from "react"
|
||||||
import { useTheme } from "next-themes"
|
import { useTheme } from "next-themes"
|
||||||
import { Toaster as Sonner, ToasterProps } from "sonner"
|
import { Toaster as Sonner, ToasterProps } from "sonner"
|
||||||
|
|
||||||
const Toaster = ({ ...props }: ToasterProps) => {
|
const Toaster = ({ ...props }: ToasterProps) => {
|
||||||
const { theme = "system" } = useTheme()
|
const { theme = "system" } = useTheme()
|
||||||
|
const [mounted, setMounted] = useState(false)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setMounted(true)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
// Evita hydration mismatch - so renderiza apos montar no cliente
|
||||||
|
if (!mounted) return null
|
||||||
|
|
||||||
const baseClass =
|
const baseClass =
|
||||||
"inline-flex w-auto min-w-0 items-center justify-center gap-2 self-center rounded-xl border border-black bg-black px-3 py-2 text-sm font-medium text-white shadow-lg"
|
"inline-flex w-auto min-w-0 items-center justify-center gap-2 self-center rounded-xl border border-black bg-black px-3 py-2 text-sm font-medium text-white shadow-lg"
|
||||||
|
|
|
||||||
|
|
@ -12,14 +12,14 @@ const Switch = React.forwardRef<
|
||||||
<SwitchPrimitive.Root
|
<SwitchPrimitive.Root
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border border-transparent bg-input transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-sidebar-accent data-[state=checked]:text-sidebar-accent-foreground",
|
"peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border border-transparent bg-muted transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-foreground data-[state=checked]:border-foreground",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<SwitchPrimitive.Thumb
|
<SwitchPrimitive.Thumb
|
||||||
className={cn(
|
className={cn(
|
||||||
"pointer-events-none block h-5 w-5 translate-x-0 rounded-full bg-background shadow transition-transform duration-200 data-[state=checked]:translate-x-[20px]",
|
"pointer-events-none block h-5 w-5 translate-x-0 rounded-full bg-background shadow-sm transition-transform duration-200 data-[state=checked]:translate-x-[20px] data-[state=checked]:bg-background",
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</SwitchPrimitive.Root>
|
</SwitchPrimitive.Root>
|
||||||
|
|
|
||||||
|
|
@ -236,12 +236,12 @@ function ratingStars(rateUrl: string): string {
|
||||||
}
|
}
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<table cellpadding="0" cellspacing="0" style="margin:16px 0;">
|
<table cellpadding="0" cellspacing="0" style="margin:16px auto;" align="center">
|
||||||
<tr>
|
<tr>
|
||||||
${stars.join("")}
|
${stars.join("")}
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
<p style="color:${COLORS.textMuted};font-size:12px;margin:4px 0 0 0;">Clique em uma estrela para avaliar</p>
|
<p style="color:${COLORS.textMuted};font-size:12px;margin:4px 0 0 0;text-align:center;">Clique em uma estrela para avaliar</p>
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -353,7 +353,7 @@ const templates: Record<TemplateName, (data: TemplateData) => string> = {
|
||||||
return baseTemplate(
|
return baseTemplate(
|
||||||
`
|
`
|
||||||
<h1 style="color:${COLORS.textPrimary};font-size:24px;font-weight:600;margin:0 0 8px 0;">
|
<h1 style="color:${COLORS.textPrimary};font-size:24px;font-weight:600;margin:0 0 8px 0;">
|
||||||
Chamado Aberto
|
Chamado aberto
|
||||||
</h1>
|
</h1>
|
||||||
<p style="color:${COLORS.textSecondary};font-size:14px;line-height:1.6;margin:0 0 24px 0;">
|
<p style="color:${COLORS.textSecondary};font-size:14px;line-height:1.6;margin:0 0 24px 0;">
|
||||||
Seu chamado foi registrado com sucesso. Nossa equipe irá analisá-lo em breve.
|
Seu chamado foi registrado com sucesso. Nossa equipe irá analisá-lo em breve.
|
||||||
|
|
@ -368,7 +368,7 @@ const templates: Record<TemplateName, (data: TemplateData) => string> = {
|
||||||
})}
|
})}
|
||||||
|
|
||||||
<div style="text-align:center;margin-top:24px;">
|
<div style="text-align:center;margin-top:24px;">
|
||||||
${button("Ver Chamado", viewUrl)}
|
${button("Ver chamado", viewUrl)}
|
||||||
</div>
|
</div>
|
||||||
`,
|
`,
|
||||||
data
|
data
|
||||||
|
|
@ -383,7 +383,7 @@ const templates: Record<TemplateName, (data: TemplateData) => string> = {
|
||||||
return baseTemplate(
|
return baseTemplate(
|
||||||
`
|
`
|
||||||
<h1 style="color:${COLORS.textPrimary};font-size:24px;font-weight:600;margin:0 0 8px 0;">
|
<h1 style="color:${COLORS.textPrimary};font-size:24px;font-weight:600;margin:0 0 8px 0;">
|
||||||
Chamado Resolvido
|
Chamado resolvido
|
||||||
</h1>
|
</h1>
|
||||||
<p style="color:${COLORS.textSecondary};font-size:14px;line-height:1.6;margin:0 0 24px 0;">
|
<p style="color:${COLORS.textSecondary};font-size:14px;line-height:1.6;margin:0 0 24px 0;">
|
||||||
Seu chamado foi marcado como resolvido. Esperamos que o atendimento tenha sido satisfatório!
|
Seu chamado foi marcado como resolvido. Esperamos que o atendimento tenha sido satisfatório!
|
||||||
|
|
@ -414,7 +414,7 @@ const templates: Record<TemplateName, (data: TemplateData) => string> = {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="text-align:center;margin-top:24px;">
|
<div style="text-align:center;margin-top:24px;">
|
||||||
${button("Ver Chamado", viewUrl)}
|
${button("Ver chamado", viewUrl)}
|
||||||
</div>
|
</div>
|
||||||
`,
|
`,
|
||||||
data
|
data
|
||||||
|
|
@ -426,7 +426,7 @@ const templates: Record<TemplateName, (data: TemplateData) => string> = {
|
||||||
const viewUrl = data.viewUrl as string
|
const viewUrl = data.viewUrl as string
|
||||||
const isForRequester = data.isForRequester as boolean
|
const isForRequester = data.isForRequester as boolean
|
||||||
|
|
||||||
const title = isForRequester ? "Agente Atribuído ao Chamado" : "Novo Chamado Atribuído"
|
const title = isForRequester ? "Agente atribuído ao chamado" : "Novo chamado atribuído"
|
||||||
const message = isForRequester
|
const message = isForRequester
|
||||||
? `O agente ${escapeHtml(data.assigneeName)} foi atribuído ao seu chamado e em breve entrará em contato.`
|
? `O agente ${escapeHtml(data.assigneeName)} foi atribuído ao seu chamado e em breve entrará em contato.`
|
||||||
: `Um novo chamado foi atribuído a você. Por favor, verifique os detalhes abaixo.`
|
: `Um novo chamado foi atribuído a você. Por favor, verifique os detalhes abaixo.`
|
||||||
|
|
@ -450,7 +450,7 @@ const templates: Record<TemplateName, (data: TemplateData) => string> = {
|
||||||
})}
|
})}
|
||||||
|
|
||||||
<div style="text-align:center;margin-top:24px;">
|
<div style="text-align:center;margin-top:24px;">
|
||||||
${button("Ver Chamado", viewUrl)}
|
${button("Ver chamado", viewUrl)}
|
||||||
</div>
|
</div>
|
||||||
`,
|
`,
|
||||||
data
|
data
|
||||||
|
|
@ -466,7 +466,7 @@ const templates: Record<TemplateName, (data: TemplateData) => string> = {
|
||||||
return baseTemplate(
|
return baseTemplate(
|
||||||
`
|
`
|
||||||
<h1 style="color:${COLORS.textPrimary};font-size:24px;font-weight:600;margin:0 0 8px 0;">
|
<h1 style="color:${COLORS.textPrimary};font-size:24px;font-weight:600;margin:0 0 8px 0;">
|
||||||
Status Atualizado
|
Status atualizado
|
||||||
</h1>
|
</h1>
|
||||||
<p style="color:${COLORS.textSecondary};font-size:14px;line-height:1.6;margin:0 0 24px 0;">
|
<p style="color:${COLORS.textSecondary};font-size:14px;line-height:1.6;margin:0 0 24px 0;">
|
||||||
O status do seu chamado foi alterado.
|
O status do seu chamado foi alterado.
|
||||||
|
|
@ -492,7 +492,7 @@ const templates: Record<TemplateName, (data: TemplateData) => string> = {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="text-align:center;margin-top:24px;">
|
<div style="text-align:center;margin-top:24px;">
|
||||||
${button("Ver Chamado", viewUrl)}
|
${button("Ver chamado", viewUrl)}
|
||||||
</div>
|
</div>
|
||||||
`,
|
`,
|
||||||
data
|
data
|
||||||
|
|
@ -506,7 +506,7 @@ const templates: Record<TemplateName, (data: TemplateData) => string> = {
|
||||||
return baseTemplate(
|
return baseTemplate(
|
||||||
`
|
`
|
||||||
<h1 style="color:${COLORS.textPrimary};font-size:24px;font-weight:600;margin:0 0 8px 0;">
|
<h1 style="color:${COLORS.textPrimary};font-size:24px;font-weight:600;margin:0 0 8px 0;">
|
||||||
Nova Atualização no Chamado
|
Nova atualização no chamado
|
||||||
</h1>
|
</h1>
|
||||||
<p style="color:${COLORS.textSecondary};font-size:14px;line-height:1.6;margin:0 0 24px 0;">
|
<p style="color:${COLORS.textSecondary};font-size:14px;line-height:1.6;margin:0 0 24px 0;">
|
||||||
${escapeHtml(data.authorName)} adicionou um comentário ao seu chamado.
|
${escapeHtml(data.authorName)} adicionou um comentário ao seu chamado.
|
||||||
|
|
@ -527,7 +527,7 @@ const templates: Record<TemplateName, (data: TemplateData) => string> = {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="text-align:center;margin-top:24px;">
|
<div style="text-align:center;margin-top:24px;">
|
||||||
${button("Ver Chamado", viewUrl)}
|
${button("Ver chamado", viewUrl)}
|
||||||
</div>
|
</div>
|
||||||
`,
|
`,
|
||||||
data
|
data
|
||||||
|
|
@ -541,14 +541,14 @@ const templates: Record<TemplateName, (data: TemplateData) => string> = {
|
||||||
return baseTemplate(
|
return baseTemplate(
|
||||||
`
|
`
|
||||||
<h1 style="color:${COLORS.textPrimary};font-size:24px;font-weight:600;margin:0 0 8px 0;">
|
<h1 style="color:${COLORS.textPrimary};font-size:24px;font-weight:600;margin:0 0 8px 0;">
|
||||||
Redefinição de Senha
|
Redefinição de senha
|
||||||
</h1>
|
</h1>
|
||||||
<p style="color:${COLORS.textSecondary};font-size:14px;line-height:1.6;margin:0 0 24px 0;">
|
<p style="color:${COLORS.textSecondary};font-size:14px;line-height:1.6;margin:0 0 24px 0;">
|
||||||
Recebemos uma solicitação para redefinir a senha da sua conta. Se você não fez essa solicitação, pode ignorar este e-mail.
|
Recebemos uma solicitação para redefinir a senha da sua conta. Se você não fez essa solicitação, pode ignorar este e-mail.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div style="text-align:center;margin:32px 0;">
|
<div style="text-align:center;margin:32px 0;">
|
||||||
${button("Redefinir Senha", resetUrl)}
|
${button("Redefinir senha", resetUrl)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p style="color:${COLORS.textMuted};font-size:12px;margin:24px 0 0 0;">
|
<p style="color:${COLORS.textMuted};font-size:12px;margin:24px 0 0 0;">
|
||||||
|
|
@ -567,14 +567,14 @@ const templates: Record<TemplateName, (data: TemplateData) => string> = {
|
||||||
return baseTemplate(
|
return baseTemplate(
|
||||||
`
|
`
|
||||||
<h1 style="color:${COLORS.textPrimary};font-size:24px;font-weight:600;margin:0 0 8px 0;">
|
<h1 style="color:${COLORS.textPrimary};font-size:24px;font-weight:600;margin:0 0 8px 0;">
|
||||||
Confirme seu E-mail
|
Confirme seu e-mail
|
||||||
</h1>
|
</h1>
|
||||||
<p style="color:${COLORS.textSecondary};font-size:14px;line-height:1.6;margin:0 0 24px 0;">
|
<p style="color:${COLORS.textSecondary};font-size:14px;line-height:1.6;margin:0 0 24px 0;">
|
||||||
Clique no botão abaixo para confirmar seu endereço de e-mail e ativar sua conta.
|
Clique no botão abaixo para confirmar seu endereço de e-mail e ativar sua conta.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div style="text-align:center;margin:32px 0;">
|
<div style="text-align:center;margin:32px 0;">
|
||||||
${button("Confirmar E-mail", verifyUrl)}
|
${button("Confirmar e-mail", verifyUrl)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p style="color:${COLORS.textMuted};font-size:12px;margin:24px 0 0 0;">
|
<p style="color:${COLORS.textMuted};font-size:12px;margin:24px 0 0 0;">
|
||||||
|
|
@ -618,7 +618,7 @@ const templates: Record<TemplateName, (data: TemplateData) => string> = {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="text-align:center;margin:32px 0;">
|
<div style="text-align:center;margin:32px 0;">
|
||||||
${button("Aceitar Convite", inviteUrl)}
|
${button("Aceitar convite", inviteUrl)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p style="color:${COLORS.textMuted};font-size:12px;margin:24px 0 0 0;">
|
<p style="color:${COLORS.textMuted};font-size:12px;margin:24px 0 0 0;">
|
||||||
|
|
@ -634,7 +634,7 @@ const templates: Record<TemplateName, (data: TemplateData) => string> = {
|
||||||
return baseTemplate(
|
return baseTemplate(
|
||||||
`
|
`
|
||||||
<h1 style="color:${COLORS.textPrimary};font-size:24px;font-weight:600;margin:0 0 8px 0;">
|
<h1 style="color:${COLORS.textPrimary};font-size:24px;font-weight:600;margin:0 0 8px 0;">
|
||||||
Novo Acesso Detectado
|
Novo acesso detectado
|
||||||
</h1>
|
</h1>
|
||||||
<p style="color:${COLORS.textSecondary};font-size:14px;line-height:1.6;margin:0 0 24px 0;">
|
<p style="color:${COLORS.textSecondary};font-size:14px;line-height:1.6;margin:0 0 24px 0;">
|
||||||
Detectamos um novo acesso à sua conta. Se foi você, pode ignorar este e-mail.
|
Detectamos um novo acesso à sua conta. Se foi você, pode ignorar este e-mail.
|
||||||
|
|
@ -672,7 +672,7 @@ const templates: Record<TemplateName, (data: TemplateData) => string> = {
|
||||||
return baseTemplate(
|
return baseTemplate(
|
||||||
`
|
`
|
||||||
<h1 style="color:${COLORS.statusPaused};font-size:24px;font-weight:600;margin:0 0 8px 0;">
|
<h1 style="color:${COLORS.statusPaused};font-size:24px;font-weight:600;margin:0 0 8px 0;">
|
||||||
SLA em Risco
|
SLA em risco
|
||||||
</h1>
|
</h1>
|
||||||
<p style="color:${COLORS.textSecondary};font-size:14px;line-height:1.6;margin:0 0 24px 0;">
|
<p style="color:${COLORS.textSecondary};font-size:14px;line-height:1.6;margin:0 0 24px 0;">
|
||||||
O chamado abaixo está próximo de violar o SLA. Ação necessária!
|
O chamado abaixo está próximo de violar o SLA. Ação necessária!
|
||||||
|
|
@ -697,7 +697,7 @@ const templates: Record<TemplateName, (data: TemplateData) => string> = {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="text-align:center;margin-top:24px;">
|
<div style="text-align:center;margin-top:24px;">
|
||||||
${button("Ver Chamado", viewUrl)}
|
${button("Ver chamado", viewUrl)}
|
||||||
</div>
|
</div>
|
||||||
`,
|
`,
|
||||||
data
|
data
|
||||||
|
|
@ -711,7 +711,7 @@ const templates: Record<TemplateName, (data: TemplateData) => string> = {
|
||||||
return baseTemplate(
|
return baseTemplate(
|
||||||
`
|
`
|
||||||
<h1 style="color:${COLORS.priorityUrgent};font-size:24px;font-weight:600;margin:0 0 8px 0;">
|
<h1 style="color:${COLORS.priorityUrgent};font-size:24px;font-weight:600;margin:0 0 8px 0;">
|
||||||
SLA Violado
|
SLA violado
|
||||||
</h1>
|
</h1>
|
||||||
<p style="color:${COLORS.textSecondary};font-size:14px;line-height:1.6;margin:0 0 24px 0;">
|
<p style="color:${COLORS.textSecondary};font-size:14px;line-height:1.6;margin:0 0 24px 0;">
|
||||||
O chamado abaixo violou o SLA estabelecido. Atenção urgente necessária!
|
O chamado abaixo violou o SLA estabelecido. Atenção urgente necessária!
|
||||||
|
|
@ -736,7 +736,7 @@ const templates: Record<TemplateName, (data: TemplateData) => string> = {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="text-align:center;margin-top:24px;">
|
<div style="text-align:center;margin-top:24px;">
|
||||||
${button("Ver Chamado", viewUrl)}
|
${button("Ver chamado", viewUrl)}
|
||||||
</div>
|
</div>
|
||||||
`,
|
`,
|
||||||
data
|
data
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue