MVP Finance Dashboard
Painel financeiro pessoal full stack com .NET 10 e React 19. Consolida contas bancárias, histórico de transações e metas financeiras em uma interface unificada com API RESTful e frontend reativo.
O MVP Finance Dashboard é um painel financeiro pessoal full stack: backend em .NET 10, frontend em React 19, os dois no mesmo projeto. A interface reúne saldo de contas, histórico de transações e metas numa tela única, alimentada por uma API que devolve sempre o mesmo envelope de resposta.
Como MVP (Minimum Viable Product, Produto Mínimo Viável), o foco está na interface, não no dado real. Tema claro e escuro, skeleton enquanto os dados chegam e um toaster avisando sobre o tempo de inicialização do Render Cloud já funcionam. As funcionalidades de negócio estão mockadas: cada card do painel lê um endpoint que devolve valores de exemplo.
Conceitos fundamentais
| Conceito | O que é |
|---|---|
| MVP (Minimum Viable Product, Produto Mínimo Viável) | Versão mínima de um produto que valida hipóteses com o menor esforço possível. |
| full stack (pilha completa) | Projeto que traz as duas pontas: o servidor que guarda e serve os dados, e a tela que o usuário vê. |
| endpoint (ponto de acesso) | Um endereço da API que responde a um pedido específico, como /contas ou /transacoes. Cada card da tela chama o seu. |
| REST (Representational State Transfer, Transferência de Estado Representacional) | Estilo arquitetural para APIs baseado em recursos, verbos HTTP e respostas sem estado (stateless), em que cada requisição carrega tudo o que o servidor precisa. |
| Envelope de resposta | Formato fixo que embrulha toda resposta da API, seja sucesso ou erro. O cliente lê sempre os mesmos campos e não precisa adivinhar o formato. |
| Mock (dado simulado) | Valor de exemplo devolvido no lugar do dado real, para a interface poder ser construída antes do banco existir. |
| spinner (indicador de carregamento) | A rodinha que gira enquanto o dado não chega. Ocupa pouco espaço, então a tela muda de tamanho quando o conteúdo real aparece. |
| salto de layout (layout shift) | O conteúdo chega e empurra o que já estava na tela, mudando tudo de lugar enquanto a pessoa lê. |
| Skeleton (esqueleto de carregamento) | Silhueta cinza no formato do conteúdo, exibida enquanto os dados chegam. Ela ocupa o mesmo espaço do dado final, então não há salto de layout. |
| Toaster (notificação flutuante) | Aviso curto que aparece num canto da tela e some sozinho, sem interromper o que o usuário está fazendo. |
Stack
| Tecnologia | Papel |
|---|---|
| .NET 10 & C# 14 | ASP.NET Core Minimal API |
| PostgreSQL 17 | Dados em memória no MVP |
| Serilog | Logs estruturados |
| Scalar | Documentação OpenAPI interativa |
| Tecnologia | Papel |
|---|---|
| React 19 & TypeScript 5 | Interface do usuário, empacotada com Vite 6 |
| TanStack Query 5 | Busca de dados e cache no cliente |
| Zustand 5 | Estado global com persistência de tema |
| React Hook Form 7 + Zod 3 | Formulários tipados com validação |
| shadcn/ui + Tailwind CSS | Componentes e estilo |
Arquitetura
| Padrão | Descrição | Detalhes |
|---|---|---|
| Vertical Slice | Código organizado por domínio de feature | não por camada técnica |
| Handler Pattern | Cada endpoint tem um handler dedicado | delega para funções com nomes descritivos |
| Contrato unificado | Todas as respostas seguem o mesmo envelope | statusactionmessagedata |
| Result Pattern | Erros como valores | sem exceções para controle de fluxo |
| Rate Limiting | 60 req/min por IP | middleware nativo do .NET |
O backend tem três fatias, uma por domínio: Features/Accounts, Features/Transactions e Features/Goals. Cada uma guarda o handler, os DTOs e os endpoints no mesmo lugar, em vez de espalhar o mesmo domínio por uma pasta de controllers, outra de services e outra de repositories.
Um envelope, quatro campos
Toda resposta da API sai no mesmo formato, o ApiResponse<T>, com quatro campos fixos:
| Campo | O que traz |
|---|---|
status | O código HTTP repetido no corpo, para o cliente não precisar ler o cabeçalho. |
action | O nome da operação que respondeu, útil no log e na depuração. |
message | Texto curto sobre o que aconteceu. |
data | O conteúdo em si, tipado. Vem vazio quando a operação falha. |
O cliente escreve o tratamento uma vez, e ele serve para a API inteira, no sucesso e no erro.
Erro como valor, não como exceção
O handler não lança exceção para dizer que algo deu errado. Ele devolve um Result<T>, que carrega IsSuccess, o valor quando deu certo, e a mensagem mais um ResultErrorType quando não deu. Os tipos de erro são cinco: Failure, NotFound, Conflict, Unauthorized e Forbidden.
Quem traduz esse tipo para o HTTP correto é o ApiResults.MapError, num lugar só. A vantagem prática é que o caminho de erro fica visível na assinatura do método, em vez de escondido num throw que o leitor descobre três arquivos depois.
Na mesma linha, o Common/Utc.cs expõe um Utc.Now que devolve DateTimeOffset.UtcNow, e ele substitui o DateTime.Now em todo o código. Data gravada em horário local é um erro que só aparece quando o servidor muda de fuso, e centralizar em um ponto tira a decisão de quem escreve a feature.
O que ainda não está pronto
O projeto é um MVP e a documentação dele é explícita sobre o que falta:
- PostgreSQL 17 está na stack como destino, mas os dados hoje ficam em memória.
- JWT em cookie HttpOnly, para o token não ficar exposto ao JavaScript.
- RBAC, controle de acesso por papel de usuário.
- FluentValidation, validação de request fora das DataAnnotations.
Status
| Aspecto | Estado |
|---|---|
| Maturidade | MVP, interface sobre dados de exemplo |
| Backend | Live no Render (plano gratuito, com atraso na primeira chamada) |
| Frontend | Live em finance.thiagocaja.dev |
| Documentação | OpenAPI interativa via Scalar, em /scalar |
| Repositório | Público |
| Monetização | Nenhuma, projeto de portfólio |