feat: Implement dedicated login page and public/dashboard layouts, refactoring authentication flow and removing the todos page.

This commit is contained in:
2025-12-12 14:22:28 -03:00
parent b47a317c33
commit b771322b24
18 changed files with 665 additions and 802 deletions

View File

@@ -1,100 +1,17 @@
<script lang="ts">
import { page } from '$app/state';
import { useQuery } from 'convex-svelte';
import { api } from '@sgse-app/backend/convex/_generated/api';
import ActionGuard from '$lib/components/ActionGuard.svelte';
import { Toaster } from 'svelte-sonner';
import PushNotificationManager from '$lib/components/PushNotificationManager.svelte';
import Sidebar from '$lib/components/Sidebar.svelte';
const { children } = $props();
// Usuário atual e consentimento LGPD
const currentUser = useQuery(api.auth.getCurrentUser, {});
const consentimentoLGPD = useQuery(api.lgpd.verificarConsentimento, { tipo: 'termo_uso' });
// Redirecionar para o termo de consentimento se obrigatório e não aceito
$effect(() => {
const p = page.url.pathname;
// Rotas públicas/que não exigem termo
if (
p === '/' ||
p === '/abrir-chamado' ||
p === '/termo-consentimento' ||
p.startsWith('/privacidade') ||
p.startsWith('/api/')
) {
return;
}
// Precisa estar autenticado para exigir LGPD
if (!currentUser?.data) {
return;
}
// Query ainda carregando ou sem dados
if (!consentimentoLGPD?.data) {
return;
}
const data = consentimentoLGPD.data;
if (data.termoObrigatorio && !data.aceito) {
const redirect = encodeURIComponent(p);
window.location.href = `/termo-consentimento?redirect=${redirect}`;
}
});
// Resolver recurso/ação a partir da rota
const routeAction = $derived.by(() => {
const p = page.url.pathname;
if (p === '/' || p === '/abrir-chamado') return null;
// Funcionários
if (p.startsWith('/recursos-humanos/funcionarios')) {
if (p.includes('/cadastro')) return { recurso: 'funcionarios', acao: 'criar' };
if (p.includes('/excluir')) return { recurso: 'funcionarios', acao: 'excluir' };
if (p.includes('/editar') || p.includes('/funcionarioId'))
return { recurso: 'funcionarios', acao: 'editar' };
return { recurso: 'funcionarios', acao: 'listar' };
}
// Símbolos
if (p.startsWith('/recursos-humanos/simbolos')) {
if (p.includes('/cadastro')) return { recurso: 'simbolos', acao: 'criar' };
if (p.includes('/excluir')) return { recurso: 'simbolos', acao: 'excluir' };
if (p.includes('/editar') || p.includes('/simboloId'))
return { recurso: 'simbolos', acao: 'editar' };
return { recurso: 'simbolos', acao: 'listar' };
}
// Outras áreas (uso genérico: ver)
if (p.startsWith('/financeiro')) return { recurso: 'financeiro', acao: 'ver' };
if (p.startsWith('/controladoria')) return { recurso: 'controladoria', acao: 'ver' };
if (p.startsWith('/licitacoes')) return { recurso: 'licitacoes', acao: 'ver' };
if (p.startsWith('/compras')) return { recurso: 'compras', acao: 'ver' };
if (p.startsWith('/juridico')) return { recurso: 'juridico', acao: 'ver' };
if (p.startsWith('/comunicacao')) return { recurso: 'comunicacao', acao: 'ver' };
if (p.startsWith('/programas-esportivos'))
return { recurso: 'programas_esportivos', acao: 'ver' };
if (p.startsWith('/secretaria-executiva'))
return { recurso: 'secretaria_executiva', acao: 'ver' };
if (p.startsWith('/gestao-pessoas')) return { recurso: 'gestao_pessoas', acao: 'ver' };
return null;
});
</script>
{#if routeAction}
<ActionGuard recurso={routeAction.recurso} acao={routeAction.acao}>
<div class="flex flex-col">
<Sidebar>
<main id="container-central" class="w-full max-w-none px-3 py-4 lg:px-4">
{@render children()}
</main>
</ActionGuard>
{:else}
<main id="container-central" class="w-full max-w-none px-3 py-4 lg:px-4">
{@render children()}
</main>
{/if}
</Sidebar>
</div>
<!-- Toast Notifications (Sonner) -->
<Toaster position="top-right" richColors closeButton expand={true} />