feat: Implement dedicated login page and public/dashboard layouts, refactoring authentication flow and removing the todos page.
This commit is contained in:
@@ -1,14 +1,21 @@
|
||||
import { createConvexHttpClient } from '@mmailaender/convex-better-auth-svelte/sveltekit';
|
||||
import { api } from '@sgse-app/backend/convex/_generated/api';
|
||||
import { error, redirect } from '@sveltejs/kit';
|
||||
import type { FunctionReference } from 'convex/server';
|
||||
|
||||
export const load = async ({ locals }) => {
|
||||
if (!locals.token) {
|
||||
throw redirect(302, '/login');
|
||||
}
|
||||
try {
|
||||
const client = createConvexHttpClient({ token: locals.token });
|
||||
const currentUser = await client.query(api.auth.getCurrentUser, {});
|
||||
const currentUser = await client.query(api.auth.getCurrentUser as FunctionReference<'query'>);
|
||||
|
||||
if (!currentUser) {
|
||||
throw redirect(302, '/login');
|
||||
}
|
||||
return { currentUser };
|
||||
} catch (error) {
|
||||
console.error('Erro ao carregar usuário atual no layout do dashboard:', error);
|
||||
// Evita quebrar toda a área logada em caso de falha transitória na API/auth
|
||||
return { currentUser: null };
|
||||
} catch {
|
||||
return error(500, 'Ops! Ocorreu um erro, tente novamente mais tarde.');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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} />
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
import { resolve } from '$app/paths';
|
||||
import { UserPlus, Mail, Clock, Award, TrendingUp, Zap, Users, Database } from 'lucide-svelte';
|
||||
import ProtectedRoute from '$lib/components/ProtectedRoute.svelte';
|
||||
import { loginModalStore } from '$lib/stores/loginModal.svelte';
|
||||
|
||||
// Queries para dados do dashboard
|
||||
const statsQuery = useQuery(api.dashboard.getStats, {});
|
||||
@@ -36,7 +35,12 @@
|
||||
|
||||
// Se for erro de autenticação, abrir modal de login automaticamente
|
||||
if (error === 'auth_required') {
|
||||
loginModalStore.open(route || to.url.pathname);
|
||||
const redirectTo = route || to.url.pathname;
|
||||
goto(`${resolve('/login')}?redirect=${encodeURIComponent(redirectTo)}`, {
|
||||
replaceState: true,
|
||||
noScroll: true
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Limpar URL usando SvelteKit (após router estar inicializado)
|
||||
@@ -65,7 +69,12 @@
|
||||
const route = urlParams.get('route') || urlParams.get('redirect') || '';
|
||||
|
||||
if (error === 'auth_required') {
|
||||
loginModalStore.open(route || window.location.pathname);
|
||||
const redirectTo = route || window.location.pathname;
|
||||
goto(`${resolve('/login')}?redirect=${encodeURIComponent(redirectTo)}`, {
|
||||
replaceState: true,
|
||||
noScroll: true
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user