diff --git a/public/images/dashboard-preview-light.webp b/public/images/dashboard-preview-light.webp index 77acca2..d3ebf36 100644 Binary files a/public/images/dashboard-preview-light.webp and b/public/images/dashboard-preview-light.webp differ diff --git a/public/images/preview-lancamentos-light.webp b/public/images/preview-lancamentos-light.webp index 45a09f0..391a3e7 100644 Binary files a/public/images/preview-lancamentos-light.webp and b/public/images/preview-lancamentos-light.webp differ diff --git a/src/app/(landing-page)/page.tsx b/src/app/(landing-page)/page.tsx index c625f7c..54699c8 100644 --- a/src/app/(landing-page)/page.tsx +++ b/src/app/(landing-page)/page.tsx @@ -12,6 +12,7 @@ import { RiFlashlightLine, RiGitBranchLine, RiGithubFill, + RiInformationLine, RiLayoutGridLine, RiLineChartLine, RiLockLine, @@ -29,89 +30,135 @@ import { import { headers } from "next/headers"; import Image from "next/image"; import Link from "next/link"; +import type { ComponentType } from "react"; import { AnimateOnScroll } from "@/features/landing/components/animate-on-scroll"; import { MobileNav } from "@/features/landing/components/mobile-nav"; import { SetupTabs } from "@/features/landing/components/setup-tabs"; import { AnimatedThemeToggler } from "@/shared/components/animated-theme-toggler"; import { Logo } from "@/shared/components/logo"; +import { + Alert, + AlertDescription, + AlertTitle, +} from "@/shared/components/ui/alert"; import { Badge } from "@/shared/components/ui/badge"; import { Button } from "@/shared/components/ui/button"; import { Card, CardContent } from "@/shared/components/ui/card"; +import { DotPattern } from "@/shared/components/ui/dot-pattern"; import { getOptionalUserSession } from "@/shared/lib/auth/server"; -const mainFeatures = [ +async function fetchGitHubStats() { + try { + const res = await fetch( + "https://api.github.com/repos/felipegcoutinho/openmonetis", + { next: { revalidate: 3600 } }, + ); + if (!res.ok) return { stars: 200, forks: 60 }; + const data = await res.json(); + return { + stars: data.stargazers_count as number, + forks: data.forks_count as number, + }; + } catch { + return { stars: 200, forks: 60 }; + } +} + +const navbarActionClassName = + "border-black/10 bg-transparent text-black/75 shadow-none hover:border-black/20 hover:bg-black/10 hover:text-black focus-visible:ring-black/20 data-[state=open]:bg-black/10 data-[state=open]:text-black"; + +type FeatureItem = { + icon: ComponentType<{ className?: string; style?: React.CSSProperties }>; + title: string; + description: string; + colorVar: string; +}; + +const mainFeatures: FeatureItem[] = [ { icon: RiWalletLine, title: "Contas e transações", description: "Registre suas contas bancárias, cartões e dinheiro. Adicione receitas, despesas e transferências. Organize por categorias. Extratos detalhados por conta.", + colorVar: "var(--data-9)", }, { icon: RiPercentLine, title: "Parcelamentos avançados", description: "Controle completo de compras parceladas. Antecipe parcelas com cálculo automático de desconto. Veja análise consolidada de todas as parcelas em aberto.", + colorVar: "var(--data-4)", }, { icon: RiRobot2Line, title: "Insights com IA", description: "Análises financeiras geradas por IA (Claude, GPT, Gemini). Insights personalizados sobre seus padrões de gastos e recomendações inteligentes.", + colorVar: "var(--data-8)", }, { icon: RiBarChartBoxLine, title: "Relatórios e gráficos", description: "Dashboard com 20+ widgets interativos. Relatórios detalhados por categoria. Gráficos de evolução e comparativos. Exportação em PDF e Excel.", + colorVar: "var(--data-5)", }, { icon: RiBankCard2Line, title: "Faturas de cartão", description: "Cadastre seus cartões e acompanhe as faturas por período. Veja o que ainda não foi fechado. Controle limites, vencimentos e fechamentos.", + colorVar: "var(--data-1)", }, { icon: RiTeamLine, title: "Gestão colaborativa", description: "Compartilhe pagadores com permissões granulares (admin/viewer). Notificações automáticas por e-mail. Colabore em lançamentos compartilhados.", + colorVar: "var(--data-3)", }, ]; -const extraFeatures = [ +const extraFeatures: FeatureItem[] = [ { icon: RiPieChartLine, title: "Categorias e orçamentos", description: "Crie categorias personalizadas e defina orçamentos mensais com indicadores visuais.", + colorVar: "var(--data-7)", }, { icon: RiFileTextLine, title: "Anotações e tarefas", description: "Notas de texto e listas de tarefas com checkboxes. Arquivamento para manter histórico.", + colorVar: "var(--data-6)", }, { icon: RiCalendarLine, title: "Calendário financeiro", description: "Visualize transações em calendário mensal. Nunca perca prazos de pagamentos.", + colorVar: "var(--data-2)", }, { icon: RiDownloadCloudLine, title: "Importação em massa", description: "Lance múltiplos lançamentos de uma vez", + colorVar: "var(--data-9)", }, { icon: RiEyeOffLine, title: "Modo privacidade", description: "Oculte valores sensíveis com um clique. Tema dark/light. Calculadora integrada.", + colorVar: "var(--data-4)", }, { icon: RiFlashlightLine, title: "Performance otimizada", description: "Sistema rápido e com alta performance", + colorVar: "var(--data-5)", }, ]; @@ -136,12 +183,75 @@ const screenshotSections = [ }, ]; -const companionBanks = ["Nubank", "Itaú", "Inter", "Mercado Pago", "Outros"]; +const companionBanks = [ + { name: "Nubank", logo: "/logos/nubank.png" }, + { name: "Itaú", logo: "/logos/itau.png" }, + { name: "Inter", logo: "/logos/interpj.png" }, + { name: "Mercado Pago", logo: "/logos/mercadopago.png" }, + { name: "Outros", logo: null }, +]; + +const stackItems = [ + { + icon: RiCodeSSlashLine, + title: "Frontend", + subtitle: "Next.js 16, TypeScript, Tailwind CSS, shadcn/ui", + description: "Interface moderna e responsiva com React 19 e App Router", + colorVar: "var(--data-3)", + }, + { + icon: RiDatabase2Line, + title: "Backend", + subtitle: "PostgreSQL 18, Drizzle ORM, Better Auth", + description: "Banco relacional robusto com type-safe ORM", + colorVar: "var(--data-9)", + }, + { + icon: RiShieldCheckLine, + title: "Segurança", + subtitle: "Better Auth com OAuth (Google) e autenticação por email", + description: "Sessões seguras e proteção de rotas por middleware", + colorVar: "var(--data-1)", + }, + { + icon: RiDeviceLine, + title: "Deploy", + subtitle: + "Docker com multi-stage build, health checks e volumes persistentes", + description: "Fácil de rodar localmente ou em qualquer servidor", + colorVar: "var(--data-5)", + }, +]; + +const whoIsItForItems = [ + { + icon: RiTimeLine, + title: "Tem disciplina de registrar gastos", + description: + "Não se importa em dedicar alguns minutos por dia ou semana para manter tudo atualizado", + colorVar: "var(--data-4)", + }, + { + icon: RiLockLine, + title: "Quer controle total sobre seus dados", + description: + "Prefere hospedar seus próprios dados ao invés de depender de serviços terceiros", + colorVar: "var(--data-9)", + }, + { + icon: RiLineChartLine, + title: "Gosta de entender exatamente onde o dinheiro vai", + description: + "Quer visualizar padrões de gastos e tomar decisões informadas", + colorVar: "var(--data-3)", + }, +]; export default async function Page() { - const [session, headersList] = await Promise.all([ + const [session, headersList, githubStats] = await Promise.all([ getOptionalUserSession(), headers(), + fetchGitHubStats(), ]); const hostname = headersList.get("host")?.replace(/:\d+$/, ""); const publicDomain = process.env.PUBLIC_DOMAIN?.replace( @@ -153,62 +263,62 @@ export default async function Page() { return (
{/* Navigation */} -
-
- +
+
+
+
+ +
+ {/* Center Navigation Links */} -
- {/* Hero Section */} -
- {/* Background gradient */} -
+ {/* Hero Section — texto + preview integrado */} +
+ {/* Background — DotPattern fade conectando com navbar */} +
+ +
+
-
- - - - + {/* Texto */} +
+ + Projeto Open Source @@ -242,30 +362,22 @@ export default async function Page() {

- Um projeto pessoal de gestão financeira. Self-hosted, sem Open - Finance, sem sincronização automática. Rode no seu computador ou - servidor e tenha controle total sobre suas finanças. + Gestão financeira self-hosted e open source. Lance manualmente ou + capture notificações bancárias direto pelo{" "} + + Companion para Android + + . Seus dados, seu servidor.

-
-

- - Aviso importante: - {" "} - Este sistema requer disciplina. Você precisa registrar - manualmente cada transação. Se prefere algo automático, este - projeto não é pra você. -

-
- -
+
@@ -283,16 +395,33 @@ export default async function Page() {
+
-
-
- - Seus dados, seu servidor -
-
- - 100% Open Source + {/* Dashboard preview integrado ao hero */} +
+
+
+
+
+
+
+ openmonetis Dashboard Preview + openmonetis Dashboard Preview
@@ -303,82 +432,57 @@ export default async function Page() {
-
-
- + {[ + { + icon: RiLayoutGridLine, + value: "20+", + label: "Widgets no dashboard", + colorVar: "var(--data-9)", + }, + { + icon: RiShieldCheckLine, + value: "100%", + label: "Self-hosted", + colorVar: "var(--data-1)", + }, + { + icon: RiStarLine, + value: `${githubStats.stars}`, + label: "Stars no GitHub", + colorVar: "var(--data-4)", + }, + { + icon: RiGitBranchLine, + value: `${githubStats.forks}`, + label: "Forks no GitHub", + colorVar: "var(--data-3)", + }, + ].map(({ icon: Icon, value, label, colorVar }) => ( +
+ + + {value} + + + {label} +
- 20+ - - Widgets no dashboard - -
-
-
- -
- 100% - - Self-hosted - -
-
-
- -
- +200 - - Stars no GitHub - -
-
-
- -
- +60 - - Forks no GitHub - -
+ ))}
- {/* Dashboard Preview Section */} -
-
-
- -
- openmonetis Dashboard Preview - openmonetis Dashboard Preview -
-
-
-
-
- {/* Screenshots Gallery Section */}
- + Conheça as telas

@@ -401,7 +505,13 @@ export default async function Page() { {section.description}

-
+
+
+
+
+
+
+
{`Preview
- + O que tem aqui

@@ -447,16 +557,18 @@ export default async function Page() {
{mainFeatures.map((feature) => ( - +
-
+
@@ -484,10 +596,18 @@ export default async function Page() { {extraFeatures.map((feature) => (
-
- +
+

@@ -514,8 +634,8 @@ export default async function Page() {
{/* Text content */}
- - + + App Android

@@ -529,48 +649,47 @@ export default async function Page() { {/* Flow steps */}
-
-
- + {[ + { + icon: RiNotification3Line, + title: "Notificação bancária chega", + subtitle: "O Companion intercepta automaticamente", + colorVar: "var(--data-1)", + }, + { + icon: RiSmartphoneLine, + title: "Dados extraídos e enviados", + subtitle: "Valor, descrição e banco são identificados", + colorVar: "var(--data-4)", + }, + { + icon: RiCheckLine, + title: "Revise e confirme no OpenMonetis", + subtitle: + "Pré-lançamentos ficam na inbox para sua aprovação", + colorVar: "var(--data-9)", + }, + ].map((step) => ( +
+
+ +
+
+

{step.title}

+

+ {step.subtitle} +

+
-
-

- Notificação bancária chega -

-

- O Companion intercepta automaticamente -

-
-
-
-
- -
-
-

- Dados extraídos e enviados -

-

- Valor, descrição e banco são identificados -

-
-
-
-
- -
-
-

- Revise e confirme no OpenMonetis -

-

- Pré-lançamentos ficam na inbox para sua aprovação -

-
-
+ ))}
{/* Supported banks */} @@ -581,10 +700,23 @@ export default async function Page() {
{companionBanks.map((bank) => ( - {bank} + {bank.logo && ( + {bank.name} + )} + {bank.name} ))}
@@ -595,7 +727,7 @@ export default async function Page() { target="_blank" > @@ -625,7 +757,7 @@ export default async function Page() {
- + Stack técnica

@@ -639,96 +771,36 @@ export default async function Page() {
- - -
- -
-

- Frontend -

-

- Next.js 16, TypeScript, Tailwind CSS, shadcn/ui -

-

- Interface moderna e responsiva com React 19 e App - Router -

+ {stackItems.map((item) => ( + + +
+
+ +
+
+

+ {item.title} +

+

+ {item.subtitle} +

+

+ {item.description} +

+
-
- - - - - -
- -
-

- Backend -

-

- PostgreSQL 18, Drizzle ORM, Better Auth -

-

- Banco relacional robusto com type-safe ORM -

-
-
-
-
- - - -
- -
-

- Segurança -

-

- Better Auth com OAuth (Google) e autenticação por - email -

-

- Sessões seguras e proteção de rotas por middleware -

-
-
-
-
- - - -
- -
-

- Deploy -

-

- Docker com multi-stage build, health checks e volumes - persistentes -

-

- Fácil de rodar localmente ou em qualquer servidor -

-
-
-
-
+ + + ))}
@@ -748,7 +820,7 @@ export default async function Page() {
- + Como usar

@@ -783,6 +855,9 @@ export default async function Page() {
+ + Para quem é? +

Para quem funciona?

@@ -794,83 +869,45 @@ export default async function Page() {
- - -
-
- + {whoIsItForItems.map((item) => ( + + +
+
+ +
+
+

{item.title}

+

+ {item.description} +

+
-
-

- Tem disciplina de registrar gastos -

-

- Não se importa em dedicar alguns minutos por dia ou - semana para manter tudo atualizado -

-
-
- - - - - -
-
- -
-
-

- Quer controle total sobre seus dados -

-

- Prefere hospedar seus próprios dados ao invés de - depender de serviços terceiros -

-
-
-
-
- - - -
-
- -
-
-

- Gosta de entender exatamente onde o dinheiro vai -

-

- Quer visualizar padrões de gastos e tomar decisões - informadas -

-
-
-
-
+ + + ))}
-
-

+ + + Não é para todo mundo + Se você não se encaixa nisso, provavelmente vai abandonar - depois de uma semana. E tudo bem! Existem outras ferramentas + depois de uma semana. Tudo certo! Existem outras ferramentas com sincronização automática que podem funcionar melhor pra você. -

-
+ +
@@ -880,7 +917,7 @@ export default async function Page() {
-
+

Pronto para testar?

@@ -895,7 +932,7 @@ export default async function Page() { className="w-full sm:w-auto" > @@ -924,7 +961,7 @@ export default async function Page() {
- +

Projeto pessoal de gestão financeira. Open source e self-hosted. @@ -940,7 +977,7 @@ export default async function Page() { target="_blank" className="hover:text-foreground transition-colors flex items-center gap-2" > - + GitHub @@ -974,16 +1011,10 @@ export default async function Page() { target="_blank" className="hover:text-foreground transition-colors flex items-center gap-2" > - + GitHub -

  • - - - App Android - -
  • @@ -994,7 +1025,7 @@ export default async function Page() { sob licença.

    - + Seus dados, seu servidor
    diff --git a/src/features/landing/components/mobile-nav.tsx b/src/features/landing/components/mobile-nav.tsx index 4afa55c..70ba9a1 100644 --- a/src/features/landing/components/mobile-nav.tsx +++ b/src/features/landing/components/mobile-nav.tsx @@ -23,9 +23,10 @@ const navLinks = [ interface MobileNavProps { isPublicDomain: boolean; isLoggedIn: boolean; + triggerClassName?: string; } -export function MobileNav({ isPublicDomain, isLoggedIn }: MobileNavProps) { +export function MobileNav({ isPublicDomain, isLoggedIn, triggerClassName }: MobileNavProps) { const [open, setOpen] = useState(false); return ( @@ -35,8 +36,9 @@ export function MobileNav({ isPublicDomain, isLoggedIn }: MobileNavProps) { size="icon" onClick={() => setOpen(true)} aria-label="Abrir menu" + className={triggerClassName} > - +