diff --git a/app/globals.css b/app/globals.css
index e6bb464..47ade7b 100644
--- a/app/globals.css
+++ b/app/globals.css
@@ -7,115 +7,165 @@
}
:root {
- --background: oklch(95.657% 0.00898 78.134);
- --foreground: oklch(0.1448 0 0);
- --card: oklch(98.531% 0.00274 84.298);
- --card-foreground: oklch(0.1448 0 0);
- --popover: oklch(1 0 0);
- --popover-foreground: oklch(0.1448 0 0);
- --primary: oklch(63.198% 0.16941 37.263);
- --primary-foreground: oklch(0.9851 0 0);
- --secondary: oklch(0.9702 0 0);
- --secondary-foreground: oklch(0.2046 0 0);
- --muted: var(--background);
- --muted-foreground: oklch(0.5555 0 0);
- --accent: oklch(0.9702 0 0);
- --accent-foreground: oklch(0.2046 0 0);
- --destructive: oklch(0.583 0.2387 28.4765);
- --destructive-foreground: oklch(1 0 0);
- --border: oklch(89.814% 0.00805 114.524);
- --input: oklch(70.84% 0.00279 106.916);
- --ring: oklch(76.109% 0.15119 44.68);
- --chart-1: oklch(70.734% 0.16977 153.383);
- --chart-2: oklch(62.464% 0.20395 25.32);
- --chart-3: oklch(58.831% 0.22222 298.916);
- --chart-4: oklch(0.4893 0.2202 264.0405);
- --chart-5: oklch(0.421 0.1792 266.0094);
- --sidebar: oklch(91.118% 0.01317 82.34);
- --sidebar-foreground: oklch(0.1448 0 0);
- --sidebar-primary: oklch(0.2046 0 0);
- --sidebar-primary-foreground: oklch(0.9851 0 0);
- --sidebar-accent: oklch(93.199% 0.00336 67.072);
- --sidebar-accent-foreground: oklch(0.2046 0 0);
- --sidebar-border: var(--primary);
- --sidebar-ring: oklch(0.709 0 0);
+ /* Base surfaces - warm cream with subtle orange undertone */
+ --background: oklch(97.512% 0.00674 67.377);
+ --foreground: oklch(18% 0.02 45);
+ --card: oklch(99% 0.006 80);
+ --card-foreground: oklch(18% 0.02 45);
+ --popover: oklch(99.5% 0.004 80);
+ --popover-foreground: oklch(18% 0.02 45);
+
+ /* Primary - rich terracotta orange */
+ --primary: oklch(69.18% 0.18855 38.353);
+ --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);
+
+ /* Muted - softer background variant */
+ --muted: oklch(94.5% 0.014 75);
+ --muted-foreground: oklch(45% 0.015 60);
+
+ /* Accent - complementary warm tone */
+ --accent: oklch(93.996% 0.01787 64.782);
+ --accent-foreground: oklch(22% 0.025 45);
+
+ /* Destructive - accessible red */
+ --destructive: oklch(55% 0.22 27);
+ --destructive-foreground: oklch(98% 0.005 30);
+
+ /* Borders and inputs - defined but subtle */
+ --border: oklch(88% 0.015 80);
+ --input: oklch(82% 0.012 75);
+ --ring: oklch(69.18% 0.18855 38.353);
+
+ /* Charts - harmonious, distinct, accessible */
+ --chart-1: oklch(65% 0.18 160);
+ --chart-2: oklch(60% 0.2 28);
+ --chart-3: oklch(58% 0.19 295);
+ --chart-4: oklch(55% 0.2 260);
+ --chart-5: oklch(68% 0.16 85);
+
+ /* Sidebar - slight elevation from background */
+ --sidebar: oklch(94.637% 0.00925 62.27);
+ --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(88.94% 0.02161 65.18);
+ --sidebar-accent-foreground: oklch(22% 0.025 45);
+ --sidebar-border: oklch(58.814% 0.15852 38.26);
+ --sidebar-ring: oklch(69.18% 0.18855 38.353);
+
+ /* Layout */
--radius: 0.8rem;
- --shadow-2xs: 0 1px 3px 0px hsl(0 0% 0% / 0.05);
- --shadow-xs: 0 1px 3px 0px hsl(0 0% 0% / 0.05);
- --shadow-sm: 0 1px 3px 0px hsl(0 0% 0% / 0.1),
- 0 1px 2px -1px hsl(0 0% 0% / 0.1);
- --shadow: 0 1px 3px 0px hsl(0 0% 0% / 0.1), 0 1px 2px -1px hsl(0 0% 0% / 0.1);
- --shadow-md: 0 1px 3px 0px hsl(0 0% 0% / 0.1),
- 0 2px 4px -1px hsl(0 0% 0% / 0.1);
- --shadow-lg: 0 1px 3px 0px hsl(0 0% 0% / 0.1),
- 0 4px 6px -1px hsl(0 0% 0% / 0.1);
- --shadow-xl: 0 1px 3px 0px hsl(0 0% 0% / 0.1),
- 0 8px 10px -1px hsl(0 0% 0% / 0.1);
- --shadow-2xl: 0 1px 3px 0px hsl(0 0% 0% / 0.25);
+
+ /* 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),
+ 0 1px 2px -1px oklch(35% 0.02 45 / 0.08);
+ --shadow: 0 2px 4px 0px oklch(35% 0.02 45 / 0.08),
+ 0 1px 2px -1px oklch(35% 0.02 45 / 0.06);
+ --shadow-md: 0 4px 6px -1px oklch(35% 0.02 45 / 0.1),
+ 0 2px 4px -2px oklch(35% 0.02 45 / 0.08);
+ --shadow-lg: 0 10px 15px -3px oklch(35% 0.02 45 / 0.1),
+ 0 4px 6px -4px oklch(35% 0.02 45 / 0.08);
+ --shadow-xl: 0 20px 25px -5px oklch(35% 0.02 45 / 0.1),
+ 0 8px 10px -6px oklch(35% 0.02 45 / 0.08);
+ --shadow-2xl: 0 25px 50px -12px oklch(35% 0.02 45 / 0.2);
+
--tracking-normal: 0em;
--spacing: 0.25rem;
- --month-picker: oklch(89.296% 0.0234 143.556);
- --month-picker-foreground: oklch(28% 0.035 143.556);
- --dark: oklch(27.171% 0.00927 294.877);
- --dark-foreground: oklch(91.3% 0.00281 84.324);
+
+ /* Special components */
+ --month-picker: oklch(92.929% 0.01274 63.703);
+ --month-picker-foreground: oklch(22% 0.015 45);
+ --dark: oklch(22% 0.015 45);
+ --dark-foreground: oklch(94% 0.008 80);
--welcome-banner: var(--primary);
- --welcome-banner-foreground: oklch(98% 0.005 35.01);
+ --welcome-banner-foreground: oklch(98% 0.008 80);
}
.dark {
- --background: oklch(18.5% 0.008 67.284);
- --foreground: oklch(96.5% 0.002 67.284);
- --card: oklch(22.8% 0.009 67.284);
- --card-foreground: oklch(96.5% 0.002 67.284);
- --popover: oklch(24.5% 0.01 67.284);
- --popover-foreground: oklch(96.5% 0.002 67.284);
- --primary: oklch(63.198% 0.16941 37.263);
- --primary-foreground: oklch(98% 0.001 67.284);
- --secondary: oklch(26.5% 0.008 67.284);
- --secondary-foreground: oklch(96.5% 0.002 67.284);
- --muted: oklch(25.2% 0.008 67.284);
- --muted-foreground: oklch(68% 0.004 67.284);
- --accent: oklch(30.5% 0.012 67.284);
- --accent-foreground: oklch(96.5% 0.002 67.284);
- --destructive: oklch(62.5% 0.218 28.4765);
- --destructive-foreground: oklch(98% 0.001 67.284);
- --border: oklch(32.5% 0.01 114.524);
- --input: oklch(38.5% 0.012 106.916);
- --ring: oklch(68% 0.135 35.01);
- --chart-1: oklch(70.734% 0.16977 153.383);
- --chart-2: oklch(62.464% 0.20395 25.32);
- --chart-3: oklch(63.656% 0.19467 301.166);
- --chart-4: oklch(60% 0.19 264.0405);
- --chart-5: oklch(56% 0.16 266.0094);
- --sidebar: oklch(20.2% 0.009 67.484);
- --sidebar-foreground: oklch(96.5% 0.002 67.284);
- --sidebar-primary: oklch(65.5% 0.148 35.01);
- --sidebar-primary-foreground: oklch(98% 0.001 67.284);
- --sidebar-accent: oklch(28.5% 0.011 67.072);
- --sidebar-accent-foreground: oklch(96.5% 0.002 67.284);
- --sidebar-border: oklch(30% 0.01 67.484);
- --sidebar-ring: oklch(68% 0.135 35.01);
+ /* Base surfaces - true dark with minimal saturation */
+ --background: oklch(14% 0.004 285);
+ --foreground: oklch(95% 0.003 285);
+ --card: oklch(18% 0.005 285);
+ --card-foreground: oklch(95% 0.003 285);
+ --popover: oklch(20% 0.006 285);
+ --popover-foreground: oklch(95% 0.003 285);
+
+ /* Primary - vibrant terracotta stands out on dark */
+ --primary: oklch(69.18% 0.18855 38.353);
+ --primary-foreground: oklch(12% 0.008 285);
+
+ /* Secondary - elevated surface */
+ --secondary: oklch(22% 0.004 285);
+ --secondary-foreground: oklch(93% 0.003 285);
+
+ /* Muted - subtle surface variant */
+ --muted: oklch(20% 0.004 285);
+ --muted-foreground: oklch(60% 0.003 285);
+
+ /* Accent - subtle highlight */
+ --accent: oklch(26% 0.006 285);
+ --accent-foreground: oklch(95% 0.003 285);
+
+ /* 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(28% 0.004 285);
+ --input: oklch(32% 0.005 285);
+ --ring: oklch(69.18% 0.18855 38.353);
+
+ /* Charts - bright and distinct on dark */
+ --chart-1: oklch(72% 0.17 158);
+ --chart-2: oklch(68% 0.19 30);
+ --chart-3: oklch(68% 0.18 298);
+ --chart-4: oklch(65% 0.18 262);
+ --chart-5: oklch(74% 0.15 88);
+
+ /* Sidebar - slight separation from main */
+ --sidebar: oklch(16% 0.004 285);
+ --sidebar-foreground: oklch(95% 0.003 285);
+ --sidebar-primary: oklch(69.18% 0.18855 38.353);
+ --sidebar-primary-foreground: oklch(12% 0.008 285);
+ --sidebar-accent: oklch(24% 0.005 285);
+ --sidebar-accent-foreground: oklch(95% 0.003 285);
+ --sidebar-border: oklch(26% 0.004 285);
+ --sidebar-ring: oklch(69.18% 0.18855 38.353);
+
+ /* Layout */
--radius: 0.8rem;
- --shadow-2xs: 0 1px 3px 0px hsl(0 0% 0% / 0.15);
- --shadow-xs: 0 1px 3px 0px hsl(0 0% 0% / 0.2);
- --shadow-sm: 0 1px 3px 0px hsl(0 0% 0% / 0.25),
- 0 1px 2px -1px hsl(0 0% 0% / 0.25);
- --shadow: 0 1px 3px 0px hsl(0 0% 0% / 0.3), 0 1px 2px -1px hsl(0 0% 0% / 0.3);
- --shadow-md: 0 1px 3px 0px hsl(0 0% 0% / 0.35),
- 0 2px 4px -1px hsl(0 0% 0% / 0.35);
- --shadow-lg: 0 1px 3px 0px hsl(0 0% 0% / 0.4),
- 0 4px 6px -1px hsl(0 0% 0% / 0.4);
- --shadow-xl: 0 1px 3px 0px hsl(0 0% 0% / 0.45),
- 0 8px 10px -1px hsl(0 0% 0% / 0.45);
- --shadow-2xl: 0 1px 3px 0px hsl(0 0% 0% / 0.5);
+
+ /* 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),
+ 0 1px 2px -1px oklch(0% 0 0 / 0.45);
+ --shadow: 0 2px 4px 0px oklch(0% 0 0 / 0.5),
+ 0 1px 2px -1px oklch(0% 0 0 / 0.4);
+ --shadow-md: 0 4px 6px -1px oklch(0% 0 0 / 0.55),
+ 0 2px 4px -2px oklch(0% 0 0 / 0.45);
+ --shadow-lg: 0 10px 15px -3px oklch(0% 0 0 / 0.55),
+ 0 4px 6px -4px oklch(0% 0 0 / 0.45);
+ --shadow-xl: 0 20px 25px -5px oklch(0% 0 0 / 0.6),
+ 0 8px 10px -6px oklch(0% 0 0 / 0.5);
+ --shadow-2xl: 0 25px 50px -12px oklch(0% 0 0 / 0.7);
+
--tracking-normal: 0em;
--spacing: 0.25rem;
- --month-picker: var(--card);
- --month-picker-foreground: var(--foreground);
- --dark: oklch(91.3% 0.00281 84.324);
- --dark-foreground: oklch(23.649% 0.00484 67.469);
- --welcome-banner: var(--card);
- --welcome-banner-foreground: --dark;
+
+ /* Special components */
+ --month-picker: oklch(22% 0.006 285);
+ --month-picker-foreground: oklch(85% 0.02 315);
+ --dark: oklch(93% 0.003 285);
+ --dark-foreground: oklch(18% 0.005 285);
+ --welcome-banner: oklch(22% 0.006 285);
+ --welcome-banner-foreground: oklch(95% 0.003 285);
}
@theme inline {
@@ -188,11 +238,11 @@
}
*::selection {
- @apply bg-violet-400 text-foreground;
+ @apply bg-primary/25 text-foreground;
}
.dark *::selection {
- @apply bg-orange-700 text-foreground;
+ @apply bg-primary/30 text-foreground;
}
button:not(:disabled),
diff --git a/components/ajustes/update-password-form.tsx b/components/ajustes/update-password-form.tsx
index 6103a68..5bb3824 100644
--- a/components/ajustes/update-password-form.tsx
+++ b/components/ajustes/update-password-form.tsx
@@ -4,10 +4,70 @@ import { updatePasswordAction } from "@/app/(dashboard)/ajustes/actions";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
+import { cn } from "@/lib/utils/ui";
import { RiEyeLine, RiEyeOffLine, RiCheckLine, RiCloseLine, RiAlertLine } from "@remixicon/react";
import { useState, useTransition, useMemo } from "react";
import { toast } from "sonner";
+interface PasswordValidation {
+ hasLowercase: boolean;
+ hasUppercase: boolean;
+ hasNumber: boolean;
+ hasSpecial: boolean;
+ hasMinLength: boolean;
+ hasMaxLength: boolean;
+ isValid: boolean;
+}
+
+function validatePassword(password: string): PasswordValidation {
+ const hasLowercase = /[a-z]/.test(password);
+ const hasUppercase = /[A-Z]/.test(password);
+ const hasNumber = /\d/.test(password);
+ const hasSpecial = /[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?`~]/.test(password);
+ const hasMinLength = password.length >= 7;
+ const hasMaxLength = password.length <= 23;
+
+ return {
+ hasLowercase,
+ hasUppercase,
+ hasNumber,
+ hasSpecial,
+ hasMinLength,
+ hasMaxLength,
+ isValid:
+ hasLowercase &&
+ hasUppercase &&
+ hasNumber &&
+ hasSpecial &&
+ hasMinLength &&
+ hasMaxLength,
+ };
+}
+
+function PasswordRequirement({
+ met,
+ label,
+}: {
+ met: boolean;
+ label: string;
+}) {
+ return (
+
+ {met ? (
+
+ ) : (
+
+ )}
+ {label}
+
+ );
+}
+
type UpdatePasswordFormProps = {
authProvider?: string; // 'google' | 'credential' | undefined
};
@@ -30,30 +90,23 @@ export function UpdatePasswordForm({ authProvider }: UpdatePasswordFormProps) {
return newPassword === confirmPassword;
}, [newPassword, confirmPassword]);
- // Indicador de força da senha (básico)
- const passwordStrength = useMemo(() => {
- if (!newPassword) return null;
- if (newPassword.length < 6) return "weak";
- if (newPassword.length >= 12 && /[A-Z]/.test(newPassword) && /[0-9]/.test(newPassword) && /[^A-Za-z0-9]/.test(newPassword)) {
- return "strong";
- }
- if (newPassword.length >= 8 && (/[A-Z]/.test(newPassword) || /[0-9]/.test(newPassword))) {
- return "medium";
- }
- return "weak";
- }, [newPassword]);
+ // Validação de requisitos da senha
+ const passwordValidation = useMemo(
+ () => validatePassword(newPassword),
+ [newPassword]
+ );
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
// Validação frontend antes de enviar
- if (newPassword !== confirmPassword) {
- toast.error("As senhas não coincidem");
+ if (!passwordValidation.isValid) {
+ toast.error("A senha não atende aos requisitos de segurança");
return;
}
- if (newPassword.length < 6) {
- toast.error("A senha deve ter no mínimo 6 caracteres");
+ if (newPassword !== confirmPassword) {
+ toast.error("As senhas não coincidem");
return;
}
@@ -145,12 +198,13 @@ export function UpdatePasswordForm({ authProvider }: UpdatePasswordFormProps) {
value={newPassword}
onChange={(e) => setNewPassword(e.target.value)}
disabled={isPending}
- placeholder="Mínimo de 6 caracteres"
+ placeholder="Crie uma senha forte"
required
- minLength={6}
+ minLength={7}
+ maxLength={23}
aria-required="true"
aria-describedby="new-password-help"
- aria-invalid={newPassword.length > 0 && newPassword.length < 6}
+ aria-invalid={newPassword.length > 0 && !passwordValidation.isValid}
/>