feat: implement user consent verification and redirection for LGPD compliance in dashboard layout and consent term page

This commit is contained in:
2025-12-02 06:17:23 -03:00
parent 2825bd0e6e
commit e460b114ed
2 changed files with 56 additions and 3 deletions

View File

@@ -1,10 +1,49 @@
<script lang="ts"> <script lang="ts">
import { page } from '$app/state'; 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 ActionGuard from '$lib/components/ActionGuard.svelte';
import { Toaster } from 'svelte-sonner'; import { Toaster } from 'svelte-sonner';
import PushNotificationManager from '$lib/components/PushNotificationManager.svelte'; import PushNotificationManager from '$lib/components/PushNotificationManager.svelte';
const { children } = $props(); 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 // Resolver recurso/ação a partir da rota
const routeAction = $derived.by(() => { const routeAction = $derived.by(() => {
const p = page.url.pathname; const p = page.url.pathname;

View File

@@ -1,5 +1,6 @@
<script lang="ts"> <script lang="ts">
import { resolve } from '$app/paths'; import { resolve } from '$app/paths';
import { page } from '$app/state';
import { Shield, CheckCircle, AlertCircle, FileText } from 'lucide-svelte'; import { Shield, CheckCircle, AlertCircle, FileText } from 'lucide-svelte';
import { useQuery, useMutation } from 'convex-svelte'; import { useQuery, useMutation } from 'convex-svelte';
import { api } from '@sgse-app/backend/convex/_generated/api'; import { api } from '@sgse-app/backend/convex/_generated/api';
@@ -21,6 +22,18 @@
const termoObrigatorio = $derived(consentimentoQuery?.data?.termoObrigatorio ?? false); const termoObrigatorio = $derived(consentimentoQuery?.data?.termoObrigatorio ?? false);
const versaoTermoAtual = $derived(consentimentoQuery?.data?.versaoTermoAtual ?? '1.0'); const versaoTermoAtual = $derived(consentimentoQuery?.data?.versaoTermoAtual ?? '1.0');
// Rota de retorno após aceite
const redirectTo = $derived(page.url.searchParams.get('redirect') || '/');
function navegarDeVolta() {
// Se vier algo como URL absoluta, mantemos; senão usamos resolve
if (redirectTo.startsWith('/')) {
window.location.href = redirectTo;
} else {
window.location.href = resolve(redirectTo);
}
}
async function aceitarTermo() { async function aceitarTermo() {
if (termoObrigatorio && !aceito) { if (termoObrigatorio && !aceito) {
erro = 'Você precisa aceitar o termo para continuar'; erro = 'Você precisa aceitar o termo para continuar';
@@ -29,7 +42,7 @@
if (!aceito) { if (!aceito) {
// Se não é obrigatório e não aceitou, apenas redireciona // Se não é obrigatório e não aceitou, apenas redireciona
window.location.href = resolve('/'); navegarDeVolta();
return; return;
} }
@@ -43,6 +56,7 @@
versao: versaoTermoAtual versao: versaoTermoAtual
}); });
sucesso = true; sucesso = true;
navegarDeVolta();
} catch (e: unknown) { } catch (e: unknown) {
erro = e instanceof Error ? e.message : 'Erro ao registrar consentimento'; erro = e instanceof Error ? e.message : 'Erro ao registrar consentimento';
} finally { } finally {
@@ -102,9 +116,9 @@
<p class="text-base-content/80 mb-6"> <p class="text-base-content/80 mb-6">
Seu consentimento foi registrado. Você pode acessar o sistema normalmente. Seu consentimento foi registrado. Você pode acessar o sistema normalmente.
</p> </p>
<a href={resolve('/')} class="btn btn-primary btn-lg"> <button type="button" class="btn btn-primary btn-lg" onclick={navegarDeVolta}>
Continuar para o Sistema Continuar para o Sistema
</a> </button>
</div> </div>
</div> </div>
{:else} {:else}