style(ui): polimento visual — tema, cards, dark mode e landing page

Raio de borda global 0.625rem → 0.7rem; ajustes finos em --card e --border.
DotPattern removido do layout, tela de auth e landing page.
Account-card redesenhado (cores de saldo, tooltip de flags de exclusão).
Budget-card, card-item, calendário (day-cell, event-modal) com layout revisado.
Auth-card-shell simplificado (sem glassmorphism/blob). Landing page com
mainFeatures + extraFeatures em grid único e dark mode nos botões de CTA.
Imagens de preview da landing atualizadas. CSS --data-7..10 removidas.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Felipe Coutinho
2026-04-20 17:52:17 +00:00
parent 5d84ae928a
commit 6d81ff8b53
67 changed files with 612 additions and 737 deletions

View File

@@ -1,5 +1,6 @@
"use client";
import { RiCalendarEventLine } from "@remixicon/react";
import type { ReactNode } from "react";
import { EVENT_TYPE_STYLES } from "@/features/calendar/components/day-cell";
import MoneyValues from "@/shared/components/money-values";
@@ -29,17 +30,13 @@ type EventModalProps = {
const EventCard = ({
children,
type,
isPagamentoFatura = false,
}: {
children: ReactNode;
type: CalendarEvent["type"];
isPagamentoFatura?: boolean;
}) => {
const style = isPagamentoFatura
? { dot: "bg-success" }
: EVENT_TYPE_STYLES[type];
const style = EVENT_TYPE_STYLES[type];
return (
<Card className="flex flex-row gap-2 p-3 mb-1">
<Card className="flex flex-row gap-2 p-3">
<span
className={cn("mt-1 size-3 shrink-0 rounded-full", style.dot)}
aria-hidden
@@ -49,41 +46,34 @@ const EventCard = ({
);
};
const DATE_FORMAT: Intl.DateTimeFormatOptions = {
day: "2-digit",
month: "2-digit",
year: "numeric",
};
const renderLancamento = (
event: Extract<CalendarEvent, { type: "transaction" }>,
) => {
const isReceita = event.transaction.transactionType === "Receita";
const isPagamentoFatura =
event.transaction.name.startsWith("Pagamento fatura -");
return (
<EventCard type="transaction" isPagamentoFatura={isPagamentoFatura}>
<EventCard type="transaction">
<div className="flex items-start justify-between gap-3">
<div className="flex flex-col gap-1">
<span
className={`text-sm font-medium leading-tight ${
isPagamentoFatura && "text-success"
}`}
>
<span className="text-sm font-medium leading-tight">
{event.transaction.name}
</span>
<div className="flex gap-1">
<Badge variant={"outline"}>{event.transaction.categoriaName}</Badge>
</div>
<Badge variant="outline">{event.transaction.categoriaName}</Badge>
</div>
<span
<MoneyValues
showPositiveSign
className={cn(
"text-sm font-medium whitespace-nowrap",
"text-base whitespace-nowrap font-medium",
isReceita ? "text-success" : "text-foreground",
)}
>
<MoneyValues
showPositiveSign
className="text-base"
amount={event.transaction.amount}
/>
</span>
amount={event.transaction.amount}
/>
</div>
</EventCard>
);
@@ -91,59 +81,80 @@ const renderLancamento = (
const renderBoleto = (event: Extract<CalendarEvent, { type: "boleto" }>) => {
const isPaid = Boolean(event.transaction.isSettled);
const dueDate = event.transaction.dueDate;
const dueDateLabel = formatFinancialDateLabel(dueDate, "Vence em", {
day: "2-digit",
month: "2-digit",
year: "numeric",
});
const dueDateLabel = formatFinancialDateLabel(
event.transaction.dueDate,
"Vence em",
DATE_FORMAT,
);
const paymentDateLabel = isPaid
? formatFinancialDateLabel(
event.transaction.boletoPaymentDate,
"Pago em",
DATE_FORMAT,
)
: null;
return (
<EventCard type="boleto">
<div className="flex items-start justify-between gap-3">
<div className="flex flex-col gap-1">
<div className="flex gap-1 items-center">
<span className="text-sm font-medium leading-tight">
{event.transaction.name}
</span>
<span className="text-sm font-medium leading-tight">
{event.transaction.name}
</span>
<div className="flex flex-wrap gap-x-3 gap-y-0.5 text-xs">
{dueDateLabel && (
<span className="text-xs text-muted-foreground leading-tight">
{dueDateLabel}
</span>
<span className="text-muted-foreground">{dueDateLabel}</span>
)}
{paymentDateLabel && (
<span className="text-success">{paymentDateLabel}</span>
)}
</div>
<Badge variant={"outline"}>{isPaid ? "Pago" : "Pendente"}</Badge>
<Badge variant="outline">{isPaid ? "Pago" : "Pendente"}</Badge>
</div>
<span className="font-medium">
<MoneyValues amount={event.transaction.amount} />
</span>
<MoneyValues
className="font-medium whitespace-nowrap"
amount={event.transaction.amount}
/>
</div>
</EventCard>
);
};
const renderCard = (event: Extract<CalendarEvent, { type: "card" }>) => (
<EventCard type="card">
<div className="flex items-start justify-between gap-3">
<div className="flex flex-col gap-1">
<div className="flex gap-1 items-center">
<span className="text-sm font-medium leading-tight">
Vencimento Fatura - {event.card.name}
</span>
</div>
const renderCard = (event: Extract<CalendarEvent, { type: "card" }>) => {
const paymentDateLabel = event.card.isPaid
? formatFinancialDateLabel(event.card.paymentDate, "Pago em", DATE_FORMAT)
: null;
<Badge variant={"outline"}>{event.card.status ?? "Invoice"}</Badge>
return (
<EventCard type="card">
<div className="flex items-start justify-between gap-3">
<div className="flex flex-col gap-1">
<span className="text-sm font-medium leading-tight">
Vencimento Fatura {event.card.name}
</span>
{paymentDateLabel && (
<span className="text-xs text-success">{paymentDateLabel}</span>
)}
<Badge variant="outline">
{event.card.isPaid ? "Pago" : (event.card.status ?? "Fatura")}
</Badge>
</div>
{event.card.totalDue !== null ? (
<MoneyValues
className="font-medium whitespace-nowrap"
amount={event.card.totalDue}
/>
) : null}
</div>
{event.card.totalDue !== null ? (
<span className="font-medium">
<MoneyValues amount={event.card.totalDue} />
</span>
) : null}
</div>
</EventCard>
);
</EventCard>
);
};
const SECTION_LABELS: Record<CalendarEvent["type"], string> = {
transaction: "Lançamentos",
boleto: "Boletos",
card: "Faturas",
};
const renderEvent = (event: CalendarEvent) => {
switch (event.type) {
@@ -169,28 +180,50 @@ export function EventModal({ open, day, onClose, onCreate }: EventModalProps) {
onCreate(day.date);
};
const description = day?.events.length
? "Confira os lançamentos e vencimentos cadastrados para este dia."
: "Nenhum lançamento encontrado para este dia. Você pode criar um novo lançamento agora.";
const hasEvents = Boolean(day?.events.length);
const grouped = day
? {
transaction: day.events.filter((e) => e.type === "transaction"),
boleto: day.events.filter((e) => e.type === "boleto"),
card: day.events.filter((e) => e.type === "card"),
}
: null;
return (
<Dialog open={open} onOpenChange={(value) => (!value ? onClose() : null)}>
<DialogContent className="max-w-xl">
<DialogHeader>
<DialogTitle>{formattedDate}</DialogTitle>
<DialogDescription>{description}</DialogDescription>
<DialogDescription>
{hasEvents
? "Lançamentos e vencimentos cadastrados para este dia."
: "Nenhum lançamento encontrado para este dia."}
</DialogDescription>
</DialogHeader>
<div className="max-h-[380px] space-y-2 overflow-y-auto pr-2">
{day?.events.length ? (
day.events.map((event) => (
<div key={event.id}>{renderEvent(event)}</div>
))
<div className="max-h-[380px] space-y-3 overflow-y-auto pr-2">
{hasEvents && grouped ? (
(["transaction", "boleto", "card"] as const)
.filter((type) => grouped[type].length > 0)
.map((type) => (
<div key={type} className="space-y-1.5">
<p className="px-1 text-xs font-semibold uppercase tracking-wide text-muted-foreground">
{SECTION_LABELS[type]}
</p>
<div className="space-y-1.5">
{grouped[type].map((event) => (
<div key={event.id}>{renderEvent(event)}</div>
))}
</div>
</div>
))
) : (
<div className="rounded-xl border border-dashed border-border/60 bg-muted/30 p-6 text-center text-sm text-muted-foreground">
Nenhum lançamento ou vencimento registrado. Clique em{" "}
<span className="font-medium text-primary">Novo lançamento</span>{" "}
para começar.
<div className="flex flex-col items-center gap-3 rounded-xl border border-dashed border-border/60 bg-muted/30 p-8 text-center">
<RiCalendarEventLine className="size-8 text-muted-foreground/50" />
<p className="text-sm text-muted-foreground">
Nenhum lançamento registrado para este dia.
</p>
</div>
)}
</div>