feat: enhance chat functionality with global notifications and user management

- Implemented global notifications for new messages, allowing users to receive alerts even when the chat is minimized or closed.
- Added functionality for users to leave group conversations and meeting rooms, with appropriate notifications sent to remaining participants.
- Introduced a modal for sending notifications within meeting rooms, enabling admins to communicate important messages to all participants.
- Enhanced the chat components to support mention functionality, allowing users to tag participants in messages for better engagement.
- Updated backend mutations to handle user exit from conversations and sending notifications, ensuring robust data handling and user experience.
This commit is contained in:
2025-11-05 10:40:30 -03:00
parent 8ca737c62f
commit 1774b135b3
6 changed files with 853 additions and 18 deletions

View File

@@ -1300,6 +1300,176 @@ export const rebaixarAdministrador = mutation({
},
});
/**
* Permite que um usuário saia de um grupo ou sala de reunião
*/
export const sairGrupoOuSala = mutation({
args: {
conversaId: v.id("conversas"),
},
returns: v.object({ sucesso: v.boolean(), erro: v.optional(v.string()) }),
handler: async (ctx, args) => {
const usuarioAtual = await getUsuarioAutenticado(ctx);
if (!usuarioAtual) {
return { sucesso: false, erro: "Não autenticado" };
}
const conversa = await ctx.db.get(args.conversaId);
if (!conversa) {
return { sucesso: false, erro: "Conversa não encontrada" };
}
// Verificar se é grupo ou sala de reunião
if (conversa.tipo !== "grupo" && conversa.tipo !== "sala_reuniao") {
return { sucesso: false, erro: "Esta funcionalidade é apenas para grupos e salas de reunião" };
}
// Verificar se usuário é participante
if (!conversa.participantes.includes(usuarioAtual._id)) {
return { sucesso: false, erro: "Você não é participante desta conversa" };
}
// Remover usuário dos participantes
const novosParticipantes = conversa.participantes.filter((p) => p !== usuarioAtual._id);
// Se for sala de reunião e o usuário for administrador, removê-lo também dos administradores
let novosAdministradores = conversa.administradores;
if (conversa.tipo === "sala_reuniao" && conversa.administradores) {
novosAdministradores = conversa.administradores.filter((adminId) => adminId !== usuarioAtual._id);
}
await ctx.db.patch(args.conversaId, {
participantes: novosParticipantes,
administradores: novosAdministradores && novosAdministradores.length > 0 ? novosAdministradores : undefined,
});
// Criar notificação para outros participantes informando que o usuário saiu
const tipoTexto = conversa.tipo === "sala_reuniao" ? "sala de reunião" : "grupo";
for (const participanteId of novosParticipantes) {
await ctx.db.insert("notificacoes", {
usuarioId: participanteId,
tipo: "nova_mensagem",
conversaId: args.conversaId,
remetenteId: usuarioAtual._id,
titulo: "Participante saiu",
descricao: `${usuarioAtual.nome} saiu da ${tipoTexto} "${conversa.nome || "Sem nome"}"`,
lida: false,
criadaEm: Date.now(),
});
}
return { sucesso: true };
},
});
/**
* Encerra uma sala de reunião (apenas administradores)
* Remove todos os participantes e marca a sala como encerrada
*/
export const encerrarReuniao = mutation({
args: {
conversaId: v.id("conversas"),
},
returns: v.object({ sucesso: v.boolean(), erro: v.optional(v.string()) }),
handler: async (ctx, args) => {
const usuarioAtual = await getUsuarioAutenticado(ctx);
if (!usuarioAtual) {
return { sucesso: false, erro: "Não autenticado" };
}
const conversa = await ctx.db.get(args.conversaId);
if (!conversa) {
return { sucesso: false, erro: "Sala de reunião não encontrada" };
}
// Verificar se é sala de reunião
if (conversa.tipo !== "sala_reuniao") {
return { sucesso: false, erro: "Esta funcionalidade é apenas para salas de reunião" };
}
// Verificar se usuário é administrador
const isAdmin = await verificarPermissaoAdmin(ctx, args.conversaId, usuarioAtual._id);
if (!isAdmin) {
return { sucesso: false, erro: "Apenas administradores podem encerrar a reunião" };
}
// Criar notificação para todos os participantes informando que a reunião foi encerrada
for (const participanteId of conversa.participantes) {
if (participanteId !== usuarioAtual._id) {
await ctx.db.insert("notificacoes", {
usuarioId: participanteId,
tipo: "nova_mensagem",
conversaId: args.conversaId,
remetenteId: usuarioAtual._id,
titulo: "Reunião encerrada",
descricao: `A sala de reunião "${conversa.nome || "Sem nome"}" foi encerrada por ${usuarioAtual.nome}`,
lida: false,
criadaEm: Date.now(),
});
}
}
// Remover todos os participantes (exceto o criador, se necessário manter histórico)
// Por enquanto, vamos apenas limpar a lista de participantes
await ctx.db.patch(args.conversaId, {
participantes: [],
administradores: undefined,
});
return { sucesso: true };
},
});
/**
* Envia uma notificação para todos os participantes de uma sala de reunião (apenas administradores)
*/
export const enviarNotificacaoReuniao = mutation({
args: {
conversaId: v.id("conversas"),
titulo: v.string(),
mensagem: v.string(),
},
returns: v.object({ sucesso: v.boolean(), erro: v.optional(v.string()) }),
handler: async (ctx, args) => {
const usuarioAtual = await getUsuarioAutenticado(ctx);
if (!usuarioAtual) {
return { sucesso: false, erro: "Não autenticado" };
}
const conversa = await ctx.db.get(args.conversaId);
if (!conversa) {
return { sucesso: false, erro: "Sala de reunião não encontrada" };
}
// Verificar se é sala de reunião
if (conversa.tipo !== "sala_reuniao") {
return { sucesso: false, erro: "Esta funcionalidade é apenas para salas de reunião" };
}
// Verificar se usuário é administrador
const isAdmin = await verificarPermissaoAdmin(ctx, args.conversaId, usuarioAtual._id);
if (!isAdmin) {
return { sucesso: false, erro: "Apenas administradores podem enviar notificações" };
}
// Criar notificação para todos os participantes
for (const participanteId of conversa.participantes) {
await ctx.db.insert("notificacoes", {
usuarioId: participanteId,
tipo: "nova_mensagem",
conversaId: args.conversaId,
remetenteId: usuarioAtual._id,
titulo: args.titulo || "Notificação da sala de reunião",
descricao: args.mensagem,
lida: false,
criadaEm: Date.now(),
});
}
return { sucesso: true };
},
});
// ========== QUERIES ==========
/**