feat: implement advanced access control system with user blocking, rate limiting, and enhanced login security; update UI components for improved user experience and documentation

This commit is contained in:
2025-10-29 09:07:37 -03:00
parent d1715f358a
commit 6b14059fde
33 changed files with 6450 additions and 1202 deletions

View File

@@ -18,10 +18,10 @@
let searchQuery = $state("");
const usuariosFiltrados = $derived.by(() => {
if (!usuarios || !Array.isArray(usuarios) || !meuPerfil) return [];
if (!usuarios?.data || !Array.isArray(usuarios.data) || !meuPerfil?.data) return [];
// Filtrar o próprio usuário da lista
let listaFiltrada = usuarios.filter((u: any) => u._id !== meuPerfil._id);
let listaFiltrada = usuarios.data.filter((u: any) => u._id !== meuPerfil.data._id);
// Aplicar busca por nome/email/matrícula
if (searchQuery.trim()) {
@@ -119,7 +119,7 @@
<!-- Lista de usuários -->
<div class="flex-1 overflow-y-auto">
{#if usuarios && usuariosFiltrados.length > 0}
{#if usuarios?.data && usuariosFiltrados.length > 0}
{#each usuariosFiltrados as usuario (usuario._id)}
<button
type="button"
@@ -163,7 +163,7 @@
</div>
</button>
{/each}
{:else if !usuarios}
{:else if !usuarios?.data}
<!-- Loading -->
<div class="flex items-center justify-center h-full">
<span class="loading loading-spinner loading-lg"></span>

View File

@@ -22,15 +22,20 @@
// Sincronizar com stores
$effect(() => {
isOpen = $chatAberto;
console.log("ChatWidget - isOpen:", isOpen);
});
$effect(() => {
isMinimized = $chatMinimizado;
console.log("ChatWidget - isMinimized:", isMinimized);
});
$effect(() => {
activeConversation = $conversaAtiva;
});
// Debug inicial
console.log("ChatWidget montado - isOpen:", isOpen, "isMinimized:", isMinimized);
function handleToggle() {
if (isOpen && !isMinimized) {
@@ -57,30 +62,36 @@
{#if !isOpen || isMinimized}
<button
type="button"
class="fixed bottom-6 right-6 btn btn-circle btn-primary btn-lg shadow-2xl z-50 hover:scale-110 transition-transform"
class="fixed btn btn-circle btn-lg shadow-2xl hover:shadow-primary/40 hover:scale-110 transition-all duration-500 group relative border-0 bg-gradient-to-br from-primary via-primary to-primary/80"
style="z-index: 99999 !important; width: 4.5rem; height: 4.5rem; bottom: 1.5rem !important; right: 1.5rem !important; position: fixed !important;"
onclick={handleToggle}
aria-label="Abrir chat"
>
<!-- Ícone de chat -->
<!-- Anel pulsante interno -->
<div class="absolute inset-0 rounded-full bg-white/10 group-hover:scale-95 transition-transform duration-500"></div>
<!-- Ícone de chat premium -->
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke-width="2"
stroke="currentColor"
class="w-7 h-7"
class="w-9 h-9 relative z-10 text-white group-hover:scale-110 transition-all duration-500"
style="filter: drop-shadow(0 2px 8px rgba(0,0,0,0.3));"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M8.625 12a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0H8.25m4.125 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0H12m4.125 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0h-.375M21 12c0 4.556-4.03 8.25-9 8.25a9.764 9.764 0 0 1-2.555-.337A5.972 5.972 0 0 1 5.41 20.97a5.969 5.969 0 0 1-.474-.065 4.48 4.48 0 0 0 .978-2.025c.09-.457-.133-.901-.467-1.226C3.93 16.178 3 14.189 3 12c0-4.556 4.03-8.25 9-8.25s9 3.694 9 8.25Z"
d="M8.625 9.75a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0H8.25m4.125 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0H12m4.125 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0h-.375m-13.5 3.01c0 1.6 1.123 2.994 2.707 3.227 1.087.16 2.185.283 3.293.369V21l4.184-4.183a1.14 1.14 0 0 1 .778-.332 48.294 48.294 0 0 0 5.83-.498c1.585-.233 2.708-1.626 2.708-3.228V6.741c0-1.602-1.123-2.995-2.707-3.228A48.394 48.394 0 0 0 12 3c-2.392 0-4.744.175-7.043.513C3.373 3.746 2.25 5.14 2.25 6.741v6.018Z"
/>
</svg>
<!-- Badge de contador -->
<!-- Badge premium com animação -->
{#if count && count > 0}
<span
class="absolute -top-1 -right-1 flex h-6 w-6 items-center justify-center rounded-full bg-error text-error-content text-xs font-bold"
class="absolute -top-1.5 -right-1.5 flex h-7 w-7 items-center justify-center rounded-full bg-gradient-to-br from-red-500 via-error to-red-600 text-white text-xs font-black shadow-2xl ring-4 ring-white z-20"
style="animation: badge-pulse 1.5s cubic-bezier(0.4, 0, 0.6, 1) infinite;"
>
{count > 9 ? "9+" : count}
</span>
@@ -91,39 +102,44 @@
<!-- Janela do Chat -->
{#if isOpen && !isMinimized}
<div
class="fixed bottom-6 right-6 z-50 flex flex-col bg-base-100 rounded-2xl shadow-2xl border border-base-300 overflow-hidden
class="fixed flex flex-col bg-base-100 rounded-2xl shadow-2xl border border-base-300 overflow-hidden
w-[400px] h-[600px] max-w-[calc(100vw-3rem)] max-h-[calc(100vh-3rem)]
md:w-[400px] md:h-[600px]
sm:w-full sm:h-full sm:bottom-0 sm:right-0 sm:rounded-none sm:max-w-full sm:max-h-full"
style="animation: slideIn 0.3s ease-out;"
style="z-index: 99999 !important; animation: slideIn 0.3s ease-out; bottom: 1.5rem !important; right: 1.5rem !important; position: fixed !important;"
>
<!-- Header -->
<!-- Header Premium -->
<div
class="flex items-center justify-between px-4 py-3 bg-primary text-primary-content border-b border-primary-focus"
class="flex items-center justify-between px-5 py-4 bg-gradient-to-r from-primary via-primary to-primary/90 text-white border-b border-white/10 shadow-lg"
>
<h2 class="text-lg font-semibold flex items-center gap-2">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-6 h-6"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M8.625 12a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0H8.25m4.125 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0H12m4.125 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0h-.375M21 12c0 4.556-4.03 8.25-9 8.25a9.764 9.764 0 0 1-2.555-.337A5.972 5.972 0 0 1 5.41 20.97a5.969 5.969 0 0 1-.474-.065 4.48 4.48 0 0 0 .978-2.025c.09-.457-.133-.901-.467-1.226C3.93 16.178 3 14.189 3 12c0-4.556 4.03-8.25 9-8.25s9 3.694 9 8.25Z"
/>
</svg>
Chat
<h2 class="text-lg font-bold flex items-center gap-3">
<!-- Ícone premium do chat -->
<div class="relative">
<div class="absolute inset-0 bg-white/20 rounded-lg blur-md"></div>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="2.5"
stroke="currentColor"
class="w-7 h-7 relative z-10"
style="filter: drop-shadow(0 2px 4px rgba(0,0,0,0.2));"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M20.25 8.511c.884.284 1.5 1.128 1.5 2.097v4.286c0 1.136-.847 2.1-1.98 2.193-.34.027-.68.052-1.02.072v3.091l-3-3c-1.354 0-2.694-.055-4.02-.163a2.115 2.115 0 0 1-.825-.242m9.345-8.334a2.126 2.126 0 0 0-.476-.095 48.64 48.64 0 0 0-8.048 0c-1.131.094-1.976 1.057-1.976 2.192v4.286c0 .837.46 1.58 1.155 1.951m9.345-8.334V6.637c0-1.621-1.152-3.026-2.76-3.235A48.455 48.455 0 0 0 11.25 3c-2.115 0-4.198.137-6.24.402-1.608.209-2.76 1.614-2.76 3.235v6.226c0 1.621 1.152 3.026 2.76 3.235.577.075 1.157.14 1.74.194V21l4.155-4.155"
/>
</svg>
</div>
<span class="tracking-wide" style="text-shadow: 0 2px 4px rgba(0,0,0,0.2);">Mensagens</span>
</h2>
<div class="flex items-center gap-1">
<!-- Botão minimizar -->
<!-- Botão minimizar premium -->
<button
type="button"
class="btn btn-ghost btn-sm btn-circle"
class="btn btn-ghost btn-sm btn-circle hover:bg-white/20 transition-all duration-300 group"
onclick={handleMinimize}
aria-label="Minimizar"
>
@@ -131,18 +147,18 @@
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke-width="2.5"
stroke="currentColor"
class="w-5 h-5"
class="w-5 h-5 group-hover:scale-110 transition-transform"
>
<path stroke-linecap="round" stroke-linejoin="round" d="M5 12h14" />
</svg>
</button>
<!-- Botão fechar -->
<!-- Botão fechar premium -->
<button
type="button"
class="btn btn-ghost btn-sm btn-circle"
class="btn btn-ghost btn-sm btn-circle hover:bg-error/20 hover:text-error-content transition-all duration-300 group"
onclick={handleClose}
aria-label="Fechar"
>
@@ -150,9 +166,9 @@
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke-width="2.5"
stroke="currentColor"
class="w-5 h-5"
class="w-5 h-5 group-hover:scale-110 group-hover:rotate-90 transition-all duration-300"
>
<path
stroke-linecap="round"
@@ -176,6 +192,17 @@
{/if}
<style>
@keyframes badge-pulse {
0%, 100% {
transform: scale(1);
opacity: 1;
}
50% {
transform: scale(1.15);
opacity: 0.9;
}
}
@keyframes slideIn {
from {
opacity: 0;

View File

@@ -59,22 +59,39 @@
});
</script>
<style>
@keyframes badge-bounce {
0%, 100% {
transform: scale(1);
}
50% {
transform: scale(1.1);
}
}
</style>
<div class="dropdown dropdown-end notification-bell">
<button
type="button"
tabindex="0"
class="btn btn-ghost btn-circle relative"
class="btn btn-ghost btn-circle relative hover:bg-gradient-to-br hover:from-primary/10 hover:to-primary/5 transition-all duration-500 group"
onclick={toggleDropdown}
aria-label="Notificações"
>
<!-- Ícone do sino -->
<!-- Glow effect -->
{#if count && count > 0}
<div class="absolute inset-0 rounded-full bg-error/20 blur-xl animate-pulse"></div>
{/if}
<!-- Ícone do sino premium -->
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke-width="2.5"
stroke="currentColor"
class="w-6 h-6"
class="w-7 h-7 relative z-10 transition-all duration-500 group-hover:scale-110 group-hover:-rotate-12 {count && count > 0 ? 'text-error drop-shadow-[0_0_8px_rgba(239,68,68,0.5)]' : 'text-primary'}"
style="filter: {count && count > 0 ? 'drop-shadow(0 0 4px rgba(239,68,68,0.4))' : 'drop-shadow(0 2px 4px rgba(0,0,0,0.1))'}"
>
<path
stroke-linecap="round"
@@ -83,10 +100,11 @@
/>
</svg>
<!-- Badge de contador -->
<!-- Badge premium com gradiente -->
{#if count && count > 0}
<span
class="absolute top-1 right-1 flex h-5 w-5 items-center justify-center rounded-full bg-error text-error-content text-xs font-bold"
class="absolute -top-1 -right-1 flex h-6 w-6 items-center justify-center rounded-full bg-gradient-to-br from-red-500 via-error to-red-600 text-white text-[10px] font-black shadow-xl ring-2 ring-white z-20"
style="animation: badge-bounce 2s ease-in-out infinite;"
>
{count > 9 ? "9+" : count}
</span>