From 23021975787140273a44e3f9ccd56a93bee73876 Mon Sep 17 00:00:00 2001 From: Jhony Date: Fri, 3 Oct 2025 17:53:39 -0300 Subject: [PATCH 1/3] =?UTF-8?q?mudan=C3=A7as=20listagem=20de=20pacientes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/doctor/medicos/page.tsx | 2 +- components/ui/patient-details-modal.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/doctor/medicos/page.tsx b/app/doctor/medicos/page.tsx index 7fa3f0f..fbd3471 100644 --- a/app/doctor/medicos/page.tsx +++ b/app/doctor/medicos/page.tsx @@ -57,8 +57,8 @@ export default function PacientesPage() { return new Intl.DateTimeFormat('pt-BR').format(date); }; - const [currentPage, setCurrentPage] = useState(1); const [itemsPerPage, setItemsPerPage] = useState(5); + const [currentPage, setCurrentPage] = useState(1); const indexOfLastItem = currentPage * itemsPerPage; const indexOfFirstItem = indexOfLastItem - itemsPerPage; diff --git a/components/ui/patient-details-modal.tsx b/components/ui/patient-details-modal.tsx index ca7b716..fea1040 100644 --- a/components/ui/patient-details-modal.tsx +++ b/components/ui/patient-details-modal.tsx @@ -5,8 +5,8 @@ import { } from "@/components/ui/dialog"; interface PatientDetailsModalProps { - patient: any; isOpen: boolean; + patient: any; onClose: () => void; } -- 2.47.2 From 622ad609a381b76e0a916f9e17fd6c55f061d566 Mon Sep 17 00:00:00 2001 From: Gabriel Lira Figueira Date: Thu, 9 Oct 2025 00:01:18 -0300 Subject: [PATCH 2/3] =?UTF-8?q?feat:=20Refatora=20p=C3=A1ginas=20de=20logi?= =?UTF-8?q?n=20para=20usar=20componente=20reutiliz=C3=A1vel?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/doctor/login/page.tsx | 162 ++-------------------- app/finance/login/page.tsx | 161 ++-------------------- app/layout.tsx | 8 +- app/manager/login/page.tsx | 160 ++-------------------- app/patient/login/page.tsx | 162 +++------------------- app/secretary/login/page.tsx | 162 ++-------------------- components/LoginForm.tsx | 247 ++++++++++++++++++++++++++++++++++ components/patient-layout.tsx | 32 ++++- package-lock.json | 124 ++++++++++------- package.json | 5 +- styles/globals.css | 125 ----------------- 11 files changed, 420 insertions(+), 928 deletions(-) create mode 100644 components/LoginForm.tsx delete mode 100644 styles/globals.css diff --git a/app/doctor/login/page.tsx b/app/doctor/login/page.tsx index 2e40bd2..3f57a22 100644 --- a/app/doctor/login/page.tsx +++ b/app/doctor/login/page.tsx @@ -1,157 +1,17 @@ -"use client" +// Caminho: app/(doctor)/login/page.tsx -import type React from "react" - -import { useState } from "react" -import { useRouter } from "next/navigation" -import { Button } from "@/components/ui/button" -import { Input } from "@/components/ui/input" -import { Label } from "@/components/ui/label" -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" -import { Separator } from "@/components/ui/separator" -import { useToast } from "@/hooks/use-toast" -import { Eye, EyeOff, Mail, Lock, Stethoscope, Loader2 } from "lucide-react" -import Link from "next/link" - -interface LoginForm { - email: string - password: string -} - -export default function DoctorLogin() { - const [form, setForm] = useState({ email: "", password: "" }) - const [showPassword, setShowPassword] = useState(false) - const [isLoading, setIsLoading] = useState(false) - const router = useRouter() - const { toast } = useToast() - - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault() - setIsLoading(true) - - // Simular autenticação - setTimeout(() => { - if (form.email && form.password) { - const doctorData = { - id: "1", - name: "Dr. João Santos", - email: form.email, - phone: "(11) 98888-8888", - cpf: "987.654.321-00", - crm: "CRM/SP 123456", - specialty: "Cardiologia", - department: "Cardiologia", - permissions: ["view_patients", "manage_appointments", "create_reports"], - } - - localStorage.setItem("doctorData", JSON.stringify(doctorData)) - localStorage.setItem("userType", "doctor") - - toast({ - title: "Login realizado com sucesso!", - description: "Bem-vindo ao sistema, " + doctorData.name, - }) - - router.push("/doctor/medicos") - } else { - toast({ - title: "Erro no login", - description: "Por favor, preencha todos os campos.", - variant: "destructive", - }) - } - setIsLoading(false) - }, 1500) - } +import { LoginForm } from "@/components/LoginForm" +export default function DoctorLoginPage() { return (
- - -
- -
-
- Área do Médico - Acesse o sistema médico -
-
- - -
-
- -
- - setForm({ ...form, email: e.target.value })} - className="pl-10 h-11 border-gray-200 focus:border-green-500 focus:ring-green-500" - required - /> -
-
- -
- -
- - setForm({ ...form, password: e.target.value })} - className="pl-10 pr-10 h-11 border-gray-200 focus:border-green-500 focus:ring-green-500" - required - /> - -
-
- - -
- -
- - - ou - -
- -
- - Voltar à página inicial - -
-
-
+
) -} +} \ No newline at end of file diff --git a/app/finance/login/page.tsx b/app/finance/login/page.tsx index a27ba24..d39fd86 100644 --- a/app/finance/login/page.tsx +++ b/app/finance/login/page.tsx @@ -1,155 +1,18 @@ -"use client" +// Caminho: app/(finance)/login/page.tsx -import type React from "react" - -import { useState } from "react" -import { useRouter } from "next/navigation" -import { Button } from "@/components/ui/button" -import { Input } from "@/components/ui/input" -import { Label } from "@/components/ui/label" -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" -import { Separator } from "@/components/ui/separator" -import { useToast } from "@/hooks/use-toast" -import { Eye, EyeOff, Mail, Lock, Stethoscope, Loader2, Receipt } from "lucide-react" -import Link from "next/link" - -interface LoginForm { - email: string - password: string -} - -export default function DoctorLogin() { - const [form, setForm] = useState({ email: "", password: "" }) - const [showPassword, setShowPassword] = useState(false) - const [isLoading, setIsLoading] = useState(false) - const router = useRouter() - const { toast } = useToast() - - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault() - setIsLoading(true) - - // Simular autenticação - setTimeout(() => { - if (form.email && form.password) { - const financierData = { - id: "1", - name: "Thiago Nigro", - email: form.email, - phone: "(11) 98888-8888", - cpf: "987.654.321-00", - department: "Financeiro", - permissions: ["view_reports", "manage_finances", "create_reports"], - } - - localStorage.setItem("financierData", JSON.stringify(financierData)) - localStorage.setItem("userType", "financier") - - toast({ - title: "Login realizado com sucesso!", - description: "Bem-vindo ao sistema, " + financierData.name, - }) - - router.push("/finance/home") - } else { - toast({ - title: "Erro no login", - description: "Por favor, preencha todos os campos.", - variant: "destructive", - }) - } - setIsLoading(false) - }, 1500) - } +import { LoginForm } from "@/components/LoginForm" +export default function FinanceLoginPage() { return ( + // Fundo com gradiente laranja, como no seu código original
- - -
- -
-
- Área do Médico - Acesse o sistema médico -
-
- - -
-
- -
- - setForm({ ...form, email: e.target.value })} - className="pl-10 h-11 border-gray-200 focus:border-green-500 focus:ring-green-500" - required - /> -
-
- -
- -
- - setForm({ ...form, password: e.target.value })} - className="pl-10 pr-10 h-11 border-gray-200 focus:border-green-500 focus:ring-green-500" - required - /> - -
-
- - -
- -
- - - ou - -
- -
- - Voltar à página inicial - -
-
-
+
) -} +} \ No newline at end of file diff --git a/app/layout.tsx b/app/layout.tsx index 3becb5e..239c8d7 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -3,15 +3,10 @@ import { GeistSans } from 'geist/font/sans' import { GeistMono } from 'geist/font/mono' import { Analytics } from '@vercel/analytics/next' import './globals.css' +import { Toaster } from "@/components/ui/toaster" // [PASSO 1.2] - Importando o nosso provider import { AppointmentsProvider } from './context/AppointmentsContext' -export const metadata: Metadata = { - title: 'Clinic App', - description: 'Created with v0', - generator: 'v0.app', -} - export default function RootLayout({ children, }: Readonly<{ @@ -25,6 +20,7 @@ export default function RootLayout({ {children} + ) diff --git a/app/manager/login/page.tsx b/app/manager/login/page.tsx index d5347ce..92c8946 100644 --- a/app/manager/login/page.tsx +++ b/app/manager/login/page.tsx @@ -1,154 +1,18 @@ -"use client" +// Caminho: app/(manager)/login/page.tsx -import type React from "react" - -import { useState } from "react" -import { useRouter } from "next/navigation" -import { Button } from "@/components/ui/button" -import { Input } from "@/components/ui/input" -import { Label } from "@/components/ui/label" -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" -import { Separator } from "@/components/ui/separator" -import { useToast } from "@/hooks/use-toast" -import { Eye, EyeOff, Mail, Lock, Stethoscope, Loader2, IdCard } from "lucide-react" -import Link from "next/link" - -interface LoginForm { - email: string - password: string -} - -export default function ManagerLogin() { - const [form, setForm] = useState({ email: "", password: "" }) - const [showPassword, setShowPassword] = useState(false) - const [isLoading, setIsLoading] = useState(false) - const router = useRouter() - const { toast } = useToast() - - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault() - setIsLoading(true) - - setTimeout(() => { - if (form.email && form.password) { - const managerData = { - id: "1", - name: "Arthur Cavalcante", - email: form.email, - phone: "(11) 98888-8888", - cpf: "987.654.321-00", - department: "Gerente", - permissions: ["manage_user", "manage_doctors", "create_reports"], - } - - localStorage.setItem("managerData", JSON.stringify(managerData)) - localStorage.setItem("userType", "manager") - - toast({ - title: "Login realizado com sucesso!", - description: "Bem-vindo ao sistema, " + managerData.name, - }) - - router.push("/manager/home") - } else { - toast({ - title: "Erro no login", - description: "Por favor, preencha todos os campos.", - variant: "destructive", - }) - } - setIsLoading(false) - }, 1500) - } +import { LoginForm } from "@/components/LoginForm" +export default function ManagerLoginPage() { return ( + // Mantemos o seu plano de fundo original
- - -
- -
-
- Área do Gestor - Acesse o sistema médico -
-
- - -
-
- -
- - setForm({ ...form, email: e.target.value })} - className="pl-10 h-11 border-gray-200 focus:border-blue-500 focus:ring-blue-500" - required - /> -
-
- -
- -
- - setForm({ ...form, password: e.target.value })} - className="pl-10 pr-10 h-11 border-gray-200 focus:border-green-500 focus:ring-green-500" - required - /> - -
-
- - -
- -
- - - ou - -
- -
- - Voltar à página inicial - -
-
-
+
) -} +} \ No newline at end of file diff --git a/app/patient/login/page.tsx b/app/patient/login/page.tsx index 1dd8046..a42be03 100644 --- a/app/patient/login/page.tsx +++ b/app/patient/login/page.tsx @@ -1,50 +1,13 @@ -"use client" +// Caminho: app/(patient)/login/page.tsx -import type React from "react" - -import { useState } from "react" -import { useRouter } from "next/navigation" import Link from "next/link" +import { LoginForm } from "@/components/LoginForm" import { Button } from "@/components/ui/button" -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" -import { Input } from "@/components/ui/input" -import { Label } from "@/components/ui/label" -import { Eye, EyeOff, ArrowLeft, Stethoscope, Mail, Lock } from "lucide-react" - -export default function PatientLogin() { - const [showPassword, setShowPassword] = useState(false) - const [email, setEmail] = useState("") - const [password, setPassword] = useState("") - const [isLoading, setIsLoading] = useState(false) - const router = useRouter() - - const handleLogin = async (e: React.FormEvent) => { - e.preventDefault() - setIsLoading(true) - - // Simulação de login - em produção, conectar com API real - setTimeout(() => { - if (email && password) { - // Salvar dados do usuário no localStorage para simulação - localStorage.setItem( - "patientData", - JSON.stringify({ - name: "João Silva", - email: email, - phone: "(11) 99999-9999", - cpf: "123.456.789-00", - birthDate: "1990-01-01", - address: "Rua das Flores, 123 - São Paulo, SP", - }), - ) - router.push("/patient/dashboard") - } - setIsLoading(false) - }, 1000) - } +import { ArrowLeft } from "lucide-react" +export default function PatientLoginPage() { return ( -
+
- - -
- -
- Área do Paciente - - Acesse sua conta para gerenciar consultas e laudos - -
- - -
-
- -
- - setEmail(e.target.value)} - className="pl-11 h-12 border-slate-200 focus:border-blue-500 focus:ring-blue-500" - required - /> -
-
- -
- -
- - setPassword(e.target.value)} - className="pl-11 pr-12 h-12 border-slate-200 focus:border-blue-500 focus:ring-blue-500" - required - /> - -
-
- - -
- -
-
-
-
-
-
- Novo por aqui? -
-
-
- - Criar nova conta - -
-
-
-
+ + {/* Este bloco é passado como 'children' para o LoginForm */} + + + + + {/* Conteúdo e espaçamento restaurados */}

Problemas para acessar? Entre em contato conosco

) -} +} \ No newline at end of file diff --git a/app/secretary/login/page.tsx b/app/secretary/login/page.tsx index df8679a..3864a08 100644 --- a/app/secretary/login/page.tsx +++ b/app/secretary/login/page.tsx @@ -1,157 +1,17 @@ -"use client" +// Caminho: app/(secretary)/login/page.tsx -import type React from "react" - -import { useState } from "react" -import { useRouter } from "next/navigation" -import { Button } from "@/components/ui/button" -import { Input } from "@/components/ui/input" -import { Label } from "@/components/ui/label" -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" -import { Separator } from "@/components/ui/separator" -import { useToast } from "@/hooks/use-toast" -import { Eye, EyeOff, Mail, Lock, UserCheck, Loader2 } from "lucide-react" -import Link from "next/link" - -interface LoginForm { - email: string - password: string -} - -export default function SecretaryLogin() { - const [form, setForm] = useState({ email: "", password: "" }) - const [showPassword, setShowPassword] = useState(false) - const [isLoading, setIsLoading] = useState(false) - const router = useRouter() - const { toast } = useToast() - - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault() - setIsLoading(true) - - // Simular autenticação - setTimeout(() => { - if (form.email && form.password) { - // Simular dados da secretária - const secretaryData = { - id: "1", - name: "Maria Silva", - email: form.email, - phone: "(11) 99999-9999", - cpf: "123.456.789-00", - employeeId: "SEC001", - department: "Recepção", - permissions: ["manage_appointments", "view_patients", "manage_schedule"], - } - - localStorage.setItem("secretaryData", JSON.stringify(secretaryData)) - localStorage.setItem("userType", "secretary") - - toast({ - title: "Login realizado com sucesso!", - description: "Bem-vinda ao sistema, " + secretaryData.name, - }) - - router.push("/secretary/pacientes") // direciona para a página - } else { - toast({ - title: "Erro no login", - description: "Por favor, preencha todos os campos.", - variant: "destructive", - }) - } - setIsLoading(false) - }, 1500) - } +import { LoginForm } from "@/components/LoginForm" +export default function SecretaryLoginPage() { return (
- - -
- -
-
- Área da Secretária - Acesse o sistema de gerenciamento -
-
- - -
-
- -
- - setForm({ ...form, email: e.target.value })} - className="pl-10 h-11 border-gray-200 focus:border-blue-500 focus:ring-blue-500" - required - /> -
-
- -
- -
- - setForm({ ...form, password: e.target.value })} - className="pl-10 pr-10 h-11 border-gray-200 focus:border-blue-500 focus:ring-blue-500" - required - /> - -
-
- - -
- -
- - - ou - -
- -
- - Voltar à página inicial - -
-
-
+
) -} +} \ No newline at end of file diff --git a/components/LoginForm.tsx b/components/LoginForm.tsx new file mode 100644 index 0000000..8c400cb --- /dev/null +++ b/components/LoginForm.tsx @@ -0,0 +1,247 @@ +// Caminho: components/LoginForm.tsx +"use client" + +import type React from "react" +import { useState } from "react" +import { useRouter } from "next/navigation" +import Link from "next/link" +import Cookies from "js-cookie" +import { jwtDecode } from "jwt-decode" +import { cn } from "@/lib/utils" + +// Componentes Shadcn UI +import { Button } from "@/components/ui/button" +import { Input } from "@/components/ui/input" +import { Label } from "@/components/ui/label" +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" +import { Separator } from "@/components/ui/separator" + +// Hook customizado +import { useToast } from "@/hooks/use-toast" + +// Ícones +import { Eye, EyeOff, Mail, Lock, Loader2, UserCheck, Stethoscope, IdCard, Receipt } from "lucide-react" + +interface LoginFormProps { + title: string + description: string + role: "secretary" | "doctor" | "patient" | "admin" | "manager" | "finance" + themeColor: "blue" | "green" | "orange" + redirectPath: string + children?: React.ReactNode +} + +interface FormState { + email: string + password: string +} + +// Supondo que o payload do seu token tenha esta estrutura +interface DecodedToken { + name: string + email: string + role: string + exp: number + // Adicione outros campos que seu token possa ter +} + +const themeClasses = { + blue: { + iconBg: "bg-blue-100", + iconText: "text-blue-600", + button: "bg-blue-600 hover:bg-blue-700", + link: "text-blue-600 hover:text-blue-700", + focus: "focus:border-blue-500 focus:ring-blue-500", + }, + green: { + iconBg: "bg-green-100", + iconText: "text-green-600", + button: "bg-green-600 hover:bg-green-700", + link: "text-green-600 hover:text-green-700", + focus: "focus:border-green-500 focus:ring-green-500", + }, + orange: { + iconBg: "bg-orange-100", + iconText: "text-orange-600", + button: "bg-orange-600 hover:bg-orange-700", + link: "text-orange-600 hover:text-orange-700", + focus: "focus:border-orange-500 focus:ring-orange-500", + }, +} + +const roleIcons = { + secretary: UserCheck, + patient: Stethoscope, + doctor: Stethoscope, + admin: UserCheck, + manager: IdCard, + finance: Receipt, +} + +export function LoginForm({ title, description, role, themeColor, redirectPath, children }: LoginFormProps) { + const [form, setForm] = useState({ email: "", password: "" }) + const [showPassword, setShowPassword] = useState(false) + const [isLoading, setIsLoading] = useState(false) + const router = useRouter() + const { toast } = useToast() + + const currentTheme = themeClasses[themeColor] + const Icon = roleIcons[role] + + // ================================================================== + // AJUSTE PRINCIPAL NA LÓGICA DE LOGIN + // ================================================================== + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setIsLoading(true); + + const LOGIN_URL = "https://yuanqfswhberkoevtmfr.supabase.co/auth/v1/token?grant_type=password"; + const API_KEY = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY; + + if (!API_KEY) { + toast({ + title: "Erro de Configuração", + description: "A chave da API não foi encontrada.", + variant: "destructive", + }); + setIsLoading(false); + return; + } + + try { + const response = await fetch(LOGIN_URL, { + method: "POST", + headers: { + "Content-Type": "application/json", + "apikey": API_KEY, + }, + body: JSON.stringify({ email: form.email, password: form.password }), + }); + + const data = await response.json(); + + if (!response.ok) { + throw new Error(data.error_description || "Credenciais inválidas. Tente novamente."); + } + + const accessToken = data.access_token; + const user = data.user; + + /* =================== Verificação de Role Desativada Temporariamente =================== */ + // if (user.user_metadata.role !== role) { + // toast({ title: "Acesso Negado", ... }); + // return; + // } + /* ===================================================================================== */ + + Cookies.set("access_token", accessToken, { expires: 1, secure: true }); + localStorage.setItem('user_info', JSON.stringify(user)); + + toast({ + title: "Login bem-sucedido!", + description: `Bem-vindo(a), ${user.user_metadata.full_name || 'usuário'}! Redirecionando...`, + }); + + router.push(redirectPath); + + } catch (error) { + toast({ + title: "Erro no Login", + description: error instanceof Error ? error.message : "Ocorreu um erro inesperado.", + }); + } finally { + setIsLoading(false); + } +}; + + // O JSX do return permanece exatamente o mesmo, preservando seus ajustes. + return ( + + +
+ +
+
+ {title} + {description} +
+
+ +
+ {/* Inputs e Botão */} +
+ +
+ + setForm({ ...form, email: e.target.value })} + className={cn("pl-11 h-12 border-slate-200", currentTheme.focus)} + required + disabled={isLoading} + /> +
+
+
+ +
+ + setForm({ ...form, password: e.target.value })} + className={cn("pl-11 pr-12 h-12 border-slate-200", currentTheme.focus)} + required + disabled={isLoading} + /> + +
+
+ +
+ {/* Conteúdo Extra (children) */} +
+ {children ? ( +
+
+
+
+
+
+ Novo por aqui? +
+
+ {children} +
+ ) : ( + <> +
+ + ou +
+
+ + Voltar à página inicial + +
+ + )} +
+
+
+ ) +} \ No newline at end of file diff --git a/components/patient-layout.tsx b/components/patient-layout.tsx index 59cdab3..3ce21bb 100644 --- a/components/patient-layout.tsx +++ b/components/patient-layout.tsx @@ -5,6 +5,7 @@ import { useState, useEffect } from "react" import Link from "next/link" import { useRouter, usePathname } from "next/navigation" import { Button } from "@/components/ui/button" +import Cookies from "js-cookie" import { Input } from "@/components/ui/input" import { Badge } from "@/components/ui/badge" import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar" @@ -66,14 +67,31 @@ export default function HospitalLayout({ children }: HospitalLayoutProps) { }, []) useEffect(() => { - const data = localStorage.getItem("patientData") - if (data) { - setPatientData(JSON.parse(data)) - } else { - router.push("/patient/login") - } - }, [router]) + // 1. Procuramos pela chave correta: 'user_info' + const userInfoString = localStorage.getItem("user_info"); + // 2. Para mais segurança, verificamos também se o token de acesso existe no cookie + const token = Cookies.get("access_token"); + if (userInfoString && token) { + const userInfo = JSON.parse(userInfoString); + + // 3. Adaptamos os dados para a estrutura que seu layout espera (PatientData) + // Usamos os dados do objeto 'user' que a API do Supabase nos deu + setPatientData({ + name: userInfo.user_metadata?.full_name || "Paciente", + email: userInfo.email || "", + // Os campos abaixo não vêm do login, então os deixamos vazios por enquanto + phone: userInfo.phone || "", + cpf: "", + birthDate: "", + address: "", + }); + } else { + // Se as informações do usuário ou o token não forem encontrados, mandamos para o login. + router.push("/patient/login"); + } + }, [router]); + const handleLogout = () => setShowLogoutDialog(true) const confirmLogout = () => { diff --git a/package-lock.json b/package-lock.json index bea8963..0d688c6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -50,8 +50,10 @@ "embla-carousel-react": "8.5.1", "geist": "^1.3.1", "input-otp": "1.4.1", + "js-cookie": "^3.0.5", + "jwt-decode": "^4.0.0", "lucide-react": "^0.454.0", - "next": "14.2.16", + "next": "^14.2.33", "next-themes": "^0.4.6", "react": "^18", "react-day-picker": "9.8.0", @@ -67,6 +69,7 @@ }, "devDependencies": { "@tailwindcss/postcss": "^4.1.9", + "@types/js-cookie": "^3.0.6", "@types/node": "^22", "@types/react": "^18", "@types/react-dom": "^18", @@ -215,15 +218,15 @@ } }, "node_modules/@next/env": { - "version": "14.2.16", - "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.16.tgz", - "integrity": "sha512-fLrX5TfJzHCbnZ9YUSnGW63tMV3L4nSfhgOQ0iCcX21Pt+VSTDuaLsSuL8J/2XAiVA5AnzvXDpf6pMs60QxOag==", + "version": "14.2.33", + "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.33.tgz", + "integrity": "sha512-CgVHNZ1fRIlxkLhIX22flAZI/HmpDaZ8vwyJ/B0SDPTBuLZ1PJ+DWMjCHhqnExfmSQzA/PbZi8OAc7PAq2w9IA==", "license": "MIT" }, "node_modules/@next/swc-darwin-arm64": { - "version": "14.2.16", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.16.tgz", - "integrity": "sha512-uFT34QojYkf0+nn6MEZ4gIWQ5aqGF11uIZ1HSxG+cSbj+Mg3+tYm8qXYd3dKN5jqKUm5rBVvf1PBRO/MeQ6rxw==", + "version": "14.2.33", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.33.tgz", + "integrity": "sha512-HqYnb6pxlsshoSTubdXKu15g3iivcbsMXg4bYpjL2iS/V6aQot+iyF4BUc2qA/J/n55YtvE4PHMKWBKGCF/+wA==", "cpu": [ "arm64" ], @@ -237,9 +240,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "14.2.16", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.16.tgz", - "integrity": "sha512-mCecsFkYezem0QiZlg2bau3Xul77VxUD38b/auAjohMA22G9KTJneUYMv78vWoCCFkleFAhY1NIvbyjj1ncG9g==", + "version": "14.2.33", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.33.tgz", + "integrity": "sha512-8HGBeAE5rX3jzKvF593XTTFg3gxeU4f+UWnswa6JPhzaR6+zblO5+fjltJWIZc4aUalqTclvN2QtTC37LxvZAA==", "cpu": [ "x64" ], @@ -253,9 +256,9 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "14.2.16", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.16.tgz", - "integrity": "sha512-yhkNA36+ECTC91KSyZcgWgKrYIyDnXZj8PqtJ+c2pMvj45xf7y/HrgI17hLdrcYamLfVt7pBaJUMxADtPaczHA==", + "version": "14.2.33", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.33.tgz", + "integrity": "sha512-JXMBka6lNNmqbkvcTtaX8Gu5by9547bukHQvPoLe9VRBx1gHwzf5tdt4AaezW85HAB3pikcvyqBToRTDA4DeLw==", "cpu": [ "arm64" ], @@ -269,9 +272,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "14.2.16", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.16.tgz", - "integrity": "sha512-X2YSyu5RMys8R2lA0yLMCOCtqFOoLxrq2YbazFvcPOE4i/isubYjkh+JCpRmqYfEuCVltvlo+oGfj/b5T2pKUA==", + "version": "14.2.33", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.33.tgz", + "integrity": "sha512-Bm+QulsAItD/x6Ih8wGIMfRJy4G73tu1HJsrccPW6AfqdZd0Sfm5Imhgkgq2+kly065rYMnCOxTBvmvFY1BKfg==", "cpu": [ "arm64" ], @@ -285,9 +288,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "14.2.16", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.16.tgz", - "integrity": "sha512-9AGcX7VAkGbc5zTSa+bjQ757tkjr6C/pKS7OK8cX7QEiK6MHIIezBLcQ7gQqbDW2k5yaqba2aDtaBeyyZh1i6Q==", + "version": "14.2.33", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.33.tgz", + "integrity": "sha512-FnFn+ZBgsVMbGDsTqo8zsnRzydvsGV8vfiWwUo1LD8FTmPTdV+otGSWKc4LJec0oSexFnCYVO4hX8P8qQKaSlg==", "cpu": [ "x64" ], @@ -301,9 +304,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "14.2.16", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.16.tgz", - "integrity": "sha512-Klgeagrdun4WWDaOizdbtIIm8khUDQJ/5cRzdpXHfkbY91LxBXeejL4kbZBrpR/nmgRrQvmz4l3OtttNVkz2Sg==", + "version": "14.2.33", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.33.tgz", + "integrity": "sha512-345tsIWMzoXaQndUTDv1qypDRiebFxGYx9pYkhwY4hBRaOLt8UGfiWKr9FSSHs25dFIf8ZqIFaPdy5MljdoawA==", "cpu": [ "x64" ], @@ -317,9 +320,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "14.2.16", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.16.tgz", - "integrity": "sha512-PwW8A1UC1Y0xIm83G3yFGPiOBftJK4zukTmk7DI1CebyMOoaVpd8aSy7K6GhobzhkjYvqS/QmzcfsWG2Dwizdg==", + "version": "14.2.33", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.33.tgz", + "integrity": "sha512-nscpt0G6UCTkrT2ppnJnFsYbPDQwmum4GNXYTeoTIdsmMydSKFz9Iny2jpaRupTb+Wl298+Rh82WKzt9LCcqSQ==", "cpu": [ "arm64" ], @@ -333,9 +336,9 @@ } }, "node_modules/@next/swc-win32-ia32-msvc": { - "version": "14.2.16", - "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.16.tgz", - "integrity": "sha512-jhPl3nN0oKEshJBNDAo0etGMzv0j3q3VYorTSFqH1o3rwv1MQRdor27u1zhkgsHPNeY1jxcgyx1ZsCkDD1IHgg==", + "version": "14.2.33", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.33.tgz", + "integrity": "sha512-pc9LpGNKhJ0dXQhZ5QMmYxtARwwmWLpeocFmVG5Z0DzWq5Uf0izcI8tLc+qOpqxO1PWqZ5A7J1blrUIKrIFc7Q==", "cpu": [ "ia32" ], @@ -349,9 +352,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "14.2.16", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.16.tgz", - "integrity": "sha512-OA7NtfxgirCjfqt+02BqxC3MIgM/JaGjw9tOe4fyZgPsqfseNiMPnCRP44Pfs+Gpo9zPN+SXaFsgP6vk8d571A==", + "version": "14.2.33", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.33.tgz", + "integrity": "sha512-nOjfZMy8B94MdisuzZo9/57xuFVLHJaDj5e/xrduJp9CV2/HrfxTRH2fbyLe+K9QT41WBLUd4iXX3R7jBp0EUg==", "cpu": [ "x64" ], @@ -2463,6 +2466,13 @@ "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", "license": "MIT" }, + "node_modules/@types/js-cookie": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-3.0.6.tgz", + "integrity": "sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/linkify-it": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", @@ -2499,14 +2509,14 @@ "version": "15.7.15", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/@types/react": { "version": "18.3.24", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.24.tgz", "integrity": "sha512-0dLEBsA1kI3OezMBF8nSsb7Nk19ZnsyE1LLhB8r27KbgU5H4pvuqZLdtE+aUkJVoXgTVuA+iLIwmZ0TuK4tx6A==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@types/prop-types": "*", @@ -2517,7 +2527,7 @@ "version": "18.3.7", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", - "dev": true, + "devOptional": true, "license": "MIT", "peerDependencies": { "@types/react": "^18.0.0" @@ -3080,12 +3090,30 @@ "jiti": "lib/jiti-cli.mjs" } }, + "node_modules/js-cookie": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", + "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "license": "MIT" }, + "node_modules/jwt-decode": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", + "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/lightningcss": { "version": "1.30.1", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz", @@ -3452,12 +3480,12 @@ } }, "node_modules/next": { - "version": "14.2.16", - "resolved": "https://registry.npmjs.org/next/-/next-14.2.16.tgz", - "integrity": "sha512-LcO7WnFu6lYSvCzZoo1dB+IO0xXz5uEv52HF1IUN0IqVTUIZGHuuR10I5efiLadGt+4oZqTcNZyVVEem/TM5nA==", + "version": "14.2.33", + "resolved": "https://registry.npmjs.org/next/-/next-14.2.33.tgz", + "integrity": "sha512-GiKHLsD00t4ACm1p00VgrI0rUFAC9cRDGReKyERlM57aeEZkOQGcZTpIbsGn0b562FTPJWmYfKwplfO9EaT6ng==", "license": "MIT", "dependencies": { - "@next/env": "14.2.16", + "@next/env": "14.2.33", "@swc/helpers": "0.5.5", "busboy": "1.6.0", "caniuse-lite": "^1.0.30001579", @@ -3472,15 +3500,15 @@ "node": ">=18.17.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "14.2.16", - "@next/swc-darwin-x64": "14.2.16", - "@next/swc-linux-arm64-gnu": "14.2.16", - "@next/swc-linux-arm64-musl": "14.2.16", - "@next/swc-linux-x64-gnu": "14.2.16", - "@next/swc-linux-x64-musl": "14.2.16", - "@next/swc-win32-arm64-msvc": "14.2.16", - "@next/swc-win32-ia32-msvc": "14.2.16", - "@next/swc-win32-x64-msvc": "14.2.16" + "@next/swc-darwin-arm64": "14.2.33", + "@next/swc-darwin-x64": "14.2.33", + "@next/swc-linux-arm64-gnu": "14.2.33", + "@next/swc-linux-arm64-musl": "14.2.33", + "@next/swc-linux-x64-gnu": "14.2.33", + "@next/swc-linux-x64-musl": "14.2.33", + "@next/swc-win32-arm64-msvc": "14.2.33", + "@next/swc-win32-ia32-msvc": "14.2.33", + "@next/swc-win32-x64-msvc": "14.2.33" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", @@ -3579,7 +3607,6 @@ "version": "8.5.6", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", - "dev": true, "funding": [ { "type": "opencollective", @@ -4126,7 +4153,6 @@ "version": "4.1.13", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.13.tgz", "integrity": "sha512-i+zidfmTqtwquj4hMEwdjshYYgMbOrPzb9a0M3ZgNa0JMoZeFC6bxZvO8yr8ozS6ix2SDz0+mvryPeBs2TFE+w==", - "dev": true, "license": "MIT" }, "node_modules/tailwindcss-animate": { diff --git a/package.json b/package.json index b7b32e0..f2020b5 100644 --- a/package.json +++ b/package.json @@ -51,8 +51,10 @@ "embla-carousel-react": "8.5.1", "geist": "^1.3.1", "input-otp": "1.4.1", + "js-cookie": "^3.0.5", + "jwt-decode": "^4.0.0", "lucide-react": "^0.454.0", - "next": "14.2.16", + "next": "^14.2.33", "next-themes": "^0.4.6", "react": "^18", "react-day-picker": "9.8.0", @@ -68,6 +70,7 @@ }, "devDependencies": { "@tailwindcss/postcss": "^4.1.9", + "@types/js-cookie": "^3.0.6", "@types/node": "^22", "@types/react": "^18", "@types/react-dom": "^18", diff --git a/styles/globals.css b/styles/globals.css deleted file mode 100644 index 23cc7e3..0000000 --- a/styles/globals.css +++ /dev/null @@ -1,125 +0,0 @@ -@import 'tailwindcss'; -@import 'tw-animate-css'; - -@custom-variant dark (&:is(.dark *)); - -:root { - --background: oklch(1 0 0); - --foreground: oklch(0.145 0 0); - --card: oklch(1 0 0); - --card-foreground: oklch(0.145 0 0); - --popover: oklch(1 0 0); - --popover-foreground: oklch(0.145 0 0); - --primary: oklch(0.205 0 0); - --primary-foreground: oklch(0.985 0 0); - --secondary: oklch(0.97 0 0); - --secondary-foreground: oklch(0.205 0 0); - --muted: oklch(0.97 0 0); - --muted-foreground: oklch(0.556 0 0); - --accent: oklch(0.97 0 0); - --accent-foreground: oklch(0.205 0 0); - --destructive: oklch(0.577 0.245 27.325); - --destructive-foreground: oklch(0.577 0.245 27.325); - --border: oklch(0.922 0 0); - --input: oklch(0.922 0 0); - --ring: oklch(0.708 0 0); - --chart-1: oklch(0.646 0.222 41.116); - --chart-2: oklch(0.6 0.118 184.704); - --chart-3: oklch(0.398 0.07 227.392); - --chart-4: oklch(0.828 0.189 84.429); - --chart-5: oklch(0.769 0.188 70.08); - --radius: 0.625rem; - --sidebar: oklch(0.985 0 0); - --sidebar-foreground: oklch(0.145 0 0); - --sidebar-primary: oklch(0.205 0 0); - --sidebar-primary-foreground: oklch(0.985 0 0); - --sidebar-accent: oklch(0.97 0 0); - --sidebar-accent-foreground: oklch(0.205 0 0); - --sidebar-border: oklch(0.922 0 0); - --sidebar-ring: oklch(0.708 0 0); -} - -.dark { - --background: oklch(0.145 0 0); - --foreground: oklch(0.985 0 0); - --card: oklch(0.145 0 0); - --card-foreground: oklch(0.985 0 0); - --popover: oklch(0.145 0 0); - --popover-foreground: oklch(0.985 0 0); - --primary: oklch(0.985 0 0); - --primary-foreground: oklch(0.205 0 0); - --secondary: oklch(0.269 0 0); - --secondary-foreground: oklch(0.985 0 0); - --muted: oklch(0.269 0 0); - --muted-foreground: oklch(0.708 0 0); - --accent: oklch(0.269 0 0); - --accent-foreground: oklch(0.985 0 0); - --destructive: oklch(0.396 0.141 25.723); - --destructive-foreground: oklch(0.637 0.237 25.331); - --border: oklch(0.269 0 0); - --input: oklch(0.269 0 0); - --ring: oklch(0.439 0 0); - --chart-1: oklch(0.488 0.243 264.376); - --chart-2: oklch(0.696 0.17 162.48); - --chart-3: oklch(0.769 0.188 70.08); - --chart-4: oklch(0.627 0.265 303.9); - --chart-5: oklch(0.645 0.246 16.439); - --sidebar: oklch(0.205 0 0); - --sidebar-foreground: oklch(0.985 0 0); - --sidebar-primary: oklch(0.488 0.243 264.376); - --sidebar-primary-foreground: oklch(0.985 0 0); - --sidebar-accent: oklch(0.269 0 0); - --sidebar-accent-foreground: oklch(0.985 0 0); - --sidebar-border: oklch(0.269 0 0); - --sidebar-ring: oklch(0.439 0 0); -} - -@theme inline { - --font-sans: var(--font-geist-sans); - --font-mono: var(--font-geist-mono); - --color-background: var(--background); - --color-foreground: var(--foreground); - --color-card: var(--card); - --color-card-foreground: var(--card-foreground); - --color-popover: var(--popover); - --color-popover-foreground: var(--popover-foreground); - --color-primary: var(--primary); - --color-primary-foreground: var(--primary-foreground); - --color-secondary: var(--secondary); - --color-secondary-foreground: var(--secondary-foreground); - --color-muted: var(--muted); - --color-muted-foreground: var(--muted-foreground); - --color-accent: var(--accent); - --color-accent-foreground: var(--accent-foreground); - --color-destructive: var(--destructive); - --color-destructive-foreground: var(--destructive-foreground); - --color-border: var(--border); - --color-input: var(--input); - --color-ring: var(--ring); - --color-chart-1: var(--chart-1); - --color-chart-2: var(--chart-2); - --color-chart-3: var(--chart-3); - --color-chart-4: var(--chart-4); - --color-chart-5: var(--chart-5); - --radius-sm: calc(var(--radius) - 4px); - --radius-md: calc(var(--radius) - 2px); - --radius-lg: var(--radius); - --radius-xl: calc(var(--radius) + 4px); - --color-sidebar: var(--sidebar); - --color-sidebar-foreground: var(--sidebar-foreground); - --color-sidebar-primary: var(--sidebar-primary); - --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); - --color-sidebar-accent: var(--sidebar-accent); - --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); - --color-sidebar-border: var(--sidebar-border); - --color-sidebar-ring: var(--sidebar-ring); -} - -@layer base { - * { - @apply border-border outline-ring/50; - } - body { - @apply bg-background text-foreground; - } -} -- 2.47.2 From 65087a9f512d5c5f1a60fd4416f715ac37b19c0e Mon Sep 17 00:00:00 2001 From: StsDanilo Date: Thu, 9 Oct 2025 17:13:51 -0300 Subject: [PATCH 3/3] merge fix --- app/layout.tsx | 11 +- components/LoginForm.tsx | 422 ++++++++++++++++++--------------------- services/api.mjs | 144 +++++++------ 3 files changed, 277 insertions(+), 300 deletions(-) diff --git a/app/layout.tsx b/app/layout.tsx index 529561d..797f31e 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -6,6 +6,9 @@ import "./globals.css"; import { Toaster } from "@/components/ui/toaster"; // [PASSO 1.2] - Importando o nosso provider import { AppointmentsProvider } from "./context/AppointmentsContext"; +import { AccessibilityProvider } from "./context/AccessibilityContext"; +import { AccessibilityModal } from "@/components/accessibility-modal"; +import { ThemeInitializer } from "@/components/theme-initializer"; export default function RootLayout({ children, @@ -13,10 +16,14 @@ export default function RootLayout({ children: React.ReactNode; }>) { return ( - + {/* [PASSO 1.2] - Envolvendo a aplicação com o provider */} - {children} + + + {children} + + diff --git a/components/LoginForm.tsx b/components/LoginForm.tsx index 8c400cb..71a6ab4 100644 --- a/components/LoginForm.tsx +++ b/components/LoginForm.tsx @@ -1,247 +1,223 @@ // Caminho: components/LoginForm.tsx -"use client" +"use client"; -import type React from "react" -import { useState } from "react" -import { useRouter } from "next/navigation" -import Link from "next/link" -import Cookies from "js-cookie" -import { jwtDecode } from "jwt-decode" -import { cn } from "@/lib/utils" +import type React from "react"; +import { useState } from "react"; +import { useRouter } from "next/navigation"; +import Link from "next/link"; +import Cookies from "js-cookie"; +import { jwtDecode } from "jwt-decode"; +import { cn } from "@/lib/utils"; // Componentes Shadcn UI -import { Button } from "@/components/ui/button" -import { Input } from "@/components/ui/input" -import { Label } from "@/components/ui/label" -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" -import { Separator } from "@/components/ui/separator" +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { Separator } from "@/components/ui/separator"; +import { apikey } from "@/services/api.mjs"; // Hook customizado -import { useToast } from "@/hooks/use-toast" +import { useToast } from "@/hooks/use-toast"; // Ícones -import { Eye, EyeOff, Mail, Lock, Loader2, UserCheck, Stethoscope, IdCard, Receipt } from "lucide-react" +import { Eye, EyeOff, Mail, Lock, Loader2, UserCheck, Stethoscope, IdCard, Receipt } from "lucide-react"; interface LoginFormProps { - title: string - description: string - role: "secretary" | "doctor" | "patient" | "admin" | "manager" | "finance" - themeColor: "blue" | "green" | "orange" - redirectPath: string - children?: React.ReactNode + title: string; + description: string; + role: "secretary" | "doctor" | "patient" | "admin" | "manager" | "finance"; + themeColor: "blue" | "green" | "orange"; + redirectPath: string; + children?: React.ReactNode; } interface FormState { - email: string - password: string + email: string; + password: string; } // Supondo que o payload do seu token tenha esta estrutura interface DecodedToken { - name: string - email: string - role: string - exp: number - // Adicione outros campos que seu token possa ter + name: string; + email: string; + role: string; + exp: number; + // Adicione outros campos que seu token possa ter } const themeClasses = { - blue: { - iconBg: "bg-blue-100", - iconText: "text-blue-600", - button: "bg-blue-600 hover:bg-blue-700", - link: "text-blue-600 hover:text-blue-700", - focus: "focus:border-blue-500 focus:ring-blue-500", - }, - green: { - iconBg: "bg-green-100", - iconText: "text-green-600", - button: "bg-green-600 hover:bg-green-700", - link: "text-green-600 hover:text-green-700", - focus: "focus:border-green-500 focus:ring-green-500", - }, - orange: { - iconBg: "bg-orange-100", - iconText: "text-orange-600", - button: "bg-orange-600 hover:bg-orange-700", - link: "text-orange-600 hover:text-orange-700", - focus: "focus:border-orange-500 focus:ring-orange-500", - }, -} - -const roleIcons = { - secretary: UserCheck, - patient: Stethoscope, - doctor: Stethoscope, - admin: UserCheck, - manager: IdCard, - finance: Receipt, -} - -export function LoginForm({ title, description, role, themeColor, redirectPath, children }: LoginFormProps) { - const [form, setForm] = useState({ email: "", password: "" }) - const [showPassword, setShowPassword] = useState(false) - const [isLoading, setIsLoading] = useState(false) - const router = useRouter() - const { toast } = useToast() - - const currentTheme = themeClasses[themeColor] - const Icon = roleIcons[role] - - // ================================================================== - // AJUSTE PRINCIPAL NA LÓGICA DE LOGIN - // ================================================================== - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - setIsLoading(true); - - const LOGIN_URL = "https://yuanqfswhberkoevtmfr.supabase.co/auth/v1/token?grant_type=password"; - const API_KEY = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY; - - if (!API_KEY) { - toast({ - title: "Erro de Configuração", - description: "A chave da API não foi encontrada.", - variant: "destructive", - }); - setIsLoading(false); - return; - } - - try { - const response = await fetch(LOGIN_URL, { - method: "POST", - headers: { - "Content-Type": "application/json", - "apikey": API_KEY, - }, - body: JSON.stringify({ email: form.email, password: form.password }), - }); - - const data = await response.json(); - - if (!response.ok) { - throw new Error(data.error_description || "Credenciais inválidas. Tente novamente."); - } - - const accessToken = data.access_token; - const user = data.user; - - /* =================== Verificação de Role Desativada Temporariamente =================== */ - // if (user.user_metadata.role !== role) { - // toast({ title: "Acesso Negado", ... }); - // return; - // } - /* ===================================================================================== */ - - Cookies.set("access_token", accessToken, { expires: 1, secure: true }); - localStorage.setItem('user_info', JSON.stringify(user)); - - toast({ - title: "Login bem-sucedido!", - description: `Bem-vindo(a), ${user.user_metadata.full_name || 'usuário'}! Redirecionando...`, - }); - - router.push(redirectPath); - - } catch (error) { - toast({ - title: "Erro no Login", - description: error instanceof Error ? error.message : "Ocorreu um erro inesperado.", - }); - } finally { - setIsLoading(false); - } + blue: { + iconBg: "bg-blue-100", + iconText: "text-blue-600", + button: "bg-blue-600 hover:bg-blue-700", + link: "text-blue-600 hover:text-blue-700", + focus: "focus:border-blue-500 focus:ring-blue-500", + }, + green: { + iconBg: "bg-green-100", + iconText: "text-green-600", + button: "bg-green-600 hover:bg-green-700", + link: "text-green-600 hover:text-green-700", + focus: "focus:border-green-500 focus:ring-green-500", + }, + orange: { + iconBg: "bg-orange-100", + iconText: "text-orange-600", + button: "bg-orange-600 hover:bg-orange-700", + link: "text-orange-600 hover:text-orange-700", + focus: "focus:border-orange-500 focus:ring-orange-500", + }, }; - // O JSX do return permanece exatamente o mesmo, preservando seus ajustes. - return ( - - -
- -
-
- {title} - {description} -
-
- -
- {/* Inputs e Botão */} -
- -
- - setForm({ ...form, email: e.target.value })} - className={cn("pl-11 h-12 border-slate-200", currentTheme.focus)} - required - disabled={isLoading} - /> -
-
-
- -
- - setForm({ ...form, password: e.target.value })} - className={cn("pl-11 pr-12 h-12 border-slate-200", currentTheme.focus)} - required - disabled={isLoading} - /> - -
-
- -
- {/* Conteúdo Extra (children) */} -
- {children ? ( -
-
-
-
+const roleIcons = { + secretary: UserCheck, + patient: Stethoscope, + doctor: Stethoscope, + admin: UserCheck, + manager: IdCard, + finance: Receipt, +}; + +export function LoginForm({ title, description, role, themeColor, redirectPath, children }: LoginFormProps) { + const [form, setForm] = useState({ email: "", password: "" }); + const [showPassword, setShowPassword] = useState(false); + const [isLoading, setIsLoading] = useState(false); + const router = useRouter(); + const { toast } = useToast(); + + const currentTheme = themeClasses[themeColor]; + const Icon = roleIcons[role]; + + // ================================================================== + // AJUSTE PRINCIPAL NA LÓGICA DE LOGIN + // ================================================================== + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setIsLoading(true); + + const LOGIN_URL = "https://yuanqfswhberkoevtmfr.supabase.co/auth/v1/token?grant_type=password"; + const API_KEY = apikey; + + if (!API_KEY) { + toast({ + title: "Erro de Configuração", + description: "A chave da API não foi encontrada.", + }); + setIsLoading(false); + return; + } + + try { + const response = await fetch(LOGIN_URL, { + method: "POST", + headers: { + "Content-Type": "application/json", + apikey: API_KEY, + }, + body: JSON.stringify({ email: form.email, password: form.password }), + }); + + const data = await response.json(); + + if (!response.ok) { + throw new Error(data.error_description || "Credenciais inválidas. Tente novamente."); + } + + const accessToken = data.access_token; + const user = data.user; + + /* =================== Verificação de Role Desativada Temporariamente =================== */ + // if (user.user_metadata.role !== role) { + // toast({ title: "Acesso Negado", ... }); + // return; + // } + /* ===================================================================================== */ + + Cookies.set("access_token", accessToken, { expires: 1, secure: true }); + localStorage.setItem("user_info", JSON.stringify(user)); + + toast({ + title: "Login bem-sucedido!", + description: `Bem-vindo(a), ${user.user_metadata.full_name || "usuário"}! Redirecionando...`, + }); + + router.push(redirectPath); + } catch (error) { + toast({ + title: "Erro no Login", + description: error instanceof Error ? error.message : "Ocorreu um erro inesperado.", + }); + } finally { + setIsLoading(false); + } + }; + + // O JSX do return permanece exatamente o mesmo, preservando seus ajustes. + return ( + + +
+
-
- Novo por aqui? +
+ {title} + {description}
-
- {children} -
- ) : ( - <> -
- - ou -
-
- - Voltar à página inicial - -
- - )} -
- - - ) -} \ No newline at end of file + + +
+ {/* Inputs e Botão */} +
+ +
+ + setForm({ ...form, email: e.target.value })} className={cn("pl-11 h-12 border-slate-200", currentTheme.focus)} required disabled={isLoading} /> +
+
+
+ +
+ + setForm({ ...form, password: e.target.value })} className={cn("pl-11 pr-12 h-12 border-slate-200", currentTheme.focus)} required disabled={isLoading} /> + +
+
+ +
+ {/* Conteúdo Extra (children) */} +
+ {children ? ( +
+
+
+
+
+
+ Novo por aqui? +
+
+ {children} +
+ ) : ( + <> +
+ + ou +
+
+ + Voltar à página inicial + +
+ + )} +
+
+ + ); +} diff --git a/services/api.mjs b/services/api.mjs index 73d3721..360c744 100644 --- a/services/api.mjs +++ b/services/api.mjs @@ -1,90 +1,84 @@ - const BASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co"; -const API_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ"; +const API_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ"; +export const apikey = API_KEY; var tempToken; export async function login() { - const response = await fetch("https://yuanqfswhberkoevtmfr.supabase.co/auth/v1/token?grant_type=password", { - method: "POST", - headers: { - "Content-Type": "application/json", - Prefer: "return=representation", - "apikey": API_KEY, // valor fixo - }, - body: JSON.stringify({ email: "riseup@popcode.com.br", password: "riseup" }), - }); + const response = await fetch("https://yuanqfswhberkoevtmfr.supabase.co/auth/v1/token?grant_type=password", { + method: "POST", + headers: { + "Content-Type": "application/json", + Prefer: "return=representation", + apikey: API_KEY, // valor fixo + }, + body: JSON.stringify({ email: "riseup@popcode.com.br", password: "riseup" }), + }); - const data = await response.json(); - - localStorage.setItem("token", data.access_token); - - return data; + const data = await response.json(); + + localStorage.setItem("token", data.access_token); + + return data; } - let loginPromise = login(); - - async function request(endpoint, options = {}) { - - if (loginPromise) { - try { - await loginPromise; - } catch (error) { - console.error("Falha na autenticação inicial:", error); - } - - loginPromise = null; - } - - const token = localStorage.getItem("token"); - - const headers = { - "Content-Type": "application/json", - "apikey": API_KEY, - ...(token ? { "Authorization": `Bearer ${token}` } : {}), - ...options.headers, - }; - - try { - const response = await fetch(`${BASE_URL}${endpoint}`, { - ...options, - headers, - }); - - if (!response.ok) { - - let errorBody = `Status: ${response.status}`; - try { - const contentType = response.headers.get("content-type"); - if (contentType && contentType.includes("application/json")) { - const jsonError = await response.json(); - - errorBody = jsonError.message || JSON.stringify(jsonError); - } else { - errorBody = await response.text(); + if (loginPromise) { + try { + await loginPromise; + } catch (error) { + console.error("Falha na autenticação inicial:", error); } - } catch (e) { - - errorBody = `Status: ${response.status} - Falha ao ler corpo do erro.`; - } - - throw new Error(`Erro HTTP: ${response.status} - Detalhes: ${errorBody}`); + + loginPromise = null; } - const contentType = response.headers.get("content-type"); - if (response.status === 204 || (contentType && !contentType.includes("application/json")) || !contentType) { - return {}; + + const token = localStorage.getItem("token"); + + const headers = { + "Content-Type": "application/json", + apikey: API_KEY, + ...(token ? { Authorization: `Bearer ${token}` } : {}), + ...options.headers, + }; + + try { + const response = await fetch(`${BASE_URL}${endpoint}`, { + ...options, + headers, + }); + + if (!response.ok) { + let errorBody = `Status: ${response.status}`; + try { + const contentType = response.headers.get("content-type"); + if (contentType && contentType.includes("application/json")) { + const jsonError = await response.json(); + + errorBody = jsonError.message || JSON.stringify(jsonError); + } else { + errorBody = await response.text(); + } + } catch (e) { + errorBody = `Status: ${response.status} - Falha ao ler corpo do erro.`; + } + + throw new Error(`Erro HTTP: ${response.status} - Detalhes: ${errorBody}`); + } + const contentType = response.headers.get("content-type"); + if (response.status === 204 || (contentType && !contentType.includes("application/json")) || !contentType) { + return {}; + } + return await response.json(); + } catch (error) { + console.error("Erro na requisição:", error); + throw error; } - return await response.json(); - } catch (error) { - console.error("Erro na requisição:", error); - throw error; - } } export const api = { - get: (endpoint) => request(endpoint, { method: "GET" }), - post: (endpoint, data) => request(endpoint, { method: "POST", body: JSON.stringify(data) }), - patch: (endpoint, data) => request(endpoint, { method: "PATCH", body: JSON.stringify(data) }), - delete: (endpoint) => request(endpoint, { method: "DELETE" }), -}; \ No newline at end of file + get: (endpoint) => request(endpoint, { method: "GET" }), + post: (endpoint, data) => request(endpoint, { method: "POST", body: JSON.stringify(data) }), + patch: (endpoint, data) => request(endpoint, { method: "PATCH", body: JSON.stringify(data) }), + delete: (endpoint) => request(endpoint, { method: "DELETE" }), +}; -- 2.47.2