From 31fe752b7def4482dce42d05ac33545ef22f0b31 Mon Sep 17 00:00:00 2001 From: Felipe Coutinho Date: Sat, 21 Feb 2026 17:48:52 +0000 Subject: [PATCH] feat(v1.5.3): status de pagamento no painel do pagador + SEO landing + WebP MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Card de Status de Pagamento com totais pagos/pendentes e lista de boletos individuais - Validação obrigatória de categoria/conta/cartão no dialog de lançamento (client + server) - SEO completo na landing: Open Graph, Twitter Card, JSON-LD, sitemap.xml, robots.txt - Imagens convertidas de PNG para WebP (performance) - HTML lang corrigido para pt-BR; template de título dinâmico Co-Authored-By: Claude Sonnet 4.6 --- CHANGELOG.md | 21 ++ app/(dashboard)/lancamentos/actions.ts | 24 ++ .../pagadores/[pagadorId]/page.tsx | 52 +++- app/(landing-page)/layout.tsx | 95 +++++++ app/(landing-page)/page.tsx | 18 +- app/layout.tsx | 7 +- app/robots.ts | 36 +++ app/sitemap.ts | 16 ++ .../lancamento-dialog/lancamento-dialog.tsx | 22 ++ .../details/pagador-card-usage-card.tsx | 130 +++++----- .../details/pagador-monthly-summary-card.tsx | 10 +- .../details/pagador-payment-method-cards.tsx | 235 +++++++++++------- lib/pagadores/details.ts | 108 ++++++++ package.json | 2 +- public/dashboard-preview-dark.png | Bin 933046 -> 0 bytes public/dashboard-preview-dark.webp | Bin 0 -> 166522 bytes public/dashboard-preview-light.png | Bin 930543 -> 0 bytes public/dashboard-preview-light.webp | Bin 0 -> 181782 bytes public/openmonetis_companion.png | Bin 153870 -> 0 bytes public/openmonetis_companion.webp | Bin 0 -> 65854 bytes public/preview-calendario-dark.png | Bin 925699 -> 0 bytes public/preview-calendario-dark.webp | Bin 0 -> 155292 bytes public/preview-calendario-light.png | Bin 955177 -> 0 bytes public/preview-calendario-light.webp | Bin 0 -> 164068 bytes public/preview-cartao-dark.png | Bin 858774 -> 0 bytes public/preview-cartao-dark.webp | Bin 0 -> 166234 bytes public/preview-cartao-light.png | Bin 879344 -> 0 bytes public/preview-cartao-light.webp | Bin 0 -> 173126 bytes public/preview-lancamentos-dark.png | Bin 1178421 -> 0 bytes public/preview-lancamentos-dark.webp | Bin 0 -> 267980 bytes public/preview-lancamentos-light.png | Bin 1196510 -> 0 bytes public/preview-lancamentos-light.webp | Bin 0 -> 278166 bytes 32 files changed, 600 insertions(+), 176 deletions(-) create mode 100644 app/(landing-page)/layout.tsx create mode 100644 app/robots.ts create mode 100644 app/sitemap.ts delete mode 100644 public/dashboard-preview-dark.png create mode 100644 public/dashboard-preview-dark.webp delete mode 100644 public/dashboard-preview-light.png create mode 100644 public/dashboard-preview-light.webp delete mode 100644 public/openmonetis_companion.png create mode 100644 public/openmonetis_companion.webp delete mode 100644 public/preview-calendario-dark.png create mode 100644 public/preview-calendario-dark.webp delete mode 100644 public/preview-calendario-light.png create mode 100644 public/preview-calendario-light.webp delete mode 100644 public/preview-cartao-dark.png create mode 100644 public/preview-cartao-dark.webp delete mode 100644 public/preview-cartao-light.png create mode 100644 public/preview-cartao-light.webp delete mode 100644 public/preview-lancamentos-dark.png create mode 100644 public/preview-lancamentos-dark.webp delete mode 100644 public/preview-lancamentos-light.png create mode 100644 public/preview-lancamentos-light.webp diff --git a/CHANGELOG.md b/CHANGELOG.md index 0cbfcf5..8ffd5e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,27 @@ Todas as mudanças notáveis deste projeto serão documentadas neste arquivo. O formato é baseado em [Keep a Changelog](https://keepachangelog.com/pt-BR/1.1.0/), e este projeto adere ao [Versionamento Semântico](https://semver.org/lang/pt-BR/). +## [1.5.3] - 2026-02-21 + +### Adicionado + +- Painel do pagador: card "Status de Pagamento" com totais pagos/pendentes e listagem individual de boletos com data de vencimento, data de pagamento e status +- Funções `fetchPagadorBoletoItems` e `fetchPagadorPaymentStatus` em `lib/pagadores/details.ts` +- SEO completo na landing page: metadata Open Graph, Twitter Card, JSON-LD Schema.org, sitemap.xml (`/app/sitemap.ts`) e robots.txt (`/app/robots.ts`) +- Layout específico da landing page (`app/(landing-page)/layout.tsx`) com metadados ricos + +### Corrigido + +- Validação obrigatória de categoria, conta e cartão no dialog de lançamento — agora validada no cliente (antes do submit) e no servidor via Zod +- Atributo `lang` do HTML corrigido de `en` para `pt-BR` + +### Alterado + +- Painel do pagador reorganizado em grid de 3 colunas com cards de Faturas, Boletos e Status de Pagamento +- `PagadorBoletoCard` refatorado para exibir lista de boletos individuais em vez de resumo agregado +- Imagens da landing page convertidas de PNG para WebP (melhora de performance) +- Template de título dinâmico no layout raiz (`%s | OpenMonetis`) + ## [1.5.2] - 2026-02-16 ### Alterado diff --git a/app/(dashboard)/lancamentos/actions.ts b/app/(dashboard)/lancamentos/actions.ts index 1bcdb05..b6364a8 100644 --- a/app/(dashboard)/lancamentos/actions.ts +++ b/app/(dashboard)/lancamentos/actions.ts @@ -182,6 +182,30 @@ const refineLancamento = ( data: z.infer & { id?: string }, ctx: z.RefinementCtx, ) => { + if (!data.categoriaId) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + path: ["categoriaId"], + message: "Selecione uma categoria.", + }); + } + + if (data.paymentMethod === "Cartão de crédito") { + if (!data.cartaoId) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + path: ["cartaoId"], + message: "Selecione o cartão.", + }); + } + } else if (!data.contaId) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + path: ["contaId"], + message: "Selecione a conta.", + }); + } + if (data.condition === "Parcelado") { if (!data.installmentCount) { ctx.addIssue({ diff --git a/app/(dashboard)/pagadores/[pagadorId]/page.tsx b/app/(dashboard)/pagadores/[pagadorId]/page.tsx index 462ba81..ddc7866 100644 --- a/app/(dashboard)/pagadores/[pagadorId]/page.tsx +++ b/app/(dashboard)/pagadores/[pagadorId]/page.tsx @@ -1,3 +1,8 @@ +import { + RiBankCard2Line, + RiBarcodeLine, + RiWallet3Line, +} from "@remixicon/react"; import { notFound } from "next/navigation"; import { getRecentEstablishmentsAction } from "@/app/(dashboard)/lancamentos/actions"; import { LancamentosPage as LancamentosSection } from "@/components/lancamentos/page/lancamentos-page"; @@ -13,9 +18,13 @@ import { PagadorHistoryCard } from "@/components/pagadores/details/pagador-histo import { PagadorInfoCard } from "@/components/pagadores/details/pagador-info-card"; import { PagadorLeaveShareCard } from "@/components/pagadores/details/pagador-leave-share-card"; import { PagadorMonthlySummaryCard } from "@/components/pagadores/details/pagador-monthly-summary-card"; -import { PagadorBoletoCard } from "@/components/pagadores/details/pagador-payment-method-cards"; +import { + PagadorBoletoCard, + PagadorPaymentStatusCard, +} from "@/components/pagadores/details/pagador-payment-method-cards"; import { PagadorSharingCard } from "@/components/pagadores/details/pagador-sharing-card"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import WidgetCard from "@/components/widget-card"; import type { pagadores } from "@/db/schema"; import { getUserId } from "@/lib/auth/server"; import { @@ -34,10 +43,12 @@ import { } from "@/lib/lancamentos/page-helpers"; import { getPagadorAccess } from "@/lib/pagadores/access"; import { + fetchPagadorBoletoItems, fetchPagadorBoletoStats, fetchPagadorCardUsage, fetchPagadorHistory, fetchPagadorMonthlyBreakdown, + fetchPagadorPaymentStatus, } from "@/lib/pagadores/details"; import { parsePeriodParam } from "@/lib/utils/period"; import { @@ -152,6 +163,8 @@ export default async function Page({ params, searchParams }: PageProps) { historyData, cardUsage, boletoStats, + boletoItems, + paymentStatus, shareRows, currentUserShare, estabelecimentos, @@ -177,6 +190,16 @@ export default async function Page({ params, searchParams }: PageProps) { pagadorId: pagador.id, period: selectedPeriod, }), + fetchPagadorBoletoItems({ + userId: dataOwnerId, + pagadorId: pagador.id, + period: selectedPeriod, + }), + fetchPagadorPaymentStatus({ + userId: dataOwnerId, + pagadorId: pagador.id, + period: selectedPeriod, + }), sharesPromise, currentUserSharePromise, getRecentEstablishmentsAction(), @@ -308,7 +331,7 @@ export default async function Page({ params, searchParams }: PageProps) { -
+
-
- - +
+ } + > + + + } + > + + + } + > + +
diff --git a/app/(landing-page)/layout.tsx b/app/(landing-page)/layout.tsx new file mode 100644 index 0000000..d52d6a0 --- /dev/null +++ b/app/(landing-page)/layout.tsx @@ -0,0 +1,95 @@ +import type { Metadata } from "next"; +import type { ReactNode } from "react"; + +const BASE_URL = process.env.PUBLIC_DOMAIN + ? `https://${process.env.PUBLIC_DOMAIN}` + : "https://openmonetis.com"; + +const TITLE = "OpenMonetis | Finanças pessoais self-hosted e open source"; +const DESCRIPTION = + "Aplicativo self-hosted de finanças pessoais. Controle lançamentos, cartões, orçamentos e categorias com total privacidade. Open source e gratuito."; + +export const metadata: Metadata = { + metadataBase: new URL(BASE_URL), + title: TITLE, + description: DESCRIPTION, + keywords: [ + "finanças pessoais", + "controle financeiro", + "self-hosted", + "open source", + "gestão financeira", + "orçamento pessoal", + "lançamentos financeiros", + "cartão de crédito", + "planejamento financeiro", + ], + alternates: { + canonical: "/", + }, + openGraph: { + type: "website", + locale: "pt_BR", + url: "/", + siteName: "OpenMonetis", + title: TITLE, + description: DESCRIPTION, + images: [ + { + url: "/dashboard-preview-light.webp", + width: 1920, + height: 1080, + alt: "OpenMonetis — Dashboard de finanças pessoais", + }, + ], + }, + twitter: { + card: "summary_large_image", + title: TITLE, + description: DESCRIPTION, + images: ["/dashboard-preview-light.webp"], + }, + robots: { + index: true, + follow: true, + googleBot: { + index: true, + follow: true, + "max-image-preview": "large", + "max-snippet": -1, + }, + }, +}; + +export default function LandingLayout({ children }: { children: ReactNode }) { + const jsonLd = { + "@context": "https://schema.org", + "@type": "SoftwareApplication", + name: "OpenMonetis", + applicationCategory: "FinanceApplication", + operatingSystem: "Web", + offers: { + "@type": "Offer", + price: "0", + priceCurrency: "BRL", + }, + description: DESCRIPTION, + url: BASE_URL, + isAccessibleForFree: true, + author: { + "@type": "Organization", + name: "OpenMonetis", + url: BASE_URL, + }, + }; + + return ( + <> +