Portfólio Blog Sobre

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.

.NET C# React TypeScript PostgreSQL TanStack Query Zustand shadcn/ui Tailwind CSS
MVP Finance Dashboard

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

ConceitoO 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 respostaFormato 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

Backend
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
Frontend
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:

CampoO que traz
statusO código HTTP repetido no corpo, para o cliente não precisar ler o cabeçalho.
actionO nome da operação que respondeu, útil no log e na depuração.
messageTexto curto sobre o que aconteceu.
dataO 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

AspectoEstado
MaturidadeMVP, interface sobre dados de exemplo
BackendLive no Render (plano gratuito, com atraso na primeira chamada)
FrontendLive em finance.thiagocaja.dev
DocumentaçãoOpenAPI interativa via Scalar, em /scalar
RepositórioPúblico
MonetizaçãoNenhuma, projeto de portfólio