refactor: integrate current user data across components
- Replaced instances of `authStore` with `currentUser` to streamline user authentication handling. - Updated permission checks and user-related data retrieval to utilize the new `useQuery` for better performance and clarity. - Cleaned up component structures and improved formatting for consistency and readability. - Enhanced error handling and user feedback mechanisms in various components to improve user experience.
This commit is contained in:
@@ -3,7 +3,6 @@
|
||||
import { api } from "@sgse-app/backend/convex/_generated/api";
|
||||
import type { Id } from "@sgse-app/backend/convex/_generated/dataModel";
|
||||
import { onMount } from "svelte";
|
||||
import { authStore } from "$lib/stores/auth.svelte";
|
||||
import { Paperclip, Smile, Send } from "lucide-svelte";
|
||||
|
||||
interface Props {
|
||||
@@ -35,18 +34,67 @@
|
||||
let uploadingFile = $state(false);
|
||||
let digitacaoTimeout: ReturnType<typeof setTimeout> | null = null;
|
||||
let showEmojiPicker = $state(false);
|
||||
let mensagemRespondendo: { id: Id<"mensagens">; conteudo: string; remetente: string } | null = $state(null);
|
||||
let mensagemRespondendo: {
|
||||
id: Id<"mensagens">;
|
||||
conteudo: string;
|
||||
remetente: string;
|
||||
} | null = $state(null);
|
||||
let showMentionsDropdown = $state(false);
|
||||
let mentionQuery = $state("");
|
||||
let mentionStartPos = $state(0);
|
||||
|
||||
// Emojis mais usados
|
||||
const emojis = [
|
||||
"😀", "😃", "😄", "😁", "😅", "😂", "🤣", "😊", "😇", "🙂",
|
||||
"🙃", "😉", "😌", "😍", "🥰", "😘", "😗", "😙", "😚", "😋",
|
||||
"😛", "😝", "😜", "🤪", "🤨", "🧐", "🤓", "😎", "🥳", "😏",
|
||||
"👍", "👎", "👏", "🙌", "🤝", "🙏", "💪", "✨", "🎉", "🎊",
|
||||
"❤️", "💙", "💚", "💛", "🧡", "💜", "🖤", "🤍", "💯", "🔥",
|
||||
"😀",
|
||||
"😃",
|
||||
"😄",
|
||||
"😁",
|
||||
"😅",
|
||||
"😂",
|
||||
"🤣",
|
||||
"😊",
|
||||
"😇",
|
||||
"🙂",
|
||||
"🙃",
|
||||
"😉",
|
||||
"😌",
|
||||
"😍",
|
||||
"🥰",
|
||||
"😘",
|
||||
"😗",
|
||||
"😙",
|
||||
"😚",
|
||||
"😋",
|
||||
"😛",
|
||||
"😝",
|
||||
"😜",
|
||||
"🤪",
|
||||
"🤨",
|
||||
"🧐",
|
||||
"🤓",
|
||||
"😎",
|
||||
"🥳",
|
||||
"😏",
|
||||
"👍",
|
||||
"👎",
|
||||
"👏",
|
||||
"🙌",
|
||||
"🤝",
|
||||
"🙏",
|
||||
"💪",
|
||||
"✨",
|
||||
"🎉",
|
||||
"🎊",
|
||||
"❤️",
|
||||
"💙",
|
||||
"💚",
|
||||
"💛",
|
||||
"🧡",
|
||||
"💜",
|
||||
"🖤",
|
||||
"🤍",
|
||||
"💯",
|
||||
"🔥",
|
||||
];
|
||||
|
||||
function adicionarEmoji(emoji: string) {
|
||||
@@ -60,7 +108,11 @@
|
||||
// Obter conversa atual
|
||||
const conversa = $derived((): ConversaComParticipantes | null => {
|
||||
if (!conversas?.data) return null;
|
||||
return (conversas.data as ConversaComParticipantes[]).find((c) => c._id === conversaId) || null;
|
||||
return (
|
||||
(conversas.data as ConversaComParticipantes[]).find(
|
||||
(c) => c._id === conversaId,
|
||||
) || null
|
||||
);
|
||||
});
|
||||
|
||||
// Obter participantes para menções (apenas grupos e salas)
|
||||
@@ -74,10 +126,13 @@
|
||||
const participantesFiltrados = $derived((): ParticipanteInfo[] => {
|
||||
if (!mentionQuery.trim()) return participantesParaMencoes().slice(0, 5);
|
||||
const query = mentionQuery.toLowerCase();
|
||||
return participantesParaMencoes().filter((p) =>
|
||||
p.nome?.toLowerCase().includes(query) ||
|
||||
(p.email && p.email.toLowerCase().includes(query))
|
||||
).slice(0, 5);
|
||||
return participantesParaMencoes()
|
||||
.filter(
|
||||
(p) =>
|
||||
p.nome?.toLowerCase().includes(query) ||
|
||||
(p.email && p.email.toLowerCase().includes(query)),
|
||||
)
|
||||
.slice(0, 5);
|
||||
});
|
||||
|
||||
// Auto-resize do textarea e detectar menções
|
||||
@@ -91,19 +146,19 @@
|
||||
// Detectar menções (@)
|
||||
const cursorPos = target.selectionStart || 0;
|
||||
const textBeforeCursor = mensagem.substring(0, cursorPos);
|
||||
const lastAtIndex = textBeforeCursor.lastIndexOf('@');
|
||||
|
||||
const lastAtIndex = textBeforeCursor.lastIndexOf("@");
|
||||
|
||||
if (lastAtIndex !== -1) {
|
||||
const textAfterAt = textBeforeCursor.substring(lastAtIndex + 1);
|
||||
// Se não há espaço após o @, mostrar dropdown
|
||||
if (!textAfterAt.includes(' ') && !textAfterAt.includes('\n')) {
|
||||
if (!textAfterAt.includes(" ") && !textAfterAt.includes("\n")) {
|
||||
mentionQuery = textAfterAt;
|
||||
mentionStartPos = lastAtIndex;
|
||||
showMentionsDropdown = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
showMentionsDropdown = false;
|
||||
|
||||
// Indicador de digitação (debounce de 1s)
|
||||
@@ -118,9 +173,11 @@
|
||||
}
|
||||
|
||||
function inserirMencao(participante: ParticipanteInfo) {
|
||||
const nome = participante.nome.split(' ')[0]; // Usar primeiro nome
|
||||
const nome = participante.nome.split(" ")[0]; // Usar primeiro nome
|
||||
const antes = mensagem.substring(0, mentionStartPos);
|
||||
const depois = mensagem.substring(textarea.selectionStart || mensagem.length);
|
||||
const depois = mensagem.substring(
|
||||
textarea.selectionStart || mensagem.length,
|
||||
);
|
||||
mensagem = antes + `@${nome} ` + depois;
|
||||
showMentionsDropdown = false;
|
||||
mentionQuery = "";
|
||||
@@ -143,8 +200,9 @@
|
||||
let match;
|
||||
while ((match = mentionRegex.exec(texto)) !== null) {
|
||||
const nomeMencionado = match[1];
|
||||
const participante = participantesParaMencoes().find((p) =>
|
||||
p.nome.split(' ')[0].toLowerCase() === nomeMencionado.toLowerCase()
|
||||
const participante = participantesParaMencoes().find(
|
||||
(p) =>
|
||||
p.nome.split(" ")[0].toLowerCase() === nomeMencionado.toLowerCase(),
|
||||
);
|
||||
if (participante) {
|
||||
mencoesIds.push(participante._id);
|
||||
@@ -168,9 +226,12 @@
|
||||
respostaPara: mensagemRespondendo?.id,
|
||||
mencoes: mencoesIds.length > 0 ? mencoesIds : undefined,
|
||||
});
|
||||
|
||||
console.log("✅ [MessageInput] Mensagem enviada com sucesso! ID:", result);
|
||||
|
||||
|
||||
console.log(
|
||||
"✅ [MessageInput] Mensagem enviada com sucesso! ID:",
|
||||
result,
|
||||
);
|
||||
|
||||
mensagem = "";
|
||||
mensagemRespondendo = null;
|
||||
showMentionsDropdown = false;
|
||||
@@ -201,17 +262,21 @@
|
||||
const handler = (e: Event) => {
|
||||
const customEvent = e as CustomEvent<{ mensagemId: Id<"mensagens"> }>;
|
||||
// Buscar informações da mensagem para exibir preview
|
||||
client.query(api.chat.obterMensagens, { conversaId, limit: 100 }).then((mensagens) => {
|
||||
const msg = (mensagens as MensagemComRemetente[]).find((m) => m._id === customEvent.detail.mensagemId);
|
||||
if (msg) {
|
||||
mensagemRespondendo = {
|
||||
id: msg._id,
|
||||
conteudo: msg.conteudo.substring(0, 100),
|
||||
remetente: msg.remetente?.nome || "Usuário",
|
||||
};
|
||||
textarea?.focus();
|
||||
}
|
||||
});
|
||||
client
|
||||
.query(api.chat.obterMensagens, { conversaId, limit: 100 })
|
||||
.then((mensagens) => {
|
||||
const msg = (mensagens as MensagemComRemetente[]).find(
|
||||
(m) => m._id === customEvent.detail.mensagemId,
|
||||
);
|
||||
if (msg) {
|
||||
mensagemRespondendo = {
|
||||
id: msg._id,
|
||||
conteudo: msg.conteudo.substring(0, 100),
|
||||
remetente: msg.remetente?.nome || "Usuário",
|
||||
};
|
||||
textarea?.focus();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
window.addEventListener("responderMensagem", handler);
|
||||
@@ -236,7 +301,7 @@
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Enter sem Shift = enviar
|
||||
if (e.key === "Enter" && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
@@ -259,7 +324,9 @@
|
||||
uploadingFile = true;
|
||||
|
||||
// 1. Obter upload URL
|
||||
const uploadUrl = await client.mutation(api.chat.uploadArquivoChat, { conversaId });
|
||||
const uploadUrl = await client.mutation(api.chat.uploadArquivoChat, {
|
||||
conversaId,
|
||||
});
|
||||
|
||||
// 2. Upload do arquivo
|
||||
const result = await fetch(uploadUrl, {
|
||||
@@ -275,7 +342,9 @@
|
||||
const { storageId } = await result.json();
|
||||
|
||||
// 3. Enviar mensagem com o arquivo
|
||||
const tipo: "imagem" | "arquivo" = file.type.startsWith("image/") ? "imagem" : "arquivo";
|
||||
const tipo: "imagem" | "arquivo" = file.type.startsWith("image/")
|
||||
? "imagem"
|
||||
: "arquivo";
|
||||
await client.mutation(api.chat.enviarMensagem, {
|
||||
conversaId,
|
||||
conteudo: tipo === "imagem" ? "" : file.name,
|
||||
@@ -306,10 +375,16 @@
|
||||
<div class="p-4">
|
||||
<!-- Preview da mensagem respondendo -->
|
||||
{#if mensagemRespondendo}
|
||||
<div class="mb-2 p-2 bg-base-200 rounded-lg flex items-center justify-between">
|
||||
<div
|
||||
class="mb-2 p-2 bg-base-200 rounded-lg flex items-center justify-between"
|
||||
>
|
||||
<div class="flex-1">
|
||||
<p class="text-xs font-medium text-base-content/70">Respondendo a {mensagemRespondendo.remetente}</p>
|
||||
<p class="text-xs text-base-content/50 truncate">{mensagemRespondendo.conteudo}</p>
|
||||
<p class="text-xs font-medium text-base-content/70">
|
||||
Respondendo a {mensagemRespondendo.remetente}
|
||||
</p>
|
||||
<p class="text-xs text-base-content/50 truncate">
|
||||
{mensagemRespondendo.conteudo}
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
@@ -324,8 +399,8 @@
|
||||
|
||||
<div class="flex items-end gap-2">
|
||||
<!-- Botão de anexar arquivo MODERNO -->
|
||||
<label
|
||||
class="flex items-center justify-center w-10 h-10 rounded-xl transition-all duration-300 group relative overflow-hidden cursor-pointer flex-shrink-0"
|
||||
<label
|
||||
class="flex items-center justify-center w-10 h-10 rounded-xl transition-all duration-300 group relative overflow-hidden cursor-pointer shrink-0"
|
||||
style="background: rgba(102, 126, 234, 0.1); border: 1px solid rgba(102, 126, 234, 0.2);"
|
||||
title="Anexar arquivo"
|
||||
>
|
||||
@@ -336,7 +411,9 @@
|
||||
disabled={uploadingFile || enviando}
|
||||
accept="*/*"
|
||||
/>
|
||||
<div class="absolute inset-0 bg-primary/0 group-hover:bg-primary/10 transition-colors duration-300"></div>
|
||||
<div
|
||||
class="absolute inset-0 bg-primary/0 group-hover:bg-primary/10 transition-colors duration-300"
|
||||
></div>
|
||||
{#if uploadingFile}
|
||||
<span class="loading loading-spinner loading-sm relative z-10"></span>
|
||||
{:else}
|
||||
@@ -349,7 +426,7 @@
|
||||
</label>
|
||||
|
||||
<!-- Botão de EMOJI MODERNO -->
|
||||
<div class="relative flex-shrink-0">
|
||||
<div class="relative shrink-0">
|
||||
<button
|
||||
type="button"
|
||||
class="flex items-center justify-center w-10 h-10 rounded-xl transition-all duration-300 group relative overflow-hidden"
|
||||
@@ -359,7 +436,9 @@
|
||||
aria-label="Adicionar emoji"
|
||||
title="Adicionar emoji"
|
||||
>
|
||||
<div class="absolute inset-0 bg-warning/0 group-hover:bg-warning/10 transition-colors duration-300"></div>
|
||||
<div
|
||||
class="absolute inset-0 bg-warning/0 group-hover:bg-warning/10 transition-colors duration-300"
|
||||
></div>
|
||||
<Smile
|
||||
class="w-5 h-5 text-warning relative z-10 group-hover:scale-110 transition-transform"
|
||||
strokeWidth={2}
|
||||
@@ -368,7 +447,7 @@
|
||||
|
||||
<!-- Picker de Emojis -->
|
||||
{#if showEmojiPicker}
|
||||
<div
|
||||
<div
|
||||
class="absolute bottom-full left-0 mb-2 p-3 bg-base-100 rounded-xl shadow-2xl border border-base-300 z-50"
|
||||
style="width: 280px; max-height: 200px; overflow-y-auto;"
|
||||
>
|
||||
@@ -399,26 +478,38 @@
|
||||
rows="1"
|
||||
disabled={enviando || uploadingFile}
|
||||
></textarea>
|
||||
|
||||
|
||||
<!-- Dropdown de Menções -->
|
||||
{#if showMentionsDropdown && participantesFiltrados().length > 0 && (conversa()?.tipo === "grupo" || conversa()?.tipo === "sala_reuniao")}
|
||||
<div class="absolute bottom-full left-0 mb-2 bg-base-100 rounded-lg shadow-xl border border-base-300 z-50 w-64 max-h-48 overflow-y-auto">
|
||||
<div
|
||||
class="absolute bottom-full left-0 mb-2 bg-base-100 rounded-lg shadow-xl border border-base-300 z-50 w-64 max-h-48 overflow-y-auto"
|
||||
>
|
||||
{#each participantesFiltrados() as participante (participante._id)}
|
||||
<button
|
||||
type="button"
|
||||
class="w-full text-left px-4 py-2 hover:bg-base-200 transition-colors flex items-center gap-2"
|
||||
onclick={() => inserirMencao(participante)}
|
||||
>
|
||||
<div class="w-8 h-8 rounded-full bg-primary/20 flex items-center justify-center overflow-hidden">
|
||||
<div
|
||||
class="w-8 h-8 rounded-full bg-primary/20 flex items-center justify-center overflow-hidden"
|
||||
>
|
||||
{#if participante.fotoPerfilUrl}
|
||||
<img src={participante.fotoPerfilUrl} alt={participante.nome} class="w-full h-full object-cover" />
|
||||
<img
|
||||
src={participante.fotoPerfilUrl}
|
||||
alt={participante.nome}
|
||||
class="w-full h-full object-cover"
|
||||
/>
|
||||
{:else}
|
||||
<span class="text-xs font-semibold">{participante.nome.charAt(0).toUpperCase()}</span>
|
||||
<span class="text-xs font-semibold"
|
||||
>{participante.nome.charAt(0).toUpperCase()}</span
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="text-sm font-medium truncate">{participante.nome}</p>
|
||||
<p class="text-xs text-base-content/60 truncate">@{participante.nome.split(' ')[0]}</p>
|
||||
<p class="text-xs text-base-content/60 truncate">
|
||||
@{participante.nome.split(" ")[0]}
|
||||
</p>
|
||||
</div>
|
||||
</button>
|
||||
{/each}
|
||||
@@ -429,15 +520,19 @@
|
||||
<!-- Botão de enviar MODERNO -->
|
||||
<button
|
||||
type="button"
|
||||
class="flex items-center justify-center w-12 h-12 rounded-xl transition-all duration-300 group relative overflow-hidden flex-shrink-0 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
class="flex items-center justify-center w-12 h-12 rounded-xl transition-all duration-300 group relative overflow-hidden shrink-0 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); box-shadow: 0 8px 24px -4px rgba(102, 126, 234, 0.4);"
|
||||
onclick={handleEnviar}
|
||||
disabled={!mensagem.trim() || enviando || uploadingFile}
|
||||
aria-label="Enviar"
|
||||
>
|
||||
<div class="absolute inset-0 bg-white/0 group-hover:bg-white/10 transition-colors duration-300"></div>
|
||||
<div
|
||||
class="absolute inset-0 bg-white/0 group-hover:bg-white/10 transition-colors duration-300"
|
||||
></div>
|
||||
{#if enviando}
|
||||
<span class="loading loading-spinner loading-sm relative z-10 text-white"></span>
|
||||
<span
|
||||
class="loading loading-spinner loading-sm relative z-10 text-white"
|
||||
></span>
|
||||
{:else}
|
||||
<!-- Ícone de avião de papel moderno -->
|
||||
<Send
|
||||
@@ -452,4 +547,3 @@
|
||||
💡 Enter para enviar • Shift+Enter para quebrar linha • 😊 Clique no emoji
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user