feat: melhora página de perfil e integra preferências de notificação

- Atualiza cores das badges para padrão cyan do projeto
- Adiciona degradê no header do card de perfil
- Implementa upload de foto de perfil via API Convex
- Integra notificações do Convex com preferências do usuário
- Cria API /api/notifications/send para verificar preferências
- Melhora layout das páginas de login/recuperação com degradê
- Adiciona badge "Helpdesk" e título "Raven" consistente

🤖 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 11:00:02 -03:00
parent 1bc08d3a5f
commit ab7dfa81ca
8 changed files with 543 additions and 59 deletions

View file

@ -2464,10 +2464,13 @@ export const create = mutation({
if (typeof schedulerRunAfter === "function") {
await schedulerRunAfter(0, api.ticketNotifications.sendTicketCreatedEmail, {
to: requesterEmail,
userId: String(requester._id),
userName: requester.name ?? undefined,
ticketId: String(id),
reference: nextRef,
subject,
priority: args.priority,
tenantId: args.tenantId,
})
}
}
@ -2870,15 +2873,19 @@ export const addComment = mutation({
await ctx.db.patch(args.ticketId, { updatedAt: now, ...responsePatch });
// Notificação por e-mail: comentário público para o solicitante
try {
const snapshotEmail = (ticketDoc.requesterSnapshot as { email?: string } | undefined)?.email
const requesterSnapshot = ticketDoc.requesterSnapshot as { email?: string; name?: string } | undefined
const snapshotEmail = requesterSnapshot?.email
if (requestedVisibility === "PUBLIC" && snapshotEmail && String(ticketDoc.requesterId) !== String(args.authorId)) {
const schedulerRunAfter = ctx.scheduler?.runAfter
if (typeof schedulerRunAfter === "function") {
await schedulerRunAfter(0, api.ticketNotifications.sendPublicCommentEmail, {
to: snapshotEmail,
userId: ticketDoc.requesterId ? String(ticketDoc.requesterId) : undefined,
userName: requesterSnapshot?.name ?? undefined,
ticketId: String(ticketDoc._id),
reference: ticketDoc.reference ?? 0,
subject: ticketDoc.subject ?? "",
tenantId: ticketDoc.tenantId,
})
}
}
@ -3146,16 +3153,21 @@ export async function resolveTicketHandler(
// Notificação por e-mail: encerramento do chamado
try {
const requesterDoc = await ctx.db.get(ticketDoc.requesterId)
const email = (requesterDoc as Doc<"users"> | null)?.email || (ticketDoc.requesterSnapshot as { email?: string } | undefined)?.email || null
const requesterDoc = await ctx.db.get(ticketDoc.requesterId) as Doc<"users"> | null
const requesterSnapshot = ticketDoc.requesterSnapshot as { email?: string; name?: string } | undefined
const email = requesterDoc?.email || requesterSnapshot?.email || null
const userName = requesterDoc?.name || requesterSnapshot?.name || undefined
if (email) {
const schedulerRunAfter = ctx.scheduler?.runAfter
if (typeof schedulerRunAfter === "function") {
await schedulerRunAfter(0, api.ticketNotifications.sendResolvedEmail, {
to: email,
userId: ticketDoc.requesterId ? String(ticketDoc.requesterId) : undefined,
userName,
ticketId: String(ticketId),
reference: ticketDoc.reference ?? 0,
subject: ticketDoc.subject ?? "",
tenantId: ticketDoc.tenantId,
})
}
}