mirror of
https://github.com/felipegcoutinho/openmonetis.git
synced 2026-05-09 11:01:45 +00:00
Refina tema global e experiencia visual de auth
This commit is contained in:
@@ -7,6 +7,11 @@ export const america = localFont({
|
||||
weight: "400",
|
||||
style: "normal",
|
||||
},
|
||||
// {
|
||||
// path: "./america-medium.woff2",
|
||||
// weight: "500",
|
||||
// style: "normal",
|
||||
// },
|
||||
],
|
||||
display: "swap",
|
||||
variable: "--font-america",
|
||||
|
||||
@@ -2,8 +2,8 @@ import { LoginForm } from "@/features/auth/components/login-form";
|
||||
|
||||
export default function LoginPage() {
|
||||
return (
|
||||
<div className="flex min-h-svh flex-col items-center justify-center p-6 md:p-10">
|
||||
<div className="w-full max-w-sm md:max-w-4xl">
|
||||
<div className="flex min-h-svh flex-col items-center justify-center bg-linear-to-b from-background via-background to-muted/20 px-5 py-8 md:px-8 md:py-10">
|
||||
<div className="w-full max-w-sm md:max-w-5xl">
|
||||
<LoginForm />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,8 +2,8 @@ import { SignupForm } from "@/features/auth/components/signup-form";
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<div className="flex min-h-svh flex-col items-center justify-center p-6 md:p-10">
|
||||
<div className="w-full max-w-sm md:max-w-4xl">
|
||||
<div className="flex min-h-svh flex-col items-center justify-center bg-linear-to-b from-background via-background to-muted/20 px-5 py-8 md:px-8 md:py-10">
|
||||
<div className="w-full max-w-sm md:max-w-5xl">
|
||||
<SignupForm />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import {
|
||||
RiArrowRightSLine,
|
||||
RiBankCard2Line,
|
||||
RiBarChartBoxLine,
|
||||
RiCalendarLine,
|
||||
@@ -211,7 +210,6 @@ export default async function Page() {
|
||||
<Link href="/signup">
|
||||
<Button size="sm" className="gap-2">
|
||||
Começar
|
||||
<RiArrowRightSLine size={16} />
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
@@ -4,53 +4,44 @@
|
||||
|
||||
@theme {
|
||||
--spacing-custom-height-card: 30rem;
|
||||
--spacing-8xl: 88rem; /* 1408px */
|
||||
--spacing-9xl: 96rem; /* 1536px */
|
||||
--spacing-8xl: 88rem;
|
||||
--spacing-9xl: 96rem;
|
||||
}
|
||||
|
||||
:root {
|
||||
/* Base surfaces - warm cream with subtle orange undertone */
|
||||
--background: oklch(98.01% 0.00331 67.026);
|
||||
--foreground: #201207;
|
||||
--background: oklch(97.036% 0.00276 84.303);
|
||||
--foreground: oklch(27% 0.008 45);
|
||||
--card: var(--background);
|
||||
--card-foreground: #201207;
|
||||
--popover: oklch(99.5% 0.004 80);
|
||||
--popover-foreground: oklch(18% 0.02 45);
|
||||
--card-foreground: var(--foreground);
|
||||
--popover: oklch(100% 0 0);
|
||||
--popover-foreground: var(--foreground);
|
||||
|
||||
/* Primary - rich terracotta orange */
|
||||
--primary: #f17a35;
|
||||
--primary: oklch(72.085% 0.16286 50.705);
|
||||
--primary-foreground: oklch(98% 0.008 80);
|
||||
|
||||
/* Secondary - warm stone with subtle saturation */
|
||||
--secondary: oklch(94% 0.018 70);
|
||||
--secondary-foreground: oklch(25% 0.025 45);
|
||||
--secondary: oklch(96.2% 0.005 70);
|
||||
--secondary-foreground: oklch(30% 0.01 45);
|
||||
|
||||
/* Muted - softer background variant */
|
||||
--muted: oklch(94.5% 0.014 75);
|
||||
--muted-foreground: #44413c;
|
||||
--muted: oklch(95% 0.0035 70);
|
||||
--muted-foreground: oklch(50% 0.007 50);
|
||||
|
||||
/* Accent - complementary warm tone */
|
||||
--accent: oklch(94% 0.01 70);
|
||||
--accent-foreground: #44413c;
|
||||
--accent: oklch(94.8% 0.009 65);
|
||||
--accent-foreground: var(--foreground);
|
||||
|
||||
/* Semantic states */
|
||||
--success: oklch(55.87% 0.12943 157.517);
|
||||
--success: oklch(61.654% 0.14385 157.131);
|
||||
--success-foreground: oklch(98% 0.01 150);
|
||||
--warning: oklch(69.913% 0.1798 49.649);
|
||||
--warning-foreground: oklch(20% 0.04 85);
|
||||
--info: oklch(55% 0.17 250);
|
||||
--info-foreground: oklch(98% 0.01 250);
|
||||
|
||||
/* Destructive - accessible red */
|
||||
--destructive: oklch(55% 0.22 27);
|
||||
--destructive-foreground: oklch(98% 0.005 30);
|
||||
|
||||
/* Borders and inputs - defined but subtle */
|
||||
--border: oklch(82% 0.012 75);
|
||||
--input: oklch(82% 0.012 75);
|
||||
--ring: oklch(69.18% 0.18855 38.353);
|
||||
--border: oklch(84.567% 0.00583 84.468);
|
||||
--input: oklch(84.567% 0.00583 84.468);
|
||||
--ring: var(--primary);
|
||||
|
||||
/* Charts - 10 harmonious, distinct, accessible colors */
|
||||
--chart-1: var(--color-emerald-500);
|
||||
--chart-2: var(--color-orange-500);
|
||||
--chart-3: var(--color-indigo-500);
|
||||
@@ -62,20 +53,17 @@
|
||||
--chart-9: var(--color-cyan-500);
|
||||
--chart-10: var(--color-lime-500);
|
||||
|
||||
/* Sidebar - slight elevation from background */
|
||||
--sidebar: oklch(100% 0 0);
|
||||
--sidebar-foreground: oklch(20% 0.02 45);
|
||||
--sidebar-primary: oklch(25% 0.025 45);
|
||||
--sidebar-primary-foreground: oklch(98% 0.008 80);
|
||||
--sidebar-accent: oklch(96.563% 0.00504 67.275);
|
||||
--sidebar-accent-foreground: oklch(22% 0.025 45);
|
||||
--sidebar-border: oklch(69.18% 0.18855 38.353);
|
||||
--sidebar-ring: oklch(69.18% 0.18855 38.353);
|
||||
--sidebar: oklch(99.3% 0.0015 75);
|
||||
--sidebar-foreground: var(--foreground);
|
||||
--sidebar-primary: var(--primary);
|
||||
--sidebar-primary-foreground: var(--primary-foreground);
|
||||
--sidebar-accent: oklch(96.5% 0.004 70);
|
||||
--sidebar-accent-foreground: var(--foreground);
|
||||
--sidebar-border: oklch(91% 0.004 70);
|
||||
--sidebar-ring: var(--primary);
|
||||
|
||||
/* Layout */
|
||||
--radius: 0.5rem;
|
||||
|
||||
/* Shadows - warm tinted for cohesion */
|
||||
--shadow-2xs: 0 1px 2px 0px oklch(35% 0.02 45 / 0.04);
|
||||
--shadow-xs: 0 1px 3px 0px oklch(35% 0.02 45 / 0.06);
|
||||
--shadow-sm: 0 1px 3px 0px oklch(35% 0.02 45 / 0.08),
|
||||
@@ -95,31 +83,25 @@
|
||||
}
|
||||
|
||||
.dark {
|
||||
/* Base surfaces - warm dark with consistent hue family */
|
||||
--background: oklch(18.5% 0.002 70);
|
||||
--foreground: oklch(92% 0.015 80);
|
||||
--card: var(--background);
|
||||
--card-foreground: oklch(92% 0.015 80);
|
||||
--popover: oklch(24% 0.003 70);
|
||||
--popover-foreground: oklch(92% 0.015 80);
|
||||
--background: oklch(20.5% 0.004 55);
|
||||
--foreground: oklch(93% 0.008 80);
|
||||
--card: oklch(23% 0.004 55);
|
||||
--card-foreground: var(--foreground);
|
||||
--popover: oklch(25% 0.004 55);
|
||||
--popover-foreground: var(--foreground);
|
||||
|
||||
/* Primary - vibrant terracotta stands out on dark */
|
||||
--primary: #fa6c26;
|
||||
--primary-foreground: oklch(20% 0.002 70);
|
||||
--primary: oklch(72.085% 0.16286 50.705);
|
||||
--primary-foreground: oklch(16% 0.004 60);
|
||||
|
||||
/* Secondary - elevated surface */
|
||||
--secondary: oklch(22% 0.004 70);
|
||||
--secondary-foreground: oklch(92% 0.015 80);
|
||||
--secondary: oklch(26% 0.004 55);
|
||||
--secondary-foreground: var(--foreground);
|
||||
|
||||
/* Muted - subtle surface variant */
|
||||
--muted: oklch(33.5% 0.005 70);
|
||||
--muted-foreground: oklch(72% 0.004 70);
|
||||
--muted: oklch(28.5% 0.0035 55);
|
||||
--muted-foreground: oklch(73% 0.006 75);
|
||||
|
||||
/* Accent - subtle highlight */
|
||||
--accent: oklch(27% 0.004 70);
|
||||
--accent-foreground: oklch(92% 0.015 80);
|
||||
--accent: oklch(30% 0.005 55);
|
||||
--accent-foreground: var(--foreground);
|
||||
|
||||
/* Semantic states */
|
||||
--success: oklch(65% 0.19 150);
|
||||
--success-foreground: oklch(15% 0.02 150);
|
||||
--warning: oklch(69.913% 0.1798 49.649);
|
||||
@@ -127,16 +109,13 @@
|
||||
--info: oklch(65% 0.17 250);
|
||||
--info-foreground: oklch(15% 0.02 250);
|
||||
|
||||
/* Destructive - accessible red for dark */
|
||||
--destructive: oklch(62% 0.2 28);
|
||||
--destructive-foreground: oklch(98% 0.005 30);
|
||||
|
||||
/* Borders and inputs - visible but subtle */
|
||||
--border: oklch(37% 0.01 70);
|
||||
--input: oklch(32% 0.005 70);
|
||||
--ring: oklch(69.18% 0.18855 38.353);
|
||||
--border: oklch(33% 0.004 55);
|
||||
--input: oklch(30% 0.004 55);
|
||||
--ring: var(--primary);
|
||||
|
||||
/* Charts - bright and distinct on dark */
|
||||
--chart-1: var(--color-emerald-500);
|
||||
--chart-2: var(--color-orange-500);
|
||||
--chart-3: var(--color-indigo-500);
|
||||
@@ -148,20 +127,17 @@
|
||||
--chart-9: var(--color-cyan-500);
|
||||
--chart-10: var(--color-lime-500);
|
||||
|
||||
/* Sidebar - slight separation from main */
|
||||
--sidebar: oklch(24% 0.003 70);
|
||||
--sidebar-foreground: oklch(92% 0.015 80);
|
||||
--sidebar-primary: oklch(69.18% 0.18855 38.353);
|
||||
--sidebar-primary-foreground: oklch(13% 0.006 70);
|
||||
--sidebar-accent: oklch(32% 0.004 70);
|
||||
--sidebar-accent-foreground: oklch(92% 0.015 80);
|
||||
--sidebar-border: oklch(26% 0.004 70);
|
||||
--sidebar-ring: oklch(69.18% 0.18855 38.353);
|
||||
--sidebar: oklch(18% 0.004 55);
|
||||
--sidebar-foreground: var(--foreground);
|
||||
--sidebar-primary: var(--primary);
|
||||
--sidebar-primary-foreground: var(--primary-foreground);
|
||||
--sidebar-accent: oklch(27% 0.004 55);
|
||||
--sidebar-accent-foreground: var(--foreground);
|
||||
--sidebar-border: oklch(31% 0.004 55);
|
||||
--sidebar-ring: var(--primary);
|
||||
|
||||
/* Layout */
|
||||
--radius: 0.5rem;
|
||||
|
||||
/* Shadows - deeper for dark mode */
|
||||
--shadow-2xs: 0 1px 2px 0px oklch(0% 0 0 / 0.3);
|
||||
--shadow-xs: 0 1px 3px 0px oklch(0% 0 0 / 0.4);
|
||||
--shadow-sm: 0 1px 3px 0px oklch(0% 0 0 / 0.45),
|
||||
@@ -254,7 +230,6 @@
|
||||
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
font-family: var(--font-america), sans-serif;
|
||||
}
|
||||
|
||||
*::selection {
|
||||
@@ -283,7 +258,6 @@
|
||||
mix-blend-mode: normal;
|
||||
}
|
||||
|
||||
/* Dialog animations */
|
||||
@keyframes dialog-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
@@ -332,7 +306,6 @@
|
||||
animation: dialog-out 0.15s ease-in;
|
||||
}
|
||||
|
||||
/* Overdue blink: alternates two stacked labels with a smooth crossfade */
|
||||
@keyframes blink-in {
|
||||
0%, 40% { opacity: 1; }
|
||||
50%, 90% { opacity: 0; }
|
||||
|
||||
27
src/features/auth/components/auth-card-shell.tsx
Normal file
27
src/features/auth/components/auth-card-shell.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import type { PropsWithChildren } from "react";
|
||||
import { Card, CardContent } from "@/shared/components/ui/card";
|
||||
import { DotPattern } from "@/shared/components/ui/dot-pattern";
|
||||
import AuthSidebar from "./auth-sidebar";
|
||||
|
||||
export function AuthCardShell({ children }: PropsWithChildren) {
|
||||
return (
|
||||
<Card className="relative overflow-hidden rounded-2xl border-primary/10 bg-card p-0 shadow-none">
|
||||
<div className="pointer-events-none absolute inset-0 overflow-hidden rounded-[inherit]">
|
||||
<DotPattern
|
||||
width={17}
|
||||
height={17}
|
||||
cx={1.3}
|
||||
cy={1.3}
|
||||
cr={1.3}
|
||||
className="text-primary/8 mask-[linear-gradient(to_bottom,black,transparent_84%)]"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-linear-to-br from-primary/6 via-transparent to-transparent" />
|
||||
</div>
|
||||
|
||||
<CardContent className="relative z-10 grid gap-0 p-0 md:min-h-[640px] md:grid-cols-[1.05fr_0.95fr]">
|
||||
<div className="flex bg-card/92 backdrop-blur-[1px]">{children}</div>
|
||||
<AuthSidebar />
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -2,14 +2,20 @@ import { cn } from "@/shared/utils/ui";
|
||||
|
||||
interface AuthHeaderProps {
|
||||
title: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export function AuthHeader({ title }: AuthHeaderProps) {
|
||||
export function AuthHeader({ title, description }: AuthHeaderProps) {
|
||||
return (
|
||||
<div className={cn("flex flex-col gap-1.5")}>
|
||||
<h1 className="text-xl font-semibold tracking-tight text-card-foreground">
|
||||
<div className={cn("flex flex-col gap-2")}>
|
||||
<h1 className="text-2xl font-semibold tracking-tight text-card-foreground">
|
||||
{title}
|
||||
</h1>
|
||||
{description ? (
|
||||
<p className="max-w-md text-sm leading-6 text-muted-foreground">
|
||||
{description}
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,12 +1,32 @@
|
||||
import { Logo } from "@/shared/components/logo";
|
||||
import { DotPattern } from "@/shared/components/ui/dot-pattern";
|
||||
|
||||
function AuthSidebar() {
|
||||
return (
|
||||
<div className="relative hidden flex-col overflow-hidden bg-primary md:flex">
|
||||
<div className="relative flex flex-1 flex-col justify-between p-8">
|
||||
<div className="space-y-4">
|
||||
<h2 className="text-3xl font-semibold leading-tight">
|
||||
<div className="pointer-events-none absolute inset-0">
|
||||
<DotPattern
|
||||
width={18}
|
||||
height={18}
|
||||
cx={1.15}
|
||||
cy={1.15}
|
||||
cr={1.15}
|
||||
className="text-black/10 mask-[radial-gradient(circle_at_top_left,black,transparent_80%)]"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-linear-to-br from-white/9 via-transparent to-black/7" />
|
||||
</div>
|
||||
<div className="relative flex flex-1 flex-col justify-between p-10 lg:p-12">
|
||||
<Logo
|
||||
variant="compact"
|
||||
invertTextOnDark={false}
|
||||
className="opacity-92 [&_img]:brightness-0 [&_img]:saturate-0"
|
||||
/>
|
||||
|
||||
<div className="max-w-sm space-y-4.5">
|
||||
<h2 className="text-[2rem] font-semibold leading-[1.04] tracking-[-0.03em] text-black/84 lg:text-[2.35rem]">
|
||||
Controle suas finanças com clareza e foco diário.
|
||||
</h2>
|
||||
<p className="text-sm opacity-90">
|
||||
<p className="max-w-[18rem] text-sm leading-6 text-black/68">
|
||||
Centralize despesas, organize cartões e acompanhe metas mensais em
|
||||
um painel inteligente feito para o seu dia a dia.
|
||||
</p>
|
||||
|
||||
@@ -3,9 +3,7 @@ import { RiFingerprintLine, RiLoader4Line } from "@remixicon/react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { type FormEvent, useEffect, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { Logo } from "@/shared/components/logo";
|
||||
import { Button } from "@/shared/components/ui/button";
|
||||
import { Card, CardContent } from "@/shared/components/ui/card";
|
||||
import {
|
||||
Field,
|
||||
FieldDescription,
|
||||
@@ -16,13 +14,16 @@ import {
|
||||
import { Input } from "@/shared/components/ui/input";
|
||||
import { authClient, googleSignInAvailable } from "@/shared/lib/auth/client";
|
||||
import { cn } from "@/shared/utils/ui";
|
||||
import { AuthCardShell } from "./auth-card-shell";
|
||||
import { AuthErrorAlert } from "./auth-error-alert";
|
||||
import { AuthHeader } from "./auth-header";
|
||||
import AuthSidebar from "./auth-sidebar";
|
||||
import { GoogleAuthButton } from "./google-auth-button";
|
||||
|
||||
type DivProps = React.ComponentProps<"div">;
|
||||
|
||||
const authLinkClassName =
|
||||
"font-medium text-foreground/88 underline decoration-border underline-offset-4 transition-colors hover:text-foreground hover:decoration-foreground/30 focus-visible:rounded-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/40";
|
||||
|
||||
export function LoginForm({ className, ...props }: DivProps) {
|
||||
const router = useRouter();
|
||||
const isGoogleAvailable = googleSignInAvailable;
|
||||
@@ -130,17 +131,18 @@ export function LoginForm({ className, ...props }: DivProps) {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cn("flex flex-col gap-6", className)} {...props}>
|
||||
<Logo className="mb-2" />
|
||||
<Card className="overflow-hidden p-0">
|
||||
<CardContent className="grid gap-0 p-0 md:grid-cols-[1.05fr_0.95fr]">
|
||||
<div className={cn("flex flex-col gap-5", className)} {...props}>
|
||||
<AuthCardShell>
|
||||
<form
|
||||
className="flex flex-col gap-6 p-6 md:p-8"
|
||||
className="flex w-full items-center px-6 py-7 md:px-10 md:py-9"
|
||||
onSubmit={handleSubmit}
|
||||
noValidate
|
||||
>
|
||||
<FieldGroup className="gap-4">
|
||||
<AuthHeader title="Entrar no OpenMonetis" />
|
||||
<FieldGroup className="mx-auto w-full max-w-md gap-5">
|
||||
<AuthHeader
|
||||
title="Entrar no OpenMonetis"
|
||||
description="Acesse sua conta para acompanhar cartões, lançamentos e metas em um só lugar."
|
||||
/>
|
||||
|
||||
<AuthErrorAlert error={error} />
|
||||
|
||||
@@ -188,7 +190,7 @@ export function LoginForm({ className, ...props }: DivProps) {
|
||||
</Button>
|
||||
</Field>
|
||||
|
||||
<FieldSeparator className="my-2 *:data-[slot=field-separator-content]:bg-card">
|
||||
<FieldSeparator className="my-1.5 *:data-[slot=field-separator-content]:bg-card">
|
||||
Ou continue com
|
||||
</FieldSeparator>
|
||||
|
||||
@@ -225,24 +227,21 @@ export function LoginForm({ className, ...props }: DivProps) {
|
||||
</Field>
|
||||
)}
|
||||
|
||||
<FieldDescription className="text-center">
|
||||
<FieldDescription className="pt-1 text-center">
|
||||
Não tem uma conta?{" "}
|
||||
<a href="/signup" className="underline underline-offset-4">
|
||||
<a href="/signup" className={authLinkClassName}>
|
||||
Inscreva-se
|
||||
</a>
|
||||
</FieldDescription>
|
||||
|
||||
<FieldDescription className="text-center text-[13px] text-muted-foreground">
|
||||
<a href="/" className={authLinkClassName}>
|
||||
Voltar para a página inicial
|
||||
</a>
|
||||
</FieldDescription>
|
||||
</FieldGroup>
|
||||
</form>
|
||||
|
||||
<AuthSidebar />
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<FieldDescription className="text-center">
|
||||
<a href="/" className="underline underline-offset-4">
|
||||
Voltar para o site
|
||||
</a>
|
||||
</FieldDescription>
|
||||
</AuthCardShell>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,9 +3,7 @@ import { RiCheckLine, RiCloseLine, RiLoader4Line } from "@remixicon/react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { type FormEvent, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { Logo } from "@/shared/components/logo";
|
||||
import { Button } from "@/shared/components/ui/button";
|
||||
import { Card, CardContent } from "@/shared/components/ui/card";
|
||||
import {
|
||||
Field,
|
||||
FieldDescription,
|
||||
@@ -16,9 +14,9 @@ import {
|
||||
import { Input } from "@/shared/components/ui/input";
|
||||
import { authClient, googleSignInAvailable } from "@/shared/lib/auth/client";
|
||||
import { cn } from "@/shared/utils/ui";
|
||||
import { AuthCardShell } from "./auth-card-shell";
|
||||
import { AuthErrorAlert } from "./auth-error-alert";
|
||||
import { AuthHeader } from "./auth-header";
|
||||
import AuthSidebar from "./auth-sidebar";
|
||||
import { GoogleAuthButton } from "./google-auth-button";
|
||||
|
||||
interface PasswordValidation {
|
||||
@@ -76,6 +74,9 @@ function PasswordRequirement({ met, label }: { met: boolean; label: string }) {
|
||||
|
||||
type DivProps = React.ComponentProps<"div">;
|
||||
|
||||
const authLinkClassName =
|
||||
"font-medium text-foreground/88 underline decoration-border underline-offset-4 transition-colors hover:text-foreground hover:decoration-foreground/30 focus-visible:rounded-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/40";
|
||||
|
||||
export function SignupForm({ className, ...props }: DivProps) {
|
||||
const router = useRouter();
|
||||
const isGoogleAvailable = googleSignInAvailable;
|
||||
@@ -149,17 +150,18 @@ export function SignupForm({ className, ...props }: DivProps) {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cn("flex flex-col gap-6", className)} {...props}>
|
||||
<Logo className="mb-2" />
|
||||
<Card className="overflow-hidden p-0">
|
||||
<CardContent className="grid gap-0 p-0 md:grid-cols-[1.05fr_0.95fr]">
|
||||
<div className={cn("flex flex-col gap-5", className)} {...props}>
|
||||
<AuthCardShell>
|
||||
<form
|
||||
className="flex flex-col gap-6 p-6 md:p-8"
|
||||
className="flex w-full items-center px-6 py-7 md:px-10 md:py-9"
|
||||
onSubmit={handleSubmit}
|
||||
noValidate
|
||||
>
|
||||
<FieldGroup className="gap-4">
|
||||
<AuthHeader title="Criar sua conta" />
|
||||
<FieldGroup className="mx-auto w-full max-w-md gap-5">
|
||||
<AuthHeader
|
||||
title="Criar sua conta"
|
||||
description="Comece com uma base organizada para acompanhar despesas, cartões e objetivos mensais."
|
||||
/>
|
||||
|
||||
<AuthErrorAlert error={error} />
|
||||
|
||||
@@ -208,7 +210,7 @@ export function SignupForm({ className, ...props }: DivProps) {
|
||||
maxLength={23}
|
||||
/>
|
||||
{password.length > 0 && (
|
||||
<div className="mt-2 grid grid-cols-2 gap-x-4 gap-y-1">
|
||||
<div className="mt-3 grid grid-cols-2 gap-x-4 gap-y-1.5 rounded-xl bg-muted/35 p-3">
|
||||
<PasswordRequirement
|
||||
met={passwordValidation.hasMinLength}
|
||||
label="Mínimo 7 caracteres"
|
||||
@@ -255,7 +257,7 @@ export function SignupForm({ className, ...props }: DivProps) {
|
||||
</Button>
|
||||
</Field>
|
||||
|
||||
<FieldSeparator className="my-2 *:data-[slot=field-separator-content]:bg-card">
|
||||
<FieldSeparator className="my-1.5 *:data-[slot=field-separator-content]:bg-card">
|
||||
Ou continue com
|
||||
</FieldSeparator>
|
||||
|
||||
@@ -268,24 +270,21 @@ export function SignupForm({ className, ...props }: DivProps) {
|
||||
/>
|
||||
</Field>
|
||||
|
||||
<FieldDescription className="text-center">
|
||||
<FieldDescription className="pt-1 text-center">
|
||||
Já tem uma conta?{" "}
|
||||
<a href="/login" className="underline underline-offset-4">
|
||||
<a href="/login" className={authLinkClassName}>
|
||||
Entrar
|
||||
</a>
|
||||
</FieldDescription>
|
||||
|
||||
<FieldDescription className="text-center text-[13px] text-muted-foreground">
|
||||
<a href="/" className={authLinkClassName}>
|
||||
Voltar para a página inicial
|
||||
</a>
|
||||
</FieldDescription>
|
||||
</FieldGroup>
|
||||
</form>
|
||||
|
||||
<AuthSidebar />
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<FieldDescription className="text-center">
|
||||
<a href="/" className="underline underline-offset-4">
|
||||
Voltar para o site
|
||||
</a>
|
||||
</FieldDescription>
|
||||
</AuthCardShell>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ export default function MonthNavigation() {
|
||||
};
|
||||
|
||||
return (
|
||||
<Card className="flex w-full flex-row p-4 sticky top-16 z-10 backdrop-blur-sm bg-card/30">
|
||||
<Card className="sticky top-16 z-10 flex w-full flex-row p-4 backdrop-blur-md bg-card/5">
|
||||
<div className="flex items-center gap-1">
|
||||
<NavigationButton
|
||||
direction="left"
|
||||
|
||||
@@ -4,6 +4,7 @@ import { AnimatedThemeToggler } from "@/shared/components/animated-theme-toggler
|
||||
import { Logo } from "@/shared/components/logo";
|
||||
import { NotificationBell } from "@/shared/components/navigation/navbar/notification-bell";
|
||||
import { RefreshPageButton } from "@/shared/components/refresh-page-button";
|
||||
import { DotPattern } from "@/shared/components/ui/dot-pattern";
|
||||
import { NavMenu } from "./nav-menu";
|
||||
import { NavbarUser } from "./navbar-user";
|
||||
|
||||
@@ -29,17 +30,26 @@ export function AppNavbar({
|
||||
notificationsSnapshot,
|
||||
}: AppNavbarProps) {
|
||||
return (
|
||||
<header className="fixed top-0 left-0 right-0 z-50 flex h-16 shrink-0 items-center bg-primary">
|
||||
<div className="w-full max-w-8xl mx-auto px-4 flex items-center gap-4 h-full">
|
||||
{/* Logo */}
|
||||
<header className="fixed top-0 left-0 right-0 z-50 flex h-16 shrink-0 items-center border-b border-black/6 bg-primary">
|
||||
<div className="pointer-events-none absolute inset-0 overflow-hidden">
|
||||
<DotPattern
|
||||
width={20}
|
||||
height={20}
|
||||
cx={1.25}
|
||||
cy={1.25}
|
||||
cr={1.25}
|
||||
className="text-black/10 mask-[linear-gradient(to_right,transparent,black_6%,black_60%,transparent)]"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-linear-to-b from-white/8 via-transparent to-black/6" />
|
||||
</div>
|
||||
|
||||
<div className="relative z-10 mx-auto flex h-full w-full max-w-8xl items-center gap-4 px-4">
|
||||
<Link href="/dashboard" className="shrink-0 mr-1">
|
||||
<Logo variant="compact" invertTextOnDark={false} />
|
||||
</Link>
|
||||
|
||||
{/* Navigation */}
|
||||
<NavMenu />
|
||||
|
||||
{/* Right-side actions */}
|
||||
<div className="ml-auto flex items-center gap-2">
|
||||
<NotificationBell
|
||||
notifications={notificationsSnapshot.notifications}
|
||||
@@ -51,7 +61,6 @@ export function AppNavbar({
|
||||
<AnimatedThemeToggler className={navbarActionClassName} />
|
||||
</div>
|
||||
|
||||
{/* User avatar */}
|
||||
<NavbarUser user={user} pagadorAvatarUrl={pagadorAvatarUrl} />
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Base para links diretos e triggers — pill arredondado
|
||||
export const linkBase =
|
||||
"inline-flex h-8 items-center justify-center rounded-full px-3 text-sm font-medium transition-all lowercase";
|
||||
"inline-flex h-8 items-center justify-center rounded-md px-2 text-sm font-medium transition-all lowercase";
|
||||
|
||||
// Estado inativo: muted, hover suave sem underline
|
||||
export const linkIdle = "text-black/75 hover:bg-black/10 hover:text-black";
|
||||
@@ -11,8 +11,8 @@ export const linkActive = "bg-black/10 text-black";
|
||||
// Trigger do NavigationMenu — espelha linkBase + linkIdle, remove estilos padrão
|
||||
export const triggerClass = [
|
||||
"h-8!",
|
||||
"rounded-full!",
|
||||
"px-3!",
|
||||
"rounded-md!",
|
||||
"px-2!",
|
||||
"py-0!",
|
||||
"text-sm!",
|
||||
"font-medium!",
|
||||
|
||||
49
src/shared/components/ui/dot-pattern.tsx
Normal file
49
src/shared/components/ui/dot-pattern.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import type { ComponentProps } from "react";
|
||||
import { cn } from "@/shared/utils/ui";
|
||||
|
||||
type DotPatternProps = ComponentProps<"svg"> & {
|
||||
width?: number;
|
||||
height?: number;
|
||||
x?: number;
|
||||
y?: number;
|
||||
cx?: number;
|
||||
cy?: number;
|
||||
cr?: number;
|
||||
};
|
||||
|
||||
export function DotPattern({
|
||||
className,
|
||||
width = 18,
|
||||
height = 18,
|
||||
x = 0,
|
||||
y = 0,
|
||||
cx = 1.5,
|
||||
cy = 1.5,
|
||||
cr = 1.5,
|
||||
...props
|
||||
}: DotPatternProps) {
|
||||
const patternId = `dot-pattern-${width}-${height}-${x}-${y}-${cx}-${cy}-${cr}`;
|
||||
|
||||
return (
|
||||
<svg
|
||||
aria-hidden
|
||||
className={cn("absolute inset-0 h-full w-full", className)}
|
||||
{...props}
|
||||
>
|
||||
<title>Dot pattern background</title>
|
||||
<defs>
|
||||
<pattern
|
||||
id={patternId}
|
||||
width={width}
|
||||
height={height}
|
||||
patternUnits="userSpaceOnUse"
|
||||
x={x}
|
||||
y={y}
|
||||
>
|
||||
<circle cx={cx} cy={cy} r={cr} fill="currentColor" />
|
||||
</pattern>
|
||||
</defs>
|
||||
<rect width="100%" height="100%" fill={`url(#${patternId})`} />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user