feat: add password reset functionality for users, including modal interface for generating temporary passwords and copying to clipboard; enhance backend mutation for secure password management and email notifications

This commit is contained in:
2025-12-09 07:41:19 -03:00
parent 4110b12724
commit 2172d9a937
2 changed files with 275 additions and 39 deletions

View File

@@ -18,7 +18,9 @@
Check,
UserPlus,
Search,
MoreVertical
MoreVertical,
Copy,
CheckCircle2
} from 'lucide-svelte';
type AvisoUsuario = {
@@ -189,6 +191,9 @@
let buscaFuncionario = $state('');
let modalExcluirAberto = $state(false);
let modalResetarSenhaAberto = $state(false);
let novaSenhaGerada = $state<string | null>(null);
let senhaCopiada = $state(false);
let processando = $state(false);
let mensagem = $state<Mensagem | null>(null);
@@ -525,6 +530,68 @@
usuarioSelecionado = null;
}
function abrirModalResetarSenha(usuario: Usuario) {
usuarioSelecionado = usuario;
novaSenhaGerada = null;
modalResetarSenhaAberto = true;
}
function fecharModalResetarSenha() {
modalResetarSenhaAberto = false;
novaSenhaGerada = null;
senhaCopiada = false;
usuarioSelecionado = null;
}
async function copiarSenha() {
if (!novaSenhaGerada) return;
try {
await navigator.clipboard.writeText(novaSenhaGerada);
senhaCopiada = true;
setTimeout(() => {
senhaCopiada = false;
}, 2000);
} catch (error) {
console.error('Erro ao copiar senha:', error);
}
}
async function resetarSenha() {
if (!usuarioSelecionado || !currentUser?.data) {
console.error('Erro: usuarioSelecionado ou currentUser não definido');
mostrarMensagem('error', 'Erro: dados do usuário não encontrados');
return;
}
try {
processando = true;
console.log('Iniciando reset de senha para:', usuarioSelecionado._id);
const resultado = await client.mutation(api.usuarios.resetarSenhaUsuario, {
usuarioId: usuarioSelecionado._id,
resetadoPorId: currentUser.data._id
});
console.log('Resultado do reset:', resultado);
if (resultado && resultado.sucesso) {
novaSenhaGerada = resultado.senhaTemporaria;
mostrarMensagem('success', 'Senha resetada com sucesso! Email enviado ao usuário.');
} else {
const erroMsg = resultado?.erro || 'Erro ao resetar senha';
console.error('Erro no reset:', erroMsg);
mostrarMensagem('error', erroMsg);
}
} catch (error) {
console.error('Erro ao resetar senha:', error);
const errorMessage = error instanceof Error ? error.message : 'Erro desconhecido ao resetar senha';
mostrarMensagem('error', errorMessage);
} finally {
processando = false;
}
}
async function excluirUsuario() {
if (!usuarioSelecionado) {
return;
@@ -912,6 +979,17 @@
{usuario.funcionario ? 'Alterar Funcionário' : 'Associar Funcionário'}
</button>
</li>
<li>
<button
type="button"
onclick={() => abrirModalResetarSenha(usuario)}
disabled={processando}
class="text-warning"
>
Resetar Senha
</button>
</li>
<div class="divider my-0"></div>
<li>
{#if usuario.bloqueado}
<button
@@ -1133,4 +1211,103 @@
</div>
</div>
{/if}
<!-- Modal Resetar Senha -->
{#if modalResetarSenhaAberto && usuarioSelecionado}
<div class="modal modal-open">
<div class="modal-box">
<h3 class="mb-4 text-lg font-bold">Resetar Senha do Usuário</h3>
<div class="mb-4">
<p class="text-base-content/80 mb-2">
<strong>Usuário:</strong>
{usuarioSelecionado.nome} ({usuarioSelecionado.matricula})
</p>
<p class="text-base-content/70 mb-4 text-sm">
Uma nova senha temporária será gerada e enviada por email ao usuário.
</p>
{#if novaSenhaGerada}
<div class="alert alert-success mb-4">
<CheckCircle class="h-6 w-6 shrink-0 stroke-current" strokeWidth={2} />
<div class="flex-1 w-full">
<p class="font-semibold text-base">Senha resetada com sucesso!</p>
<p class="mt-3 text-sm font-medium">
<strong>Nova senha temporária:</strong>
</p>
<div class="bg-base-200 border-base-300 mt-2 flex items-center justify-between gap-2 rounded-lg border p-4">
<code class="text-xl font-mono font-bold select-all">{novaSenhaGerada}</code>
<button
type="button"
class="btn btn-sm btn-ghost shrink-0"
onclick={copiarSenha}
aria-label="Copiar senha"
title="Copiar senha"
>
{#if senhaCopiada}
<CheckCircle2 class="h-5 w-5 text-success" strokeWidth={2} />
{:else}
<Copy class="h-5 w-5" strokeWidth={2} />
{/if}
</button>
</div>
<p class="mt-3 text-xs text-base-content/70">
✓ Esta senha foi enviada por email para <strong>{usuarioSelecionado.email}</strong>
</p>
<p class="mt-1 text-xs text-base-content/60">
O usuário precisará alterar a senha no próximo login.
</p>
</div>
</div>
{:else}
<div class="alert alert-warning">
<AlertTriangle class="h-6 w-6 shrink-0 stroke-current" strokeWidth={2} />
<span>
O usuário precisará alterar a senha no próximo login. Todas as sessões ativas serão encerradas.
</span>
</div>
{/if}
</div>
<div class="modal-action">
{#if novaSenhaGerada}
<button type="button" class="btn btn-primary" onclick={fecharModalResetarSenha}>
Fechar
</button>
{:else}
<button
type="button"
class="btn"
onclick={fecharModalResetarSenha}
disabled={processando}
>
Cancelar
</button>
<button
type="button"
class="btn btn-warning"
onclick={resetarSenha}
disabled={processando}
>
{#if processando}
<span class="loading loading-spinner loading-sm"></span>
{/if}
Resetar Senha
</button>
{/if}
</div>
</div>
<div class="modal-backdrop">
<button
type="button"
onclick={fecharModalResetarSenha}
onkeydown={(e) => e.key === 'Escape' && fecharModalResetarSenha()}
aria-label="Fechar modal"
class="sr-only"
>
Fechar
</button>
</div>
</div>
{/if}
</ProtectedRoute>