feat: multi-usuário com autenticação JWT
- Tabela `profiles` + coluna `profile_id` em todas as entidades (categories, transactions, recurring_expenses, accounts, player_profile, xp_events, player_quests, player_achievements, player_cosmetics) - Dados existentes migrados para profile_id = 1 (Manoel) - CLI `./api create-user --name <n> --password <p>` cria perfil com seed de categorias e player_profile; faz upsert de senha se já existir - Auth substituída: cookie+APP_PASSWORD → JWT Bearer 24h (HS256) - Middleware RequireAuth injeta profile_id no context de todas as rotas - Todos os repositórios filtram por profile_id do context - Endpoints: POST /api/auth/login, GET /api/auth/me, POST /api/auth/change-password, POST /api/logout - Frontend: auth store usa localStorage (fc_token/fc_profile), api.ts envia Authorization header, LoginView usa campo name - SettingsView reescrita com troca de senha e logout - docker-compose.yml: remove APP_USERNAME/APP_PASSWORD, adiciona JWT_SECRET Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
<template>
|
||||
<div
|
||||
class="fc-panel"
|
||||
:class="{ 'fc-panel--glow': glow, [`fc-panel--${variant}`]: !!variant }"
|
||||
>
|
||||
<template v-if="corners">
|
||||
<span class="fc-corner fc-corner--tl" />
|
||||
<span class="fc-corner fc-corner--tr" />
|
||||
<span class="fc-corner fc-corner--bl" />
|
||||
<span class="fc-corner fc-corner--br" />
|
||||
</template>
|
||||
|
||||
<header v-if="title || $slots.header" class="fc-panel__header">
|
||||
<div v-if="title" class="fc-panel__title">:: {{ title }}</div>
|
||||
<slot name="header" />
|
||||
</header>
|
||||
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineProps<{
|
||||
/** Painel "principal" da rota: borda roxa + halo. Use com parcimônia. */
|
||||
glow?: boolean
|
||||
/** Cantos em L decorativos. Reserve pro card de destaque. */
|
||||
corners?: boolean
|
||||
/** 'danger' (recorrência ausente, meta falhou) | 'success' */
|
||||
variant?: 'danger' | 'success'
|
||||
/** Título estilo HUD; renderizado automaticamente como `:: NOME`. */
|
||||
title?: string
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.fc-panel {
|
||||
position: relative;
|
||||
background: linear-gradient(180deg, var(--fc-bg-raised), var(--fc-bg-panel));
|
||||
border: 2px solid var(--fc-panel-edge);
|
||||
border-radius: var(--fc-radius);
|
||||
padding: var(--fc-space-4);
|
||||
box-shadow: var(--fc-shadow-panel);
|
||||
}
|
||||
|
||||
.fc-panel--glow {
|
||||
border-color: var(--fc-accent-3);
|
||||
box-shadow: var(--fc-shadow-panel-glow);
|
||||
}
|
||||
|
||||
.fc-panel--danger {
|
||||
border-color: var(--fc-red);
|
||||
box-shadow:
|
||||
inset 0 1px 0 rgba(255,255,255,.05),
|
||||
0 0 0 1px var(--fc-red),
|
||||
0 0 16px rgba(255, 59, 107, .3),
|
||||
0 8px 24px rgba(0,0,0,.4);
|
||||
}
|
||||
|
||||
.fc-panel--success {
|
||||
border-color: var(--fc-green);
|
||||
box-shadow:
|
||||
inset 0 1px 0 rgba(255,255,255,.05),
|
||||
0 0 0 1px var(--fc-green),
|
||||
0 0 16px rgba(57, 255, 122, .25),
|
||||
0 8px 24px rgba(0,0,0,.4);
|
||||
}
|
||||
|
||||
.fc-panel__header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: var(--fc-space-3);
|
||||
gap: var(--fc-space-2);
|
||||
}
|
||||
|
||||
.fc-panel__title {
|
||||
font-family: var(--fc-font-pixel);
|
||||
font-size: 9px;
|
||||
color: var(--fc-accent-2);
|
||||
letter-spacing: .04em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.fc-corner {
|
||||
position: absolute;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border: 2px solid var(--fc-accent-2);
|
||||
pointer-events: none;
|
||||
}
|
||||
.fc-corner--tl { top: -2px; left: -2px; border-right: none; border-bottom: none; }
|
||||
.fc-corner--tr { top: -2px; right: -2px; border-left: none; border-bottom: none; }
|
||||
.fc-corner--bl { bottom: -2px; left: -2px; border-right: none; border-top: none; }
|
||||
.fc-corner--br { bottom: -2px; right: -2px; border-left: none; border-top: none; }
|
||||
</style>
|
||||
Reference in New Issue
Block a user