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

@@ -0,0 +1,308 @@
<script lang="ts">
import { useQuery, useConvexClient } from "convex-svelte";
import { api } from "@sgse-app/backend/convex/_generated/api";
import { authStore } from "$lib/stores/auth.svelte";
import UserStatusBadge from "$lib/components/ti/UserStatusBadge.svelte";
import type { Id } from "@sgse-app/backend/convex/_generated/dataModel";
import { goto } from "$app/navigation";
const client = useConvexClient();
const usuarios = useQuery(api.usuarios.listar, {});
let filtroNome = $state("");
let filtroStatus = $state<"todos" | "ativo" | "bloqueado" | "inativo">("todos");
let usuarioSelecionado = $state<any>(null);
let modalAberto = $state(false);
let modalAcao = $state<"bloquear" | "desbloquear" | "reset">("bloquear");
let motivo = $state("");
let processando = $state(false);
// Usuários filtrados
const usuariosFiltrados = $derived.by(() => {
if (!usuarios?.data || !Array.isArray(usuarios.data)) return [];
return usuarios.data.filter(u => {
const matchNome = !filtroNome ||
u.nome.toLowerCase().includes(filtroNome.toLowerCase()) ||
u.matricula.includes(filtroNome) ||
u.email?.toLowerCase().includes(filtroNome.toLowerCase());
const matchStatus = filtroStatus === "todos" ||
(filtroStatus === "ativo" && u.ativo && !u.bloqueado) ||
(filtroStatus === "bloqueado" && u.bloqueado) ||
(filtroStatus === "inativo" && !u.ativo);
return matchNome && matchStatus;
});
});
const stats = $derived.by(() => {
if (!usuarios?.data || !Array.isArray(usuarios.data)) return null;
return {
total: usuarios.data.length,
ativos: usuarios.data.filter(u => u.ativo && !u.bloqueado).length,
bloqueados: usuarios.data.filter(u => u.bloqueado).length,
inativos: usuarios.data.filter(u => !u.ativo).length
};
});
function abrirModal(usuario: any, acao: typeof modalAcao) {
usuarioSelecionado = usuario;
modalAcao = acao;
motivo = "";
modalAberto = true;
}
function fecharModal() {
modalAberto = false;
usuarioSelecionado = null;
motivo = "";
}
async function executarAcao() {
if (!usuarioSelecionado) return;
if (!authStore.usuario) {
alert("Usuário não autenticado");
return;
}
processando = true;
try {
if (modalAcao === "bloquear") {
await client.mutation(api.usuarios.bloquearUsuario, {
usuarioId: usuarioSelecionado._id as Id<"usuarios">,
motivo,
bloqueadoPorId: authStore.usuario._id as Id<"usuarios">
});
} else if (modalAcao === "desbloquear") {
await client.mutation(api.usuarios.desbloquearUsuario, {
usuarioId: usuarioSelecionado._id as Id<"usuarios">,
desbloqueadoPorId: authStore.usuario._id as Id<"usuarios">
});
} else if (modalAcao === "reset") {
await client.mutation(api.usuarios.resetarSenhaUsuario, {
usuarioId: usuarioSelecionado._id as Id<"usuarios">,
resetadoPorId: authStore.usuario._id as Id<"usuarios">
});
}
fecharModal();
} catch (error) {
console.error("Erro ao executar ação:", error);
alert("Erro ao executar ação. Veja o console.");
} finally {
processando = false;
}
}
</script>
<div class="container mx-auto px-4 py-6 max-w-7xl">
<!-- Header -->
<div class="flex items-center justify-between mb-6">
<div>
<h1 class="text-3xl font-bold text-base-content">Gestão de Usuários</h1>
<p class="text-base-content/60 mt-1">Gerenciar usuários do sistema</p>
</div>
<a href="/ti/usuarios/criar" class="btn btn-primary">
<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="M12 4v16m8-8H4" />
</svg>
Criar Usuário
</a>
</div>
<!-- Stats -->
{#if stats}
<div class="grid grid-cols-1 md:grid-cols-4 gap-4 mb-6">
<div class="stat bg-base-100 shadow rounded-lg">
<div class="stat-title">Total</div>
<div class="stat-value text-primary">{stats.total}</div>
</div>
<div class="stat bg-base-100 shadow rounded-lg">
<div class="stat-title">Ativos</div>
<div class="stat-value text-success">{stats.ativos}</div>
</div>
<div class="stat bg-base-100 shadow rounded-lg">
<div class="stat-title">Bloqueados</div>
<div class="stat-value text-error">{stats.bloqueados}</div>
</div>
<div class="stat bg-base-100 shadow rounded-lg">
<div class="stat-title">Inativos</div>
<div class="stat-value text-warning">{stats.inativos}</div>
</div>
</div>
{/if}
<!-- Filtros -->
<div class="card bg-base-100 shadow-xl mb-6">
<div class="card-body">
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="form-control">
<label class="label">
<span class="label-text">Buscar por nome, matrícula ou email</span>
</label>
<input
type="text"
bind:value={filtroNome}
placeholder="Digite para buscar..."
class="input input-bordered"
/>
</div>
<div class="form-control">
<label class="label">
<span class="label-text">Filtrar por status</span>
</label>
<select bind:value={filtroStatus} class="select select-bordered">
<option value="todos">Todos</option>
<option value="ativo">Ativos</option>
<option value="bloqueado">Bloqueados</option>
<option value="inativo">Inativos</option>
</select>
</div>
</div>
</div>
</div>
<!-- Tabela -->
<div class="card bg-base-100 shadow-xl">
<div class="card-body">
<h2 class="card-title mb-4">
Usuários ({usuariosFiltrados.length})
</h2>
<div class="overflow-x-auto">
<table class="table table-zebra">
<thead>
<tr>
<th>Matrícula</th>
<th>Nome</th>
<th>Email</th>
<th>Status</th>
<th>Ações</th>
</tr>
</thead>
<tbody>
{#each usuariosFiltrados as usuario}
<tr>
<td class="font-mono">{usuario.matricula}</td>
<td>{usuario.nome}</td>
<td>{usuario.email || "-"}</td>
<td>
<UserStatusBadge ativo={usuario.ativo} bloqueado={usuario.bloqueado} />
</td>
<td>
<div class="flex gap-2">
{#if usuario.bloqueado}
<button
class="btn btn-sm btn-success"
onclick={() => abrirModal(usuario, "desbloquear")}
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 11V7a4 4 0 118 0m-4 8v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2z" />
</svg>
Desbloquear
</button>
{:else}
<button
class="btn btn-sm btn-error"
onclick={() => abrirModal(usuario, "bloquear")}
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" />
</svg>
Bloquear
</button>
{/if}
<button
class="btn btn-sm btn-warning"
onclick={() => abrirModal(usuario, "reset")}
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" 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>
Reset Senha
</button>
</div>
</td>
</tr>
{:else}
<tr>
<td colspan="5" class="text-center py-8 text-base-content/60">
Nenhum usuário encontrado
</td>
</tr>
{/each}
</tbody>
</table>
</div>
</div>
</div>
</div>
<!-- Modal -->
{#if modalAberto}
<div class="modal modal-open">
<div class="modal-box">
<h3 class="font-bold text-lg mb-4">
{modalAcao === "bloquear" ? "Bloquear Usuário" :
modalAcao === "desbloquear" ? "Desbloquear Usuário" :
"Resetar Senha"}
</h3>
<div class="mb-4">
<p class="text-base-content/80">
<strong>Usuário:</strong> {usuarioSelecionado?.nome} ({usuarioSelecionado?.matricula})
</p>
</div>
{#if modalAcao === "bloquear"}
<div class="form-control mb-4">
<label class="label">
<span class="label-text">Motivo do bloqueio *</span>
</label>
<textarea
bind:value={motivo}
class="textarea textarea-bordered"
placeholder="Digite o motivo..."
rows="3"
></textarea>
</div>
{/if}
{#if modalAcao === "reset"}
<div class="alert alert-info mb-4">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" class="stroke-current shrink-0 w-6 h-6">
<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"></path>
</svg>
<span>Uma senha temporária será gerada automaticamente.</span>
</div>
{/if}
<div class="modal-action">
<button
class="btn btn-ghost"
onclick={fecharModal}
disabled={processando}
>
Cancelar
</button>
<button
class="btn btn-primary"
onclick={executarAcao}
disabled={processando || (modalAcao === "bloquear" && !motivo.trim())}
>
{#if processando}
<span class="loading loading-spinner loading-sm"></span>
{/if}
Confirmar
</button>
</div>
</div>
<div class="modal-backdrop" onclick={fecharModal}></div>
</div>
{/if}

View File

@@ -0,0 +1,559 @@
<script lang="ts">
import { useQuery, useConvexClient } from "convex-svelte";
import { api } from "@sgse-app/backend/convex/_generated/api";
import { goto } from "$app/navigation";
import type { Id } from "@sgse-app/backend/convex/_generated/dataModel";
import ProtectedRoute from "$lib/components/ProtectedRoute.svelte";
const client = useConvexClient();
const roles = useQuery(api.roles.listar, {});
const funcionarios = useQuery(api.funcionarios.getAll, {});
// Debug - Remover após teste
$effect(() => {
console.log("=== DEBUG PERFIS ===");
console.log("roles:", roles);
console.log("roles?.data:", roles?.data);
console.log("É array?", Array.isArray(roles?.data));
if (roles?.data) {
console.log("Quantidade de perfis:", roles.data.length);
console.log("Perfis:", roles.data);
}
});
// Estados do formulário
let matricula = $state("");
let nome = $state("");
let email = $state("");
let roleId = $state("");
let funcionarioId = $state("");
let senhaInicial = $state("");
let confirmarSenha = $state("");
let processando = $state(false);
let mensagem = $state<{ tipo: "success" | "error"; texto: string } | null>(null);
function mostrarMensagem(tipo: "success" | "error", texto: string) {
mensagem = { tipo, texto };
setTimeout(() => {
mensagem = null;
}, 5000);
}
async function handleSubmit(e: Event) {
e.preventDefault();
// Validações
const matriculaStr = String(matricula).trim();
if (!matriculaStr || !nome.trim() || !email.trim() || !roleId || !senhaInicial) {
mostrarMensagem("error", "Preencha todos os campos obrigatórios");
return;
}
if (senhaInicial !== confirmarSenha) {
mostrarMensagem("error", "As senhas não conferem");
return;
}
if (senhaInicial.length < 8) {
mostrarMensagem("error", "A senha deve ter no mínimo 8 caracteres");
return;
}
processando = true;
try {
const resultado = await client.mutation(api.usuarios.criar, {
matricula: matriculaStr,
nome: nome.trim(),
email: email.trim(),
roleId: roleId as Id<"roles">,
funcionarioId: funcionarioId ? (funcionarioId as Id<"funcionarios">) : undefined,
senhaInicial: senhaInicial,
});
if (resultado.sucesso) {
if (senhaGerada) {
mostrarMensagem(
"success",
`Usuário criado! SENHA TEMPORÁRIA: ${senhaGerada} - Anote esta senha, ela não será exibida novamente!`
);
setTimeout(() => {
goto("/ti/usuarios");
}, 5000);
} else {
mostrarMensagem("success", "Usuário criado com sucesso!");
setTimeout(() => {
goto("/ti/usuarios");
}, 2000);
}
} else {
mostrarMensagem("error", resultado.erro);
}
} catch (error: any) {
mostrarMensagem("error", error.message || "Erro ao criar usuário");
} finally {
processando = false;
}
}
let senhaGerada = $state("");
let mostrarSenha = $state(false);
// Auto-completar ao selecionar funcionário
$effect(() => {
if (funcionarioId && funcionarios?.data) {
const funcSelecionado = funcionarios.data.find((f: any) => f._id === funcionarioId);
if (funcSelecionado) {
email = funcSelecionado.email || email;
nome = funcSelecionado.nome || nome;
matricula = funcSelecionado.matricula || matricula;
}
}
});
function gerarSenhaAleatoria() {
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789@#$!";
let senha = "";
for (let i = 0; i < 12; i++) {
senha += chars.charAt(Math.floor(Math.random() * chars.length));
}
senhaInicial = senha;
confirmarSenha = senha;
senhaGerada = senha;
mostrarSenha = true;
}
function copiarSenha() {
if (senhaGerada) {
navigator.clipboard.writeText(senhaGerada);
mostrarMensagem("success", "Senha copiada para área de transferência!");
}
}
</script>
<ProtectedRoute allowedRoles={["ti_master", "admin"]} maxLevel={1}>
<div class="container mx-auto px-4 py-6 max-w-4xl">
<!-- Header -->
<div class="mb-8">
<div class="flex items-center justify-between mb-4">
<div class="flex items-center gap-4">
<div class="p-3 bg-primary/10 rounded-xl">
<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="M18 9v3m0 0v3m0-3h3m-3 0h-3m-2-5a4 4 0 11-8 0 4 4 0 018 0zM3 20a6 6 0 0112 0v1H3v-1z"
/>
</svg>
</div>
<div>
<h1 class="text-3xl font-bold text-base-content">Criar Novo Usuário</h1>
<p class="text-base-content/60 mt-1">Cadastre um novo usuário no sistema</p>
</div>
</div>
<a href="/ti/usuarios" class="btn btn-outline btn-primary gap-2">
<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="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z"
/>
</svg>
Voltar para Usuários
</a>
</div>
</div>
<!-- Breadcrumbs -->
<div class="text-sm breadcrumbs mb-6">
<ul>
<li><a href="/ti/painel-administrativo">Dashboard TI</a></li>
<li><a href="/ti/usuarios">Usuários</a></li>
<li>Criar Usuário</li>
</ul>
</div>
<!-- Mensagens -->
{#if mensagem}
<div
class="alert mb-6"
class:alert-success={mensagem.tipo === "success"}
class:alert-error={mensagem.tipo === "error"}
>
<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 mensagem.tipo === "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>{mensagem.texto}</span>
</div>
{/if}
<!-- Formulário -->
<div class="card bg-base-100 shadow-2xl border border-base-300">
<div class="card-body">
<div class="flex items-center gap-3 mb-6">
<div class="p-2 bg-primary/10 rounded-lg">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-6 w-6 text-primary"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"
/>
</svg>
</div>
<h2 class="card-title text-2xl">Informações do Usuário</h2>
</div>
<form onsubmit={handleSubmit}>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- Funcionário (primeiro) -->
<div class="form-control md:col-span-2">
<label class="label" for="funcionario">
<span class="label-text font-semibold">Vincular Funcionário (Opcional)</span>
</label>
<select
id="funcionario"
class="select select-bordered"
bind:value={funcionarioId}
disabled={processando || !funcionarios?.data}
>
<option value="">Selecione um funcionário para auto-completar dados</option>
{#if funcionarios?.data}
{#each funcionarios.data as func}
<option value={func._id}>{func.nome} - Mat: {func.matricula}</option>
{/each}
{/if}
</select>
<div class="label">
<span class="label-text-alt">Ao selecionar, os campos serão preenchidos automaticamente</span>
</div>
</div>
<!-- Matrícula -->
<div class="form-control">
<label class="label" for="matricula">
<span class="label-text font-semibold">Matrícula *</span>
</label>
<input
id="matricula"
type="number"
placeholder="Ex: 12345"
class="input input-bordered"
bind:value={matricula}
required
disabled={processando}
/>
</div>
<!-- Nome -->
<div class="form-control">
<label class="label" for="nome">
<span class="label-text font-semibold">Nome Completo *</span>
</label>
<input
id="nome"
type="text"
placeholder="Ex: João da Silva"
class="input input-bordered"
bind:value={nome}
required
disabled={processando}
/>
</div>
<!-- Email -->
<div class="form-control md:col-span-2">
<label class="label" for="email">
<span class="label-text font-semibold">E-mail *</span>
</label>
<input
id="email"
type="email"
placeholder="usuario@sgse.pe.gov.br"
class="input input-bordered"
bind:value={email}
required
disabled={processando}
/>
</div>
<!-- Perfil/Role -->
<div class="form-control">
<label class="label" for="role">
<span class="label-text font-semibold">Perfil de Acesso *</span>
</label>
<select
id="role"
class="select select-bordered"
bind:value={roleId}
required
disabled={processando || !roles?.data}
>
<option value="">Selecione um perfil</option>
{#if roles?.data && Array.isArray(roles.data)}
{#each roles.data as role}
<option value={role._id}>
{role.descricao} ({role.nome})
</option>
{/each}
{:else}
<option disabled>Carregando perfis...</option>
{/if}
</select>
{#if !roles?.data || !Array.isArray(roles.data)}
<div class="label">
<span class="label-text-alt text-warning">Carregando perfis disponíveis...</span>
</div>
{/if}
</div>
<div class="divider md:col-span-2 mt-4">
<div class="flex items-center gap-2">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-5 w-5 text-primary"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"
/>
</svg>
Senha Inicial
</div>
</div>
<!-- Senha -->
<div class="form-control">
<label class="label" for="senha">
<span class="label-text font-semibold">Senha Inicial *</span>
</label>
<input
id="senha"
type="password"
placeholder="Mínimo 8 caracteres"
class="input input-bordered"
bind:value={senhaInicial}
required
minlength="8"
disabled={processando}
/>
<div class="label">
<span class="label-text-alt">Mínimo 8 caracteres</span>
</div>
</div>
<!-- Confirmar Senha -->
<div class="form-control">
<label class="label" for="confirmar-senha">
<span class="label-text font-semibold">Confirmar Senha *</span>
</label>
<input
id="confirmar-senha"
type="password"
placeholder="Digite novamente"
class="input input-bordered"
bind:value={confirmarSenha}
required
minlength="8"
disabled={processando}
/>
</div>
<!-- Botão Gerar Senha e Visualização -->
<div class="md:col-span-2">
<button
type="button"
class="btn btn-sm btn-outline btn-info"
onclick={gerarSenhaAleatoria}
disabled={processando}
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-4 w-4"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"
/>
</svg>
Gerar Senha Forte Aleatória
</button>
{#if mostrarSenha && senhaGerada}
<div class="alert alert-warning mt-4">
<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="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>
<div class="flex-1">
<h3 class="font-bold">Senha Gerada:</h3>
<div class="flex items-center gap-2 mt-2">
<code class="bg-base-300 px-3 py-2 rounded text-lg font-mono select-all">
{senhaGerada}
</code>
<button
type="button"
class="btn btn-sm btn-ghost"
onclick={copiarSenha}
title="Copiar senha"
>
<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="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"
/>
</svg>
Copiar
</button>
</div>
<p class="text-sm mt-2">
⚠️ <strong>IMPORTANTE:</strong> Anote esta senha! Você precisará repassá-la
manualmente ao usuário até que o SMTP seja configurado.
</p>
</div>
</div>
{/if}
</div>
</div>
<div class="alert alert-info mt-6">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
class="stroke-current shrink-0 w-6 h-6"
>
<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"
></path>
</svg>
<div>
<h3 class="font-bold">Informações Importantes</h3>
<ul class="text-sm list-disc list-inside mt-2 space-y-1">
<li>O usuário deverá alterar a senha no primeiro acesso</li>
<li>As credenciais devem ser repassadas manualmente (por enquanto)</li>
<li>
Configure o SMTP em <a href="/ti/configuracoes-email" class="link"
>Configurações de Email</a
> para envio automático
</li>
</ul>
</div>
</div>
<div class="card-actions justify-end mt-8 pt-6 border-t border-base-300">
<a href="/ti/usuarios" class="btn btn-ghost gap-2" class:btn-disabled={processando}>
<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
</a>
<button type="submit" class="btn btn-primary gap-2" disabled={processando}>
{#if processando}
<span class="loading loading-spinner loading-sm"></span>
Criando Usuário...
{: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>
Criar Usuário
{/if}
</button>
</div>
</form>
</div>
</div>
</div>
</ProtectedRoute>