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:
2026-05-27 20:04:44 -03:00
co-authored by Claude Sonnet 4.6
parent 50637bd590
commit acbce4edc0
49 changed files with 2662 additions and 382 deletions
+164
View File
@@ -0,0 +1,164 @@
# Financeiro Carvalho · Handoff (Arcade Neon)
Pacote de design pronto pra ser entregue ao Claude Code / Cursor / Aider implementar.
Direção visual escolhida: **Arcade Neon**.
## O que tem aqui
```
neon-handoff/
├── STYLE_GUIDE.md ← leia primeiro
├── preview.html ← referência visual interativa (abra num servidor local)
├── tokens.css ← variáveis CSS + reset + classes utilitárias
├── components/ ← Vue 3 SFCs de referência
│ ├── NeonPanel.vue
│ ├── XPBar.vue
│ ├── HUDStat.vue
│ └── CharacterSprite.vue
├── sprites/
│ ├── sprite-data.json ← grade raw 17×24 + lista de cosméticos
│ ├── icons.json ← heart, coin, star, anchor (8×8 cada)
│ ├── manoel-default.png (e 4x)
│ ├── manoel-lv01.png (e 4x) · cabelo castanho, camisa azul, calça caqui
│ ├── manoel-lv03-bone-verolme.png · boné azul
│ ├── manoel-lv05-wingspan.png · camisa verde
│ ├── manoel-lv07-chapeu-sol.png · look "equipado" atual
│ ├── manoel-lv10-anti-vento.png · casaco roxo
│ ├── manoel-lv15-capitao.png · trajado de capitão (vermelho/preto)
│ └── manoel-sheet-4x.png · todos lado a lado pra referência
└── README.md ← este arquivo
```
## Como integrar no apps/web
1. Copie `tokens.css` para `src/styles/tokens.css`. Importe **uma vez** em `src/main.ts`:
```ts
import './styles/tokens.css'
```
2. Copie `components/*.vue` para `src/components/`. Eles já consomem `var(--fc-*)`.
3. Copie `sprites/sprite-data.json` e `sprites/icons.json` pra `src/assets/`.
Garanta que `tsconfig.json` tenha `"resolveJsonModule": true` (Vite default já tem).
4. Embrulhe o `<router-view>` em `<div class="fc-app">` — é isso que aplica o fundo + scanlines + grid.
```vue
<!-- App.vue -->
<template>
<div class="fc-app">
<AppHud />
<RouterView />
</div>
</template>
```
5. As 8 rotas do PRD (`/`, `/transacoes`, `/importar`, `/categorias`, `/recorrencias`,
`/contas`, `/personagem`, `/configuracoes`) usam o mesmo header HUD. Veja
`STYLE_GUIDE.md` §3.6 pro shape do componente.
## Sprites · uso
### Renderizar dinâmico (recomendado)
`<CharacterSprite>` lê `sprite-data.json` e aceita `theme` como prop. Cosméticos
equipados são merged no store (Pinia) e passados como `theme`.
```vue
<CharacterSprite :scale="6" :theme="gameStore.equippedTheme" :celebrate="gameStore.justLeveledUp" />
```
Exemplo de store getter:
```ts
// stores/game.ts
const equippedTheme = computed(() => {
return cosmetics.value
.filter(c => c.equipped)
.reduce((acc, c) => ({ ...acc, ...c.colors }), {} as Record<string, string>)
})
```
### Usar PNG estático
Útil pra previews em página de loja (`/personagem` → seção "cosméticos"):
```html
<img src="@/assets/sprites/manoel-lv03-bone-verolme-4x.png" alt="Boné Verolme" />
```
Sempre `image-rendering: pixelated;` no CSS pra não suavizar.
### Regenerar PNGs
`sprites/sprite-data.json` é a fonte da verdade. Se você ajustar uma cor de
cosmético lá, regenere os PNGs com um script Node:
```js
// scripts/build-sprites.mjs
import { createCanvas } from '@napi-rs/canvas' // ou node-canvas
import fs from 'node:fs/promises'
const data = JSON.parse(await fs.readFile('src/assets/sprite-data.json', 'utf8'))
// ... mesmo render do PixelSprite acima, salva PNG por cosmético
```
Ou peça pro Claude Code regenerar — o algoritmo é trivial (loop pela grade,
fillRect por célula com a cor do slot).
## Princípios para o Claude implementar
Por ordem de importância:
1. **HUD persistente em todas as rotas.** Não use sidebar — o app é um HUD de jogo.
2. **Nunca use hex direto.** Sempre `var(--fc-*)`. Se faltar token, adicione-o ao
`tokens.css` e documente no `STYLE_GUIDE.md` §1.1.
3. **Press Start 2P pra label e número. JetBrains Mono pra dado. Inter pra prosa.**
Press Start 2P sempre em maiúscula.
4. **Glow é raro.** Só nos números heroicos (47%) e no painel principal de cada
rota. Se cada card brilha, nada brilha.
5. **Verde/vermelho são binários** — meta sim / meta não. Não use vermelho pra valor
negativo (transação) — só pra alerta.
6. **Cada animação tem propósito.** Ver §6 do guia.
7. **Mobile vira bottom-nav.** Não tente espremer a HUD horizontal num iPhone.
## Telas a implementar
Conforme `STYLE_GUIDE.md` + screens visíveis em `preview.html` (Arcade · 01):
- [ ] `/` Dashboard (hero poupado, categorias, histórico, transações, recorrências, quests, patrimônio, widget personagem)
- [ ] `/transacoes` lista + CRUD
- [ ] `/importar` upload OFX/CSV + preview de deduplicação
- [ ] `/categorias` CRUD com pixel-icon picker
- [ ] `/recorrencias` lista mensal com status (paid / due / missing)
- [ ] `/contas` cards de conta + patrimônio total
- [ ] `/personagem` painel RPG completo
- [ ] `/configuracoes` formulário simples (perfil, meta %, prefs visuais)
Para qualquer tela nova: comece copiando a estrutura do dashboard,
substitua o conteúdo dos painéis, mantenha a HUD.
## Componentes adicionais que faltam
Esses não estão neste pacote — implemente seguindo os tokens já definidos:
- `AppHud.vue` (cabeçalho persistente)
- `BottomNav.vue` (mobile)
- `TransactionRow.vue`
- `QuestCard.vue`
- `CategoryBar.vue`
- `AchievementCell.vue`
- `LevelUpModal.vue` (overlay celebratório no level up)
- `MonthSwitcher.vue` ( MAIO · 2026 )
## Tom de voz
Português brasileiro, direto, com vocabulário de jogo. Não é app corporativo.
Ver `STYLE_GUIDE.md` §8.
---
Qualquer dúvida sobre **intenção visual** (não implementação), volte ao
`STYLE_GUIDE.md` ou ao `preview.html`. A página de design original do projeto
(canvas com 4 direções) também segue acessível pra comparação.