chore: adicionar configuração de dependências no pnpm-workspace

Adiciona a configuração 'onlyBuiltDependencies' no arquivo
pnpm-workspace.yaml, especificando as dependências que devem ser
construídas: esbuild, sharp e unrs-resolver. Essa mudança visa
otimizar o gerenciamento de dependências no projeto.
This commit is contained in:
Felipe Coutinho
2025-11-16 12:28:15 -03:00
parent 7d88852ceb
commit b124d5193f
9 changed files with 1029 additions and 611 deletions

1
.gitignore vendored
View File

@@ -22,6 +22,7 @@ next-env.d.ts
/build /build
/dist /dist
*.tsbuildinfo *.tsbuildinfo
.pnpm-store
# === Testing === # === Testing ===
/coverage /coverage

314
README.md
View File

@@ -1,6 +1,8 @@
# OpenSheets # OpenSheets
> Uma aplicação moderna e completa construída com **Next.js 16**, **Better Auth**, **Drizzle ORM**, **PostgreSQL** e **shadcn/ui**. > Projeto pessoal de gestão financeira. Self-hosted, manual e open source.
> **⚠️ Não há versão online hospedada.** Você precisa clonar o repositório e rodar localmente ou no seu próprio servidor.
[![Next.js](https://img.shields.io/badge/Next.js-16-black?style=flat-square&logo=next.js)](https://nextjs.org/) [![Next.js](https://img.shields.io/badge/Next.js-16-black?style=flat-square&logo=next.js)](https://nextjs.org/)
[![TypeScript](https://img.shields.io/badge/TypeScript-5.9-blue?style=flat-square&logo=typescript)](https://www.typescriptlang.org/) [![TypeScript](https://img.shields.io/badge/TypeScript-5.9-blue?style=flat-square&logo=typescript)](https://www.typescriptlang.org/)
@@ -23,23 +25,78 @@
- [Configuração de Variáveis de Ambiente](#-configuração-de-variáveis-de-ambiente) - [Configuração de Variáveis de Ambiente](#-configuração-de-variáveis-de-ambiente)
- [Banco de Dados](#-banco-de-dados) - [Banco de Dados](#-banco-de-dados)
- [Arquitetura](#-arquitetura) - [Arquitetura](#-arquitetura)
- [Troubleshooting](#-troubleshooting)
- [Contribuindo](#-contribuindo) - [Contribuindo](#-contribuindo)
--- ---
## 🎯 Sobre o Projeto ## 🎯 Sobre o Projeto
**OpenSheets** é uma aplicação full-stack moderna projetada para controle de finanças pessoais. Construída com as melhores práticas de desenvolvimento e ferramentas de ponta, oferece uma base sólida e escalável para gestão financeira completa. **OpenSheets** é um projeto pessoal de gestão financeira que criei para organizar minhas próprias finanças. Cansei de usar planilhas desorganizadas e aplicativos que não fazem exatamente o que preciso, então decidi construir algo do jeito que funciona pra mim.
### Por que usar o OpenSheets? A ideia é simples: ter um lugar onde consigo ver todas as minhas contas, cartões, gastos e receitas de forma clara. Se isso for útil pra você também, fique à vontade para usar e contribuir.
-**Pronto para Produção** - Docker, health checks, migrations automáticas ### ⚠️ Avisos importantes
-**TypeScript First** - Type safety em toda a aplicação
-**Autenticação Completa** - Better Auth com OAuth, email magic links **1. Não há versão hospedada online**
-**ORM Moderno** - Drizzle com Drizzle Studio integrado
-**UI Components** - shadcn/ui com design system completo Este projeto é self-hosted. Você precisa rodar no seu próprio computador ou servidor. Não existe uma versão pública online onde você pode simplesmente criar uma conta.
-**Developer Experience** - Hot reload, Turbopack, ESLint configurado
**2. Não há Open Finance**
Você precisa registrar manualmente suas transações. Se você procura algo que sincroniza automaticamente com seu banco, este projeto não é pra você.
**3. Requer disciplina**
O OpenSheets funciona melhor para quem:
- Tem disciplina de registrar os gastos regularmente
- Quer controle total sobre seus dados
- Gosta de entender exatamente onde o dinheiro está indo
- Sabe rodar projetos localmente ou tem vontade de aprender
Se você não se importa em dedicar alguns minutos por dia (ou semana) para manter tudo atualizado, vai funcionar bem. Caso contrário, provavelmente vai abandonar depois de uma semana.
### O que tem aqui
💰 **Controle de contas e transações**
- Registre suas contas bancárias, cartões e dinheiro em espécie
- Adicione receitas, despesas e transferências entre contas
- Organize tudo por categorias (moradia, alimentação, transporte, etc.)
- Veja o saldo atual de cada conta
📊 **Relatórios e gráficos**
- Dashboard com resumo mensal das suas finanças
- Gráficos de evolução do patrimônio
- Comparação de gastos por categoria
- Entenda pra onde seu dinheiro está indo
💳 **Faturas de cartão de crédito**
- Cadastre seus cartões e acompanhe as faturas
- Veja o que ainda não foi fechado na fatura atual
- Controle de limites e vencimentos
🎯 **Orçamentos**
- Defina quanto quer gastar por categoria no mês
- Acompanhe se está dentro do planejado
### Stack técnica
Construído com tecnologias modernas que facilitam o desenvolvimento:
- **Next.js 16** com App Router e Turbopack
- **TypeScript** em tudo
- **PostgreSQL 18** como banco de dados
- **Drizzle ORM** para trabalhar com o banco
- **Better Auth** para login (email + OAuth)
- **shadcn/ui** para os componentes da interface
- **Docker** para facilitar deploy e desenvolvimento
- **Tailwind CSS** para estilização
O projeto é open source, seus dados ficam no seu controle (pode rodar localmente ou no seu próprio servidor), e você pode customizar o que quiser.
--- ---
@@ -727,242 +784,6 @@ opensheets/
--- ---
## 🆘 Troubleshooting
### Erro: "DATABASE_URL env variable is not set"
**Causa:** Arquivo `.env` não existe ou `DATABASE_URL` não configurado
**Solução:**
```bash
cp .env.example .env
# Edite .env e configure DATABASE_URL
```
---
### Container do app não conecta ao banco
**Causa:** `DATABASE_URL` usa `localhost` em vez de `db`
**Solução:**
Para Docker, use o **nome do serviço**:
```env
# ❌ Errado (localhost não funciona dentro do container)
DATABASE_URL=postgresql://opensheets:senha@localhost:5432/opensheets_db
# ✅ Correto (usa nome do serviço Docker)
DATABASE_URL=postgresql://opensheets:senha@db:5432/opensheets_db
```
Para desenvolvimento local (sem Docker app):
```env
# ✅ Correto (app roda local, banco em Docker)
DATABASE_URL=postgresql://opensheets:senha@localhost:5432/opensheets_db
```
**Verifique o status do banco:**
```bash
docker compose ps
docker compose logs db
```
---
### Porta 3000 ou 5432 já está em uso
**Solução:**
Edite o `.env`:
```env
APP_PORT=3001
DB_PORT=5433
```
Ou pare o processo que está usando:
```bash
# Descobrir quem usa a porta
lsof -i :3000
lsof -i :5432
# Matar processo
kill -9 <PID>
```
---
### Migrations não rodam
**Com Docker:**
Migrations rodam automaticamente no startup. Veja logs:
```bash
pnpm docker:logs:app
```
Se falharem, rode manualmente:
```bash
docker compose exec app pnpm db:push
```
**Sem Docker:**
```bash
pnpm db:push
```
---
### Erro: "server.js not found"
**Causa:** Next.js não gerou standalone build
**Solução:**
1. Verifique `next.config.ts`:
```typescript
const nextConfig: NextConfig = {
output: "standalone", // ← Deve estar presente
};
```
2. Rebuild:
```bash
docker compose down
docker compose up --build
```
---
### Erro ao atualizar PostgreSQL 16 → 18
**Causa:** Volumes antigos são incompatíveis
**Solução:**
```bash
# ⚠️ ATENÇÃO: Isso apaga dados do banco local!
docker compose down -v
# Suba novamente com PostgreSQL 18
docker compose up --build
```
**Para preservar dados:**
```bash
# 1. Backup
docker compose exec db pg_dumpall -U opensheets > backup.sql
# 2. Limpa volumes
docker compose down -v
# 3. Sobe PG 18
docker compose up -d db
# 4. Aguarda (15s)
sleep 15
# 5. Restaura
docker compose exec -T db psql -U opensheets -d opensheets_db < backup.sql
```
---
### Drizzle Studio não abre
**Solução:**
1. Verifique se o banco está rodando:
```bash
docker compose ps
```
2. Teste conexão:
```bash
psql $DATABASE_URL
```
3. Abra Drizzle Studio:
```bash
pnpm db:studio
```
---
### Build do Docker muito lento
**Causa:** Cache não está sendo aproveitado
**Solução:**
1. Use BuildKit:
```bash
export DOCKER_BUILDKIT=1
docker compose build
```
2. Limpe cache antigo:
```bash
docker builder prune
```
3. Multi-stage build já otimiza camadas
---
### "Permission denied" ao rodar Docker
**Causa:** Usuário não está no grupo docker
**Solução (Linux):**
```bash
sudo usermod -aG docker $USER
newgrp docker
```
**Solução (Mac/Windows):**
- Docker Desktop deve estar rodando
- Verifique configurações de permissão
---
### Limpar tudo e começar do zero
```bash
# Para containers e remove volumes
docker compose down -v
# Remove images não usadas
docker system prune -a
# Remove TUDO do Docker (cuidado!)
docker system prune -a --volumes
# Rebuild do zero
pnpm docker:up
```
---
## 🤝 Contribuindo ## 🤝 Contribuindo
Contribuições são muito bem-vindas! Contribuições são muito bem-vindas!
@@ -991,7 +812,6 @@ Contribuições são muito bem-vindas!
### Padrões ### Padrões
- Use **TypeScript** - Use **TypeScript**
- Siga o **ESLint** configurado
- Documente **features novas** - Documente **features novas**
- Use **commits semânticos** (feat, fix, docs, etc) - Use **commits semânticos** (feat, fix, docs, etc)

View File

@@ -9,12 +9,13 @@ import {
RiBankCardLine, RiBankCardLine,
RiBarChartBoxLine, RiBarChartBoxLine,
RiCalendarLine, RiCalendarLine,
RiCodeSSlashLine,
RiDatabase2Line,
RiDeviceLine, RiDeviceLine,
RiEyeOffLine, RiGithubFill,
RiLineChartLine, RiLineChartLine,
RiLockLine, RiLockLine,
RiMoneyDollarCircleLine, RiMoneyDollarCircleLine,
RiNotificationLine,
RiPieChartLine, RiPieChartLine,
RiShieldCheckLine, RiShieldCheckLine,
RiTimeLine, RiTimeLine,
@@ -33,6 +34,29 @@ export default async function Page() {
<div className="flex items-center"> <div className="flex items-center">
<Logo /> <Logo />
</div> </div>
{/* Center Navigation Links */}
<nav className="hidden md:flex items-center gap-6 absolute left-1/2 transform -translate-x-1/2">
<a
href="#funcionalidades"
className="text-sm text-muted-foreground hover:text-foreground transition-colors"
>
Funcionalidades
</a>
<a
href="#stack"
className="text-sm text-muted-foreground hover:text-foreground transition-colors"
>
Stack
</a>
<a
href="#como-usar"
className="text-sm text-muted-foreground hover:text-foreground transition-colors"
>
Como usar
</a>
</nav>
<nav className="flex items-center gap-2 md:gap-4"> <nav className="flex items-center gap-2 md:gap-4">
<AnimatedThemeToggler /> <AnimatedThemeToggler />
{session?.user ? ( {session?.user ? (
@@ -50,7 +74,7 @@ export default async function Page() {
</Link> </Link>
<Link href="/signup"> <Link href="/signup">
<Button size="sm" className="gap-2"> <Button size="sm" className="gap-2">
Começar Grátis Começar
<RiArrowRightSLine size={16} /> <RiArrowRightSLine size={16} />
</Button> </Button>
</Link> </Link>
@@ -64,77 +88,90 @@ export default async function Page() {
<section className="relative py-16 md:py-24 lg:py-32"> <section className="relative py-16 md:py-24 lg:py-32">
<div className="container"> <div className="container">
<div className="mx-auto flex max-w-5xl flex-col items-center text-center gap-6"> <div className="mx-auto flex max-w-5xl flex-col items-center text-center gap-6">
<Badge variant="secondary" className="mb-2"> <Badge variant="primary" className="mb-2">
<RiLineChartLine size={14} className="mr-1" /> <RiGithubFill size={14} className="mr-1" />
Controle Financeiro Inteligente Projeto Open Source
</Badge> </Badge>
<h1 className="text-4xl md:text-5xl lg:text-6xl font-bold tracking-tight"> <h1 className="text-4xl md:text-5xl lg:text-6xl font-bold tracking-tight">
Gerencie suas finanças Suas finanças,
<span className="text-primary"> com simplicidade</span> <span className="text-primary"> do seu jeito</span>
</h1> </h1>
<p className="text-lg md:text-xl text-muted-foreground max-w-2xl"> <p className="text-lg md:text-xl text-muted-foreground max-w-2xl">
Organize seus gastos, acompanhe receitas, gerencie cartões de Um projeto pessoal de gestão financeira. Self-hosted, sem Open
crédito e tome decisões financeiras mais inteligentes. Tudo em um Finance, sem sincronização automática. Rode no seu computador ou
só lugar. servidor e tenha controle total sobre suas finanças.
</p> </p>
<div className="rounded-lg border bg-muted/30 p-4 max-w-2xl">
<p className="text-sm text-muted-foreground">
<span className="font-semibold text-foreground">
Aviso importante:
</span>{" "}
Este sistema requer disciplina. Você precisa registrar
manualmente cada transação. Se prefere algo automático, este
projeto não é pra você.
</p>
</div>
<div className="flex flex-col sm:flex-row gap-4 mt-4"> <div className="flex flex-col sm:flex-row gap-4 mt-4">
<Link href="/signup"> <Link
href="https://github.com/felipegcoutinho/opensheets-app"
target="_blank"
>
<Button size="lg" className="gap-2 w-full sm:w-auto"> <Button size="lg" className="gap-2 w-full sm:w-auto">
Começar Gratuitamente <RiGithubFill size={18} />
<RiArrowRightSLine size={18} /> Baixar no GitHub
</Button> </Button>
</Link> </Link>
<Link href="/login"> <Link
href="https://github.com/felipegcoutinho/opensheets-app#readme"
target="_blank"
>
<Button <Button
size="lg" size="lg"
variant="outline" variant="outline"
className="w-full sm:w-auto" className="w-full sm:w-auto gap-2"
> >
Fazer Login Ver Documentação
</Button> </Button>
</Link> </Link>
</div> </div>
<div className="mt-8 flex flex-wrap items-center justify-center gap-6 text-sm text-muted-foreground"> <div className="mt-8 flex flex-wrap items-center justify-center gap-6 text-sm text-muted-foreground">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<RiShieldCheckLine size={18} className="text-primary" /> <RiLockLine size={18} className="text-primary" />
Dados Seguros Seus dados, seu servidor
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<RiEyeOffLine size={18} className="text-primary" /> <RiGithubFill size={18} className="text-primary" />
Modo Privacidade 100% Open Source
</div>
<div className="flex items-center gap-2">
<RiDeviceLine size={18} className="text-primary" />
100% Responsivo
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</section> </section>
{/* Features Section */} {/* What's Here Section */}
<section className="py-16 md:py-24 bg-muted/30"> <section id="funcionalidades" className="py-16 md:py-24 bg-muted/30">
<div className="container"> <div className="container">
<div className="mx-auto max-w-5xl"> <div className="mx-auto max-w-5xl">
<div className="text-center mb-12"> <div className="text-center mb-12">
<Badge variant="secondary" className="mb-4"> <Badge variant="primary" className="mb-4">
Funcionalidades O que tem aqui
</Badge> </Badge>
<h2 className="text-3xl md:text-4xl font-bold tracking-tight mb-4"> <h2 className="text-3xl md:text-4xl font-bold tracking-tight mb-4">
Tudo que você precisa para gerenciar suas finanças Funcionalidades que importam
</h2> </h2>
<p className="text-lg text-muted-foreground max-w-2xl mx-auto"> <p className="text-lg text-muted-foreground max-w-2xl mx-auto">
Ferramentas poderosas e intuitivas para controle financeiro Ferramentas simples para organizar suas contas, cartões, gastos
completo e receitas
</p> </p>
</div> </div>
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3"> <div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
<Card className="border-2 hover:border-primary/50 transition-colors"> <Card className="border-[1.5px] hover:border-primary/50 transition-colors">
<CardContent className="pt-6"> <CardContent className="pt-6">
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">
<div className="flex h-12 w-12 items-center justify-center rounded-lg bg-primary/10"> <div className="flex h-12 w-12 items-center justify-center rounded-lg bg-primary/10">
@@ -142,18 +179,38 @@ export default async function Page() {
</div> </div>
<div> <div>
<h3 className="font-semibold text-lg mb-2"> <h3 className="font-semibold text-lg mb-2">
Lançamentos Contas e transações
</h3> </h3>
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">
Registre receitas e despesas com categorização Registre suas contas bancárias, cartões e dinheiro.
automática e controle detalhado de pagadores e contas. Adicione receitas, despesas e transferências. Organize
por categorias.
</p> </p>
</div> </div>
</div> </div>
</CardContent> </CardContent>
</Card> </Card>
<Card className="border-2 hover:border-primary/50 transition-colors"> <Card className="border-[1.5px] hover:border-primary/50 transition-colors">
<CardContent className="pt-6">
<div className="flex flex-col gap-4">
<div className="flex h-12 w-12 items-center justify-center rounded-lg bg-primary/10">
<RiBarChartBoxLine size={24} className="text-primary" />
</div>
<div>
<h3 className="font-semibold text-lg mb-2">
Relatórios e gráficos
</h3>
<p className="text-sm text-muted-foreground">
Dashboard com resumo mensal. Gráficos de evolução do
patrimônio. Entenda pra onde seu dinheiro está indo.
</p>
</div>
</div>
</CardContent>
</Card>
<Card className="border-[1.5px] hover:border-primary/50 transition-colors">
<CardContent className="pt-6"> <CardContent className="pt-6">
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">
<div className="flex h-12 w-12 items-center justify-center rounded-lg bg-primary/10"> <div className="flex h-12 w-12 items-center justify-center rounded-lg bg-primary/10">
@@ -161,35 +218,38 @@ export default async function Page() {
</div> </div>
<div> <div>
<h3 className="font-semibold text-lg mb-2"> <h3 className="font-semibold text-lg mb-2">
Cartões de Crédito Faturas de cartão
</h3> </h3>
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">
Gerencie múltiplos cartões, acompanhe faturas, limites e Cadastre seus cartões e acompanhe as faturas. Veja o que
nunca perca o controle dos gastos. ainda não foi fechado. Controle limites e vencimentos.
</p> </p>
</div> </div>
</div> </div>
</CardContent> </CardContent>
</Card> </Card>
<Card className="border-2 hover:border-primary/50 transition-colors"> <Card className="border-[1.5px] hover:border-primary/50 transition-colors">
<CardContent className="pt-6"> <CardContent className="pt-6">
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">
<div className="flex h-12 w-12 items-center justify-center rounded-lg bg-primary/10"> <div className="flex h-12 w-12 items-center justify-center rounded-lg bg-primary/10">
<RiPieChartLine size={24} className="text-primary" /> <RiPieChartLine size={24} className="text-primary" />
</div> </div>
<div> <div>
<h3 className="font-semibold text-lg mb-2">Categorias</h3> <h3 className="font-semibold text-lg mb-2">
Categorias personalizadas
</h3>
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">
Organize suas transações em categorias personalizadas e Crie e organize suas próprias categorias. Moradia,
visualize onde seu dinheiro está indo. alimentação, transporte, ou o que fizer sentido pra
você.
</p> </p>
</div> </div>
</div> </div>
</CardContent> </CardContent>
</Card> </Card>
<Card className="border-2 hover:border-primary/50 transition-colors"> <Card className="border-[1.5px] hover:border-primary/50 transition-colors">
<CardContent className="pt-6"> <CardContent className="pt-6">
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">
<div className="flex h-12 w-12 items-center justify-center rounded-lg bg-primary/10"> <div className="flex h-12 w-12 items-center justify-center rounded-lg bg-primary/10">
@@ -201,41 +261,26 @@ export default async function Page() {
<div> <div>
<h3 className="font-semibold text-lg mb-2">Orçamentos</h3> <h3 className="font-semibold text-lg mb-2">Orçamentos</h3>
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">
Defina limites de gastos por categoria e receba alertas Defina quanto quer gastar por categoria no mês.
para manter suas finanças no caminho certo. Acompanhe se está dentro do planejado.
</p> </p>
</div> </div>
</div> </div>
</CardContent> </CardContent>
</Card> </Card>
<Card className="border-2 hover:border-primary/50 transition-colors"> <Card className="border-[1.5px] hover:border-primary/50 transition-colors">
<CardContent className="pt-6">
<div className="flex flex-col gap-4">
<div className="flex h-12 w-12 items-center justify-center rounded-lg bg-primary/10">
<RiBarChartBoxLine size={24} className="text-primary" />
</div>
<div>
<h3 className="font-semibold text-lg mb-2">Insights</h3>
<p className="text-sm text-muted-foreground">
Análise detalhada de padrões de gastos com gráficos e
relatórios para decisões mais inteligentes.
</p>
</div>
</div>
</CardContent>
</Card>
<Card className="border-2 hover:border-primary/50 transition-colors">
<CardContent className="pt-6"> <CardContent className="pt-6">
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">
<div className="flex h-12 w-12 items-center justify-center rounded-lg bg-primary/10"> <div className="flex h-12 w-12 items-center justify-center rounded-lg bg-primary/10">
<RiCalendarLine size={24} className="text-primary" /> <RiCalendarLine size={24} className="text-primary" />
</div> </div>
<div> <div>
<h3 className="font-semibold text-lg mb-2">Calendário</h3> <h3 className="font-semibold text-lg mb-2">
Calendário financeiro
</h3>
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">
Visualize suas transações em calendário mensal e nunca Visualize suas transações em calendário mensal. Nunca
perca prazos importantes. perca prazos importantes.
</p> </p>
</div> </div>
@@ -247,167 +292,343 @@ export default async function Page() {
</div> </div>
</section> </section>
{/* Benefits Section */} {/* Tech Stack Section */}
<section className="py-16 md:py-24"> <section id="stack" className="py-16 md:py-24">
<div className="container"> <div className="container">
<div className="mx-auto max-w-5xl"> <div className="mx-auto max-w-5xl">
<div className="grid gap-12 lg:grid-cols-2 items-center"> <div className="text-center mb-12">
<div> <Badge variant="primary" className="mb-4">
<Badge variant="secondary" className="mb-4"> Stack técnica
Vantagens </Badge>
</Badge> <h2 className="text-3xl md:text-4xl font-bold tracking-tight mb-4">
<h2 className="text-3xl md:text-4xl font-bold tracking-tight mb-6"> Construído com tecnologias modernas
Controle financeiro descomplicado </h2>
</h2> <p className="text-lg text-muted-foreground max-w-2xl mx-auto">
<div className="space-y-6"> Open source, self-hosted e fácil de customizar
<div className="flex gap-4"> </p>
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-primary/10"> </div>
<RiShieldCheckLine size={20} className="text-primary" />
</div> <div className="grid gap-6 md:grid-cols-2">
<Card className="border-[1.5px]">
<CardContent>
<div className="flex items-start gap-4">
<RiCodeSSlashLine
size={32}
className="text-primary shrink-0"
/>
<div> <div>
<h3 className="font-semibold mb-1"> <h3 className="font-semibold text-lg mb-2">Frontend</h3>
Segurança em Primeiro Lugar <p className="text-sm text-muted-foreground mb-3">
</h3> Next.js 16, TypeScript, Tailwind CSS, shadcn/ui
<p className="text-sm text-muted-foreground"> </p>
Seus dados financeiros são criptografados e armazenados <p className="text-xs text-muted-foreground">
com os mais altos padrões de segurança. Interface moderna e responsiva com React 19 e App Router
</p> </p>
</div> </div>
</div> </div>
</CardContent>
</Card>
<Card className="border-[1.5px]">
<CardContent>
<div className="flex items-start gap-4">
<RiDatabase2Line
size={32}
className="text-primary shrink-0"
/>
<div>
<h3 className="font-semibold text-lg mb-2">Backend</h3>
<p className="text-sm text-muted-foreground mb-3">
PostgreSQL 18, Drizzle ORM, Better Auth
</p>
<p className="text-xs text-muted-foreground">
Banco relacional robusto com type-safe ORM
</p>
</div>
</div>
</CardContent>
</Card>
<Card className="border-[1.5px]">
<CardContent>
<div className="flex items-start gap-4">
<RiShieldCheckLine
size={32}
className="text-primary shrink-0"
/>
<div>
<h3 className="font-semibold text-lg mb-2">Segurança</h3>
<p className="text-sm text-muted-foreground mb-3">
Better Auth com OAuth (Google) e autenticação por email
</p>
<p className="text-xs text-muted-foreground">
Sessões seguras e proteção de rotas por middleware
</p>
</div>
</div>
</CardContent>
</Card>
<Card className="border-[1.5px]">
<CardContent>
<div className="flex items-start gap-4">
<RiDeviceLine size={32} className="text-primary shrink-0" />
<div>
<h3 className="font-semibold text-lg mb-2">Deploy</h3>
<p className="text-sm text-muted-foreground mb-3">
Docker com multi-stage build, health checks e volumes
persistentes
</p>
<p className="text-xs text-muted-foreground">
Fácil de rodar localmente ou em qualquer servidor
</p>
</div>
</div>
</CardContent>
</Card>
</div>
<div className="mt-8 text-center">
<p className="text-sm text-muted-foreground">
Seus dados ficam no seu controle. Pode rodar localmente ou no
seu próprio servidor.
</p>
</div>
</div>
</div>
</section>
{/* How to run Section */}
<section id="como-usar" className="py-16 md:py-24">
<div className="container">
<div className="mx-auto max-w-3xl">
<div className="text-center mb-12">
<Badge variant="primary" className="mb-4">
Como usar
</Badge>
<h2 className="text-3xl md:text-4xl font-bold tracking-tight mb-4">
Rode no seu computador
</h2>
<p className="text-lg text-muted-foreground">
Não versão hospedada online. Você precisa rodar localmente.
</p>
</div>
<div className="space-y-6">
<Card className="border-[1.5px]">
<CardContent>
<div className="flex gap-4">
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-primary text-primary-foreground font-bold">
1
</div>
<div>
<h3 className="font-semibold mb-2">
Clone o repositório
</h3>
<code className="text-sm bg-muted px-2 py-1 rounded">
git clone
https://github.com/felipegcoutinho/opensheets-app.git
</code>
</div>
</div>
</CardContent>
</Card>
<Card className="border-[1.5px]">
<CardContent>
<div className="flex gap-4">
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-primary text-primary-foreground font-bold">
2
</div>
<div>
<h3 className="font-semibold mb-2">
Configure as variáveis de ambiente
</h3>
<p className="text-sm text-muted-foreground">
Copie o{" "}
<code className="bg-muted px-1 rounded">
.env.example
</code>{" "}
para <code className="bg-muted px-1 rounded">.env</code>{" "}
e configure o banco de dados
</p>
</div>
</div>
</CardContent>
</Card>
<Card className="border-[1.5px]">
<CardContent>
<div className="flex gap-4">
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-primary text-primary-foreground font-bold">
3
</div>
<div>
<h3 className="font-semibold mb-2">
Suba o banco via Docker
</h3>
<code className="text-sm bg-muted px-2 py-1 rounded">
docker compose up db -d
</code>
</div>
</div>
</CardContent>
</Card>
<Card className="border-[1.5px]">
<CardContent>
<div className="flex gap-4">
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-primary text-primary-foreground font-bold">
4
</div>
<div>
<h3 className="font-semibold mb-2">
Rode a aplicação localmente
</h3>
<div className="space-y-2">
<code className="block text-sm bg-muted px-2 py-1 rounded">
pnpm install
</code>
<code className="block text-sm bg-muted px-2 py-1 rounded">
pnpm db:push
</code>
<code className="block text-sm bg-muted px-2 py-1 rounded">
pnpm dev
</code>
</div>
</div>
</div>
</CardContent>
</Card>
</div>
<div className="mt-8 text-center">
<Link
href="https://github.com/felipegcoutinho/opensheets-app#-início-rápido"
target="_blank"
className="text-sm text-primary hover:underline"
>
Ver documentação completa
</Link>
</div>
</div>
</div>
</section>
{/* Who is this for Section */}
<section className="py-16 md:py-24 bg-muted/30">
<div className="container">
<div className="mx-auto max-w-3xl">
<div className="text-center mb-12">
<h2 className="text-3xl md:text-4xl font-bold tracking-tight mb-4">
Para quem funciona?
</h2>
<p className="text-lg text-muted-foreground">
O OpenSheets funciona melhor se você:
</p>
</div>
<div className="space-y-4">
<Card className="border-[1.5px]">
<CardContent>
<div className="flex gap-4"> <div className="flex gap-4">
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-primary/10"> <div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-primary/10">
<RiTimeLine size={20} className="text-primary" /> <RiTimeLine size={20} className="text-primary" />
</div> </div>
<div> <div>
<h3 className="font-semibold mb-1">Economize Tempo</h3> <h3 className="font-semibold mb-1">
Tem disciplina de registrar gastos
</h3>
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">
Interface intuitiva que permite registrar transações em Não se importa em dedicar alguns minutos por dia ou
segundos e acompanhar tudo de forma visual. semana para manter tudo atualizado
</p> </p>
</div> </div>
</div> </div>
</CardContent>
</Card>
<Card className="border-[1.5px]">
<CardContent>
<div className="flex gap-4"> <div className="flex gap-4">
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-primary/10"> <div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-primary/10">
<RiNotificationLine size={20} className="text-primary" /> <RiLockLine size={20} className="text-primary" />
</div> </div>
<div> <div>
<h3 className="font-semibold mb-1"> <h3 className="font-semibold mb-1">
Alertas Inteligentes Quer controle total sobre seus dados
</h3> </h3>
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">
Receba notificações sobre vencimentos, limites de Prefere hospedar seus próprios dados ao invés de
orçamento e padrões incomuns de gastos. depender de serviços terceiros
</p> </p>
</div> </div>
</div> </div>
</CardContent>
</Card>
<Card className="border-[1.5px]">
<CardContent>
<div className="flex gap-4"> <div className="flex gap-4">
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-primary/10"> <div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-primary/10">
<RiEyeOffLine size={20} className="text-primary" /> <RiLineChartLine size={20} className="text-primary" />
</div> </div>
<div> <div>
<h3 className="font-semibold mb-1">Modo Privacidade</h3> <h3 className="font-semibold mb-1">
Gosta de entender exatamente onde o dinheiro vai
</h3>
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">
Oculte valores sensíveis com um clique para visualizar Quer visualizar padrões de gastos e tomar decisões
suas finanças em qualquer lugar com discrição. informadas
</p> </p>
</div> </div>
</div> </div>
</div> </CardContent>
</div> </Card>
</div>
<div className="space-y-4"> <div className="mt-8 rounded-lg border bg-background p-6 text-center">
<Card className="border-2"> <p className="text-sm text-muted-foreground">
<CardContent className="pt-6"> Se você não se encaixa nisso, provavelmente vai abandonar depois
<div className="flex items-start gap-4"> de uma semana. E tudo bem! Existem outras ferramentas com
<RiLineChartLine sincronização automática que podem funcionar melhor pra você.
size={32} </p>
className="text-primary shrink-0"
/>
<div>
<h3 className="font-semibold text-lg mb-2">
Visualização Clara
</h3>
<p className="text-sm text-muted-foreground">
Gráficos interativos e dashboards personalizáveis
mostram sua situação financeira de forma clara e
objetiva.
</p>
</div>
</div>
</CardContent>
</Card>
<Card className="border-2">
<CardContent className="pt-6">
<div className="flex items-start gap-4">
<RiDeviceLine
size={32}
className="text-primary shrink-0"
/>
<div>
<h3 className="font-semibold text-lg mb-2">
Acesso em Qualquer Lugar
</h3>
<p className="text-sm text-muted-foreground">
Design responsivo que funciona perfeitamente em
desktop, tablet e smartphone. Suas finanças sempre à
mão.
</p>
</div>
</div>
</CardContent>
</Card>
<Card className="border-2">
<CardContent className="pt-6">
<div className="flex items-start gap-4">
<RiLockLine size={32} className="text-primary shrink-0" />
<div>
<h3 className="font-semibold text-lg mb-2">
Privacidade Garantida
</h3>
<p className="text-sm text-muted-foreground">
Seus dados são seus. Sem compartilhamento com
terceiros, sem anúncios, sem surpresas.
</p>
</div>
</div>
</CardContent>
</Card>
</div>
</div> </div>
</div> </div>
</div> </div>
</section> </section>
{/* CTA Section */} {/* CTA Section */}
<section className="py-16 md:py-24 bg-muted/30"> <section className="py-16 md:py-24">
<div className="container"> <div className="container">
<div className="mx-auto max-w-3xl text-center"> <div className="mx-auto max-w-3xl text-center">
<h2 className="text-3xl md:text-4xl font-bold tracking-tight mb-4"> <h2 className="text-3xl md:text-4xl font-bold tracking-tight mb-4">
Pronto para transformar suas finanças? Pronto para testar?
</h2> </h2>
<p className="text-lg text-muted-foreground mb-8"> <p className="text-lg text-muted-foreground mb-8">
Comece agora mesmo a organizar seu dinheiro de forma inteligente. Clone o repositório, rode localmente e veja se faz sentido pra
É grátis e leva menos de um minuto. você. É open source e gratuito.
</p> </p>
<div className="flex flex-col sm:flex-row gap-4 justify-center"> <div className="flex flex-col sm:flex-row gap-4 justify-center">
<Link href="/signup"> <Link
href="https://github.com/felipegcoutinho/opensheets-app"
target="_blank"
>
<Button size="lg" className="gap-2 w-full sm:w-auto"> <Button size="lg" className="gap-2 w-full sm:w-auto">
Criar Conta Gratuita <RiGithubFill size={18} />
<RiArrowRightSLine size={18} /> Baixar Projeto
</Button> </Button>
</Link> </Link>
<Link href="/login"> <Link
href="https://github.com/felipegcoutinho/opensheets-app#-início-rápido"
target="_blank"
>
<Button <Button
size="lg" size="lg"
variant="outline" variant="outline"
className="w-full sm:w-auto" className="w-full sm:w-auto gap-2"
> >
tenho conta Como Instalar
</Button> </Button>
</Link> </Link>
</div> </div>
@@ -418,113 +639,70 @@ export default async function Page() {
{/* Footer */} {/* Footer */}
<footer className="border-t py-12 mt-auto"> <footer className="border-t py-12 mt-auto">
<div className="container"> <div className="container">
<div className="grid gap-8 md:grid-cols-2 lg:grid-cols-4"> <div className="mx-auto max-w-5xl">
<div> <div className="grid gap-8 md:grid-cols-3">
<Logo /> <div>
<p className="text-sm text-muted-foreground mt-4"> <Logo />
Gerencie suas finanças pessoais com simplicidade e segurança. <p className="text-sm text-muted-foreground mt-4">
Projeto pessoal de gestão financeira. Open source e
self-hosted.
</p>
</div>
<div>
<h3 className="font-semibold mb-4">Projeto</h3>
<ul className="space-y-3 text-sm text-muted-foreground">
<li>
<Link
href="https://github.com/felipegcoutinho/opensheets"
target="_blank"
className="hover:text-foreground transition-colors flex items-center gap-2"
>
<RiGithubFill size={16} />
GitHub
</Link>
</li>
<li>
<Link
href="https://github.com/felipegcoutinho/opensheets#readme"
target="_blank"
className="hover:text-foreground transition-colors"
>
Documentação
</Link>
</li>
<li>
<Link
href="https://github.com/felipegcoutinho/opensheets/issues"
target="_blank"
className="hover:text-foreground transition-colors"
>
Reportar Bug
</Link>
</li>
</ul>
</div>
<div>
<h3 className="font-semibold mb-4">Stack</h3>
<ul className="space-y-3 text-sm text-muted-foreground">
<li>Next.js 16 + TypeScript</li>
<li>PostgreSQL 18 + Drizzle ORM</li>
<li>Better Auth + shadcn/ui</li>
<li>Docker + Docker Compose</li>
</ul>
</div>
</div>
<div className="border-t mt-12 pt-8 flex flex-col md:flex-row justify-between items-center gap-4 text-sm text-muted-foreground">
<p>
© {new Date().getFullYear()} OpenSheets. Projeto open source sob
licença MIT.
</p> </p>
</div> <div className="flex items-center gap-2">
<RiShieldCheckLine size={16} className="text-primary" />
<div> <span>Seus dados, seu servidor</span>
<h3 className="font-semibold mb-4">Produto</h3> </div>
<ul className="space-y-3 text-sm text-muted-foreground">
<li>
<Link
href="/login"
className="hover:text-foreground transition-colors"
>
Funcionalidades
</Link>
</li>
<li>
<Link
href="/login"
className="hover:text-foreground transition-colors"
>
Preços
</Link>
</li>
<li>
<Link
href="/login"
className="hover:text-foreground transition-colors"
>
Segurança
</Link>
</li>
</ul>
</div>
<div>
<h3 className="font-semibold mb-4">Recursos</h3>
<ul className="space-y-3 text-sm text-muted-foreground">
<li>
<Link
href="/login"
className="hover:text-foreground transition-colors"
>
Blog
</Link>
</li>
<li>
<Link
href="/login"
className="hover:text-foreground transition-colors"
>
Ajuda
</Link>
</li>
<li>
<Link
href="/login"
className="hover:text-foreground transition-colors"
>
Tutoriais
</Link>
</li>
</ul>
</div>
<div>
<h3 className="font-semibold mb-4">Legal</h3>
<ul className="space-y-3 text-sm text-muted-foreground">
<li>
<Link
href="/login"
className="hover:text-foreground transition-colors"
>
Privacidade
</Link>
</li>
<li>
<Link
href="/login"
className="hover:text-foreground transition-colors"
>
Termos de Uso
</Link>
</li>
<li>
<Link
href="/login"
className="hover:text-foreground transition-colors"
>
Cookies
</Link>
</li>
</ul>
</div>
</div>
<div className="border-t mt-12 pt-8 flex flex-col md:flex-row justify-between items-center gap-4 text-sm text-muted-foreground">
<p>
© {new Date().getFullYear()} OpenSheets. Todos os direitos
reservados.
</p>
<div className="flex items-center gap-2">
<RiShieldCheckLine size={16} className="text-primary" />
<span>Seus dados são protegidos e criptografados</span>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -178,6 +178,11 @@
@apply border-border outline-ring/50; @apply border-border outline-ring/50;
} }
html {
scroll-behavior: smooth;
scroll-padding-top: 80px; /* Offset para o header sticky */
}
body { body {
@apply bg-background text-foreground; @apply bg-background text-foreground;
} }

View File

@@ -4,6 +4,7 @@ import { Toaster } from "@/components/ui/sonner";
import { main_font } from "@/public/fonts/font_index"; import { main_font } from "@/public/fonts/font_index";
import type { Metadata } from "next"; import type { Metadata } from "next";
import "./globals.css"; import "./globals.css";
import { Analytics } from "@vercel/analytics/next";
export const metadata: Metadata = { export const metadata: Metadata = {
title: "OpenSheets", title: "OpenSheets",
@@ -17,13 +18,6 @@ export default function RootLayout({
}>) { }>) {
return ( return (
<html lang="en" suppressHydrationWarning> <html lang="en" suppressHydrationWarning>
<head>
<script
defer
src="https://umami.felipecoutinho.com/script.js"
data-website-id="42f8519e-de88-467e-8969-d13a76211e43"
></script>
</head>
<body <body
className={`${main_font.className} antialiased`} className={`${main_font.className} antialiased`}
suppressHydrationWarning suppressHydrationWarning
@@ -34,6 +28,7 @@ export default function RootLayout({
<Toaster position="top-right" /> <Toaster position="top-right" />
</PrivacyProvider> </PrivacyProvider>
</ThemeProvider> </ThemeProvider>
<Analytics />
</body> </body>
</html> </html>
); );

View File

@@ -7,7 +7,7 @@ function Card({ className, ...props }: React.ComponentProps<"div">) {
<div <div
data-slot="card" data-slot="card"
className={cn( className={cn(
"bg-card text-card-foreground flex flex-col gap-6 border drop-shadow-xs py-6 rounded-md hover:border-primary/50 transition-colors", "bg-card text-card-foreground flex flex-col gap-6 border-[1.5px] drop-shadow-xs py-6 rounded-md hover:border-primary/50 transition-colors",
className className
)} )}
{...props} {...props}

View File

@@ -14,7 +14,8 @@
"db:push": "drizzle-kit push", "db:push": "drizzle-kit push",
"db:studio": "drizzle-kit studio", "db:studio": "drizzle-kit studio",
"docker:up": "docker compose up --build", "docker:up": "docker compose up --build",
"docker:up:detached": "docker compose up --build -d", "docker:up:db": "docker compose up -d db",
"docker:up:d": "docker compose up --build -d",
"docker:down": "docker compose down", "docker:down": "docker compose down",
"docker:down:volumes": "docker compose down -v", "docker:down:volumes": "docker compose down -v",
"docker:logs": "docker compose logs -f", "docker:logs": "docker compose logs -f",
@@ -27,10 +28,6 @@
"@ai-sdk/anthropic": "^2.0.44", "@ai-sdk/anthropic": "^2.0.44",
"@ai-sdk/google": "^2.0.31", "@ai-sdk/google": "^2.0.31",
"@ai-sdk/openai": "^2.0.66", "@ai-sdk/openai": "^2.0.66",
"@dnd-kit/core": "6.3.1",
"@dnd-kit/modifiers": "9.0.0",
"@dnd-kit/sortable": "10.0.0",
"@dnd-kit/utilities": "3.2.2",
"@openrouter/ai-sdk-provider": "^1.2.2", "@openrouter/ai-sdk-provider": "^1.2.2",
"@radix-ui/react-alert-dialog": "1.1.15", "@radix-ui/react-alert-dialog": "1.1.15",
"@radix-ui/react-avatar": "1.1.11", "@radix-ui/react-avatar": "1.1.11",
@@ -53,6 +50,7 @@
"@radix-ui/react-tooltip": "1.2.8", "@radix-ui/react-tooltip": "1.2.8",
"@remixicon/react": "4.7.0", "@remixicon/react": "4.7.0",
"@tanstack/react-table": "8.21.3", "@tanstack/react-table": "8.21.3",
"@vercel/analytics": "^1.5.0",
"ai": "^5.0.93", "ai": "^5.0.93",
"better-auth": "1.3.34", "better-auth": "1.3.34",
"class-variance-authority": "0.7.1", "class-variance-authority": "0.7.1",
@@ -80,6 +78,7 @@
"@types/node": "24.10.1", "@types/node": "24.10.1",
"@types/react": "19.2.4", "@types/react": "19.2.4",
"@types/react-dom": "19.2.3", "@types/react-dom": "19.2.3",
"depcheck": "^1.4.7",
"drizzle-kit": "0.31.7", "drizzle-kit": "0.31.7",
"eslint": "9.39.1", "eslint": "9.39.1",
"eslint-config-next": "16.0.3", "eslint-config-next": "16.0.3",

564
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

4
pnpm-workspace.yaml Normal file
View File

@@ -0,0 +1,4 @@
onlyBuiltDependencies:
- esbuild
- sharp
- unrs-resolver