372 lines
14 KiB
Svelte
372 lines
14 KiB
Svelte
<script lang="ts">
|
|
import { useConvexClient } from "convex-svelte";
|
|
import { api } from "@sgse-app/backend/convex/_generated/api";
|
|
import { authStore } from "$lib/stores/auth.svelte";
|
|
import { goto } from "$app/navigation";
|
|
import { onMount } from "svelte";
|
|
|
|
const convex = useConvexClient();
|
|
|
|
let senhaAtual = $state("");
|
|
let novaSenha = $state("");
|
|
let confirmarSenha = $state("");
|
|
let carregando = $state(false);
|
|
let notice = $state<{ type: "success" | "error"; message: string } | null>(null);
|
|
let mostrarSenhaAtual = $state(false);
|
|
let mostrarNovaSenha = $state(false);
|
|
let mostrarConfirmarSenha = $state(false);
|
|
|
|
onMount(() => {
|
|
if (!authStore.autenticado) {
|
|
goto("/");
|
|
}
|
|
});
|
|
|
|
function validarSenha(senha: string): { valido: boolean; erros: string[] } {
|
|
const erros: string[] = [];
|
|
|
|
if (senha.length < 8) {
|
|
erros.push("A senha deve ter no mínimo 8 caracteres");
|
|
}
|
|
if (!/[A-Z]/.test(senha)) {
|
|
erros.push("A senha deve conter pelo menos uma letra maiúscula");
|
|
}
|
|
if (!/[a-z]/.test(senha)) {
|
|
erros.push("A senha deve conter pelo menos uma letra minúscula");
|
|
}
|
|
if (!/[0-9]/.test(senha)) {
|
|
erros.push("A senha deve conter pelo menos um número");
|
|
}
|
|
if (!/[!@#$%^&*(),.?":{}|<>]/.test(senha)) {
|
|
erros.push("A senha deve conter pelo menos um caractere especial");
|
|
}
|
|
|
|
return {
|
|
valido: erros.length === 0,
|
|
erros,
|
|
};
|
|
}
|
|
|
|
async function handleSubmit(e: Event) {
|
|
e.preventDefault();
|
|
notice = null;
|
|
|
|
// Validações
|
|
if (!senhaAtual || !novaSenha || !confirmarSenha) {
|
|
notice = {
|
|
type: "error",
|
|
message: "Todos os campos são obrigatórios",
|
|
};
|
|
return;
|
|
}
|
|
|
|
if (novaSenha !== confirmarSenha) {
|
|
notice = {
|
|
type: "error",
|
|
message: "A nova senha e a confirmação não coincidem",
|
|
};
|
|
return;
|
|
}
|
|
|
|
if (senhaAtual === novaSenha) {
|
|
notice = {
|
|
type: "error",
|
|
message: "A nova senha deve ser diferente da senha atual",
|
|
};
|
|
return;
|
|
}
|
|
|
|
const validacao = validarSenha(novaSenha);
|
|
if (!validacao.valido) {
|
|
notice = {
|
|
type: "error",
|
|
message: validacao.erros.join(". "),
|
|
};
|
|
return;
|
|
}
|
|
|
|
carregando = true;
|
|
|
|
try {
|
|
if (!authStore.token) {
|
|
throw new Error("Token não encontrado");
|
|
}
|
|
|
|
const resultado = await convex.mutation(api.autenticacao.alterarSenha, {
|
|
token: authStore.token,
|
|
senhaAntiga: senhaAtual,
|
|
novaSenha: novaSenha,
|
|
});
|
|
|
|
if (resultado.sucesso) {
|
|
notice = {
|
|
type: "success",
|
|
message: "Senha alterada com sucesso! Redirecionando...",
|
|
};
|
|
|
|
// Limpar campos
|
|
senhaAtual = "";
|
|
novaSenha = "";
|
|
confirmarSenha = "";
|
|
|
|
// Redirecionar após 2 segundos
|
|
setTimeout(() => {
|
|
goto("/");
|
|
}, 2000);
|
|
} else {
|
|
notice = {
|
|
type: "error",
|
|
message: resultado.erro || "Erro ao alterar senha",
|
|
};
|
|
}
|
|
} catch (error: any) {
|
|
notice = {
|
|
type: "error",
|
|
message: error.message || "Erro ao conectar com o servidor",
|
|
};
|
|
} finally {
|
|
carregando = false;
|
|
}
|
|
}
|
|
|
|
function cancelar() {
|
|
goto("/");
|
|
}
|
|
</script>
|
|
|
|
<main class="container mx-auto px-4 py-8 max-w-2xl">
|
|
<!-- Header -->
|
|
<div class="mb-8">
|
|
<div class="flex items-center gap-3 mb-2">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-10 w-10 text-primary" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z" />
|
|
</svg>
|
|
<h1 class="text-4xl font-bold text-primary">Alterar Senha</h1>
|
|
</div>
|
|
<p class="text-base-content/70 text-lg">
|
|
Atualize sua senha de acesso ao sistema
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Breadcrumbs -->
|
|
<div class="text-sm breadcrumbs mb-6">
|
|
<ul>
|
|
<li><a href="/">Dashboard</a></li>
|
|
<li>Alterar Senha</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<!-- Alertas -->
|
|
{#if notice}
|
|
<div class="alert {notice.type === 'success' ? 'alert-success' : 'alert-error'} mb-6 shadow-lg">
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
class="stroke-current shrink-0 h-6 w-6"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
{#if notice.type === "success"}
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
|
|
/>
|
|
{:else}
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z"
|
|
/>
|
|
{/if}
|
|
</svg>
|
|
<span>{notice.message}</span>
|
|
</div>
|
|
{/if}
|
|
|
|
<!-- Formulário -->
|
|
<div class="card bg-base-100 shadow-xl border border-base-300">
|
|
<div class="card-body">
|
|
<form onsubmit={handleSubmit} class="space-y-6">
|
|
<!-- Senha Atual -->
|
|
<div class="form-control">
|
|
<label class="label" for="senha-atual">
|
|
<span class="label-text font-semibold">Senha Atual</span>
|
|
<span class="label-text-alt text-error">*</span>
|
|
</label>
|
|
<div class="relative">
|
|
<input
|
|
id="senha-atual"
|
|
type={mostrarSenhaAtual ? "text" : "password"}
|
|
placeholder="Digite sua senha atual"
|
|
class="input input-bordered input-primary w-full pr-12"
|
|
bind:value={senhaAtual}
|
|
required
|
|
disabled={carregando}
|
|
/>
|
|
<button
|
|
type="button"
|
|
class="absolute right-3 top-1/2 -translate-y-1/2 btn btn-ghost btn-sm btn-circle"
|
|
onclick={() => (mostrarSenhaAtual = !mostrarSenhaAtual)}
|
|
>
|
|
{#if mostrarSenhaAtual}
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21" />
|
|
</svg>
|
|
{:else}
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
|
|
</svg>
|
|
{/if}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Nova Senha -->
|
|
<div class="form-control">
|
|
<label class="label" for="nova-senha">
|
|
<span class="label-text font-semibold">Nova Senha</span>
|
|
<span class="label-text-alt text-error">*</span>
|
|
</label>
|
|
<div class="relative">
|
|
<input
|
|
id="nova-senha"
|
|
type={mostrarNovaSenha ? "text" : "password"}
|
|
placeholder="Digite sua nova senha"
|
|
class="input input-bordered input-primary w-full pr-12"
|
|
bind:value={novaSenha}
|
|
required
|
|
disabled={carregando}
|
|
/>
|
|
<button
|
|
type="button"
|
|
class="absolute right-3 top-1/2 -translate-y-1/2 btn btn-ghost btn-sm btn-circle"
|
|
onclick={() => (mostrarNovaSenha = !mostrarNovaSenha)}
|
|
>
|
|
{#if mostrarNovaSenha}
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21" />
|
|
</svg>
|
|
{:else}
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
|
|
</svg>
|
|
{/if}
|
|
</button>
|
|
</div>
|
|
<label class="label">
|
|
<span class="label-text-alt text-base-content/60">
|
|
Mínimo 8 caracteres, com letras maiúsculas, minúsculas, números e caracteres especiais
|
|
</span>
|
|
</label>
|
|
</div>
|
|
|
|
<!-- Confirmar Senha -->
|
|
<div class="form-control">
|
|
<label class="label" for="confirmar-senha">
|
|
<span class="label-text font-semibold">Confirmar Nova Senha</span>
|
|
<span class="label-text-alt text-error">*</span>
|
|
</label>
|
|
<div class="relative">
|
|
<input
|
|
id="confirmar-senha"
|
|
type={mostrarConfirmarSenha ? "text" : "password"}
|
|
placeholder="Digite novamente sua nova senha"
|
|
class="input input-bordered input-primary w-full pr-12"
|
|
bind:value={confirmarSenha}
|
|
required
|
|
disabled={carregando}
|
|
/>
|
|
<button
|
|
type="button"
|
|
class="absolute right-3 top-1/2 -translate-y-1/2 btn btn-ghost btn-sm btn-circle"
|
|
onclick={() => (mostrarConfirmarSenha = !mostrarConfirmarSenha)}
|
|
>
|
|
{#if mostrarConfirmarSenha}
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21" />
|
|
</svg>
|
|
{:else}
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
|
|
</svg>
|
|
{/if}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Requisitos de Senha -->
|
|
<div class="alert alert-info">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="stroke-current shrink-0 h-6 w-6" fill="none" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
</svg>
|
|
<div>
|
|
<h3 class="font-bold">Requisitos de Senha:</h3>
|
|
<ul class="text-sm list-disc list-inside mt-2 space-y-1">
|
|
<li>Mínimo de 8 caracteres</li>
|
|
<li>Pelo menos uma letra maiúscula (A-Z)</li>
|
|
<li>Pelo menos uma letra minúscula (a-z)</li>
|
|
<li>Pelo menos um número (0-9)</li>
|
|
<li>Pelo menos um caractere especial (!@#$%^&*...)</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Botões -->
|
|
<div class="flex gap-4 justify-end mt-8">
|
|
<button
|
|
type="button"
|
|
class="btn btn-ghost"
|
|
onclick={cancelar}
|
|
disabled={carregando}
|
|
>
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
|
</svg>
|
|
Cancelar
|
|
</button>
|
|
<button
|
|
type="submit"
|
|
class="btn btn-primary"
|
|
disabled={carregando}
|
|
>
|
|
{#if carregando}
|
|
<span class="loading loading-spinner loading-sm"></span>
|
|
Alterando...
|
|
{:else}
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
|
|
</svg>
|
|
Alterar Senha
|
|
{/if}
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Dicas de Segurança -->
|
|
<div class="mt-6 card bg-base-200 shadow-lg">
|
|
<div class="card-body">
|
|
<h3 class="card-title text-lg">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-warning" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
|
|
</svg>
|
|
Dicas de Segurança
|
|
</h3>
|
|
<ul class="text-sm space-y-2 text-base-content/70">
|
|
<li>✅ Nunca compartilhe sua senha com ninguém</li>
|
|
<li>✅ Use uma senha única para cada sistema</li>
|
|
<li>✅ Altere sua senha regularmente</li>
|
|
<li>✅ Não use informações pessoais óbvias (nome, data de nascimento, etc.)</li>
|
|
<li>✅ Considere usar um gerenciador de senhas</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
|