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:
@@ -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 ==========
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user