- 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]>
96 lines
2.5 KiB
Vue
96 lines
2.5 KiB
Vue
<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>
|