Substitui o portal Vite+Vue puro por Nuxt 3 com BFF embutido (Nitro server
routes) e fluxo de autenticação Keycloak via token-handler pattern.
Server (BFF):
- server/api/auth/{login,callback,refresh,logout,me}.ts — Keycloak PKCE
- server/api/proxy/[...path].ts — proxy autenticado pro core-api com tenant
- server/utils/{session,keycloak,pkce,redis,tenant,prefeitura}.ts
- server/middleware/csrf.ts — Origin check + header X-Requested-With
Auth (token-handler pattern):
- JWT vive só server-side em Redis; cliente recebe cookie session-id opaco
- Refresh transparente quando access_token expira
- Multi-tenant via hostname → X-Municipio/X-Dominio injetados no proxy
- Realm dedicado: modumfiscal-portal-{env}
Frontend (Nuxt):
- src/pages/** (file-based routing) substitui src/views/
- Plugins SSR: prefeitura (bootstrap pré-hidratação) + auth (hidrata user via /api/auth/me)
- Composables useAuth, useApi, useLoginModal, useFocusLoginInput
- Modal global de login quando middleware /portal/** bloqueia
- Splash overlay no boot esconde flash do preset inicial pro tema dinâmico
- DocumentoInput bloqueia campo quando user autenticado (pré-preenche em certidão/IPTU)
Removidos:
- index.html, vite.config.js, src/main.js, src/router/
- src/config/apiClient.js (substituído por \$fetch via /api/proxy)
- src/services/{auth,prefeitura}Service.js (lógica migrada pra composables/plugins)
- src/mocks/ (não mais usado)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
44 lines
2.3 KiB
Vue
44 lines
2.3 KiB
Vue
<script setup>
|
|
defineProps({
|
|
icon: { type: String, required: true },
|
|
titulo: { type: String, required: true },
|
|
descricao: { type: String, default: '' },
|
|
to: { type: [String, Object], default: null },
|
|
requiresAuth: { type: Boolean, default: false },
|
|
})
|
|
|
|
const cardClasses = 'group flex flex-col gap-3 bg-white dark:bg-slate-800 rounded-xl border border-slate-200 dark:border-slate-700 p-5 hover:border-primary/40 dark:hover:border-primary/50 hover:shadow-md transition-all duration-200 cursor-pointer'
|
|
</script>
|
|
|
|
<template>
|
|
<NuxtLink v-if="to" :to="to" :class="cardClasses">
|
|
<div class="flex items-start justify-between">
|
|
<div class="w-10 h-10 bg-primary/8 dark:bg-primary/15 rounded-lg flex items-center justify-center group-hover:bg-primary/15 dark:group-hover:bg-primary/25 transition-colors">
|
|
<i :class="['pi', icon, 'text-primary text-lg']" />
|
|
</div>
|
|
<i v-if="requiresAuth" class="pi pi-lock text-slate-300 dark:text-slate-600 text-xs" title="Requer login" />
|
|
</div>
|
|
<div>
|
|
<p class="font-semibold text-slate-800 dark:text-slate-100 text-sm group-hover:text-primary transition-colors">{{ titulo }}</p>
|
|
<p v-if="descricao" class="text-xs text-slate-500 dark:text-slate-400 mt-1 leading-relaxed">{{ descricao }}</p>
|
|
</div>
|
|
<div class="flex items-center gap-1 text-xs text-primary font-medium opacity-0 group-hover:opacity-100 transition-opacity">
|
|
<span>Acessar</span>
|
|
<i class="pi pi-arrow-right text-xs" />
|
|
</div>
|
|
</NuxtLink>
|
|
|
|
<div v-else :class="cardClasses">
|
|
<div class="flex items-start justify-between">
|
|
<div class="w-10 h-10 bg-primary/8 dark:bg-primary/15 rounded-lg flex items-center justify-center group-hover:bg-primary/15 dark:group-hover:bg-primary/25 transition-colors">
|
|
<i :class="['pi', icon, 'text-primary text-lg']" />
|
|
</div>
|
|
<i v-if="requiresAuth" class="pi pi-lock text-slate-300 dark:text-slate-600 text-xs" title="Requer login" />
|
|
</div>
|
|
<div>
|
|
<p class="font-semibold text-slate-800 dark:text-slate-100 text-sm">{{ titulo }}</p>
|
|
<p v-if="descricao" class="text-xs text-slate-500 dark:text-slate-400 mt-1 leading-relaxed">{{ descricao }}</p>
|
|
</div>
|
|
</div>
|
|
</template>
|