diff --git a/app/doctor/login/page.tsx b/app/doctor/login/page.tsx
index c9cf646..051706e 100644
--- a/app/doctor/login/page.tsx
+++ b/app/doctor/login/page.tsx
@@ -1,11 +1,31 @@
// Caminho: app/(doctor)/login/page.tsx
import { LoginForm } from "@/components/LoginForm";
+import Link from "next/link"; // Adicionado para o link de "Voltar"
export default function DoctorLoginPage() {
+ // NOTA: Esta página se tornou obsoleta com a criação do /login central.
+ // O ideal no futuro é deletar esta página e redirecionar os usuários.
+
return (
-
+
+
Área do Médico
+
Acesse o sistema médico
+
+ {/* --- ALTERAÇÃO PRINCIPAL AQUI --- */}
+ {/* Chamando o LoginForm unificado sem props desnecessárias */}
+
+ {/* Adicionamos um link de "Voltar" como filho (children) */}
+
+
+
+ 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 d00e41c..636680a 100644
--- a/app/finance/login/page.tsx
+++ b/app/finance/login/page.tsx
@@ -1,12 +1,31 @@
// Caminho: app/(finance)/login/page.tsx
import { LoginForm } from "@/components/LoginForm";
+import Link from "next/link"; // Adicionado para o link de "Voltar"
export default function FinanceLoginPage() {
+ // NOTA: Esta página se tornou obsoleta com a criação do /login central.
+ // O ideal no futuro é deletar esta página e redirecionar os usuários.
+
return (
- // Fundo com gradiente laranja, como no seu código original
-
+
+
Área Financeira
+
Acesse o sistema de faturamento
+
+ {/* --- ALTERAÇÃO PRINCIPAL AQUI --- */}
+ {/* Chamando o LoginForm unificado sem props desnecessárias */}
+
+ {/* Adicionamos um link de "Voltar" como filho (children) */}
+
+
+
+ Voltar à página inicial
+
+
+
+
+
);
-}
+}
\ No newline at end of file
diff --git a/app/login/page.tsx b/app/login/page.tsx
new file mode 100644
index 0000000..b193142
--- /dev/null
+++ b/app/login/page.tsx
@@ -0,0 +1,82 @@
+// Caminho: app/login/page.tsx
+
+import { LoginForm } from "@/components/LoginForm";
+import Link from "next/link";
+import Image from "next/image";
+import { Button } from "@/components/ui/button";
+import { ArrowLeft } from "lucide-react"; // Importa o ícone de seta
+
+export default function LoginPage() {
+ return (
+
+
+ {/* PAINEL ESQUERDO: O Formulário */}
+
+
+ {/* Link para Voltar */}
+
+
+
+ Voltar à página inicial
+
+
+
+ {/* O contêiner principal que agora terá a sombra e o estilo de card */}
+
+
+
Acesse sua conta
+
Bem-vindo(a) de volta ao MedConnect!
+
+
+
+ {/* Children para o LoginForm */}
+
+
+
+ Esqueceu sua senha?
+
+
+
+
+
+
+ Não tem uma conta de paciente?
+
+
+ Crie uma agora
+
+
+
+
+
+
+ {/* PAINEL DIREITO: A Imagem e Branding */}
+
+ {/* Usamos o componente para otimização e performance */}
+
+ {/* Camada de sobreposição para escurecer a imagem e destacar o texto */}
+
+ {/* BLOCO DE NOME ADICIONADO */}
+
+
+ MedConnect
+
+
+
+ Tecnologia e Cuidado a Serviço da Sua Saúde.
+
+
+ Acesse seu portal para uma experiência de saúde integrada, segura e eficiente.
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/app/manager/login/page.tsx b/app/manager/login/page.tsx
index db3aadc..cb236d3 100644
--- a/app/manager/login/page.tsx
+++ b/app/manager/login/page.tsx
@@ -1,12 +1,31 @@
// Caminho: app/(manager)/login/page.tsx
import { LoginForm } from "@/components/LoginForm";
+import Link from "next/link"; // Adicionado para o link de "Voltar"
export default function ManagerLoginPage() {
+ // NOTA: Esta página se tornou obsoleta com a criação do /login central.
+ // O ideal no futuro é deletar esta página e redirecionar os usuários.
+
return (
- // Mantemos o seu plano de fundo original
-
+
+
Área do Gestor
+
Acesse o sistema médico
+
+ {/* --- ALTERAÇÃO PRINCIPAL AQUI --- */}
+ {/* Chamando o LoginForm unificado sem props desnecessárias */}
+
+ {/* Adicionamos um link de "Voltar" como filho (children) */}
+
+
+
+ Voltar à página inicial
+
+
+
+
+
);
-}
+}
\ No newline at end of file
diff --git a/app/page.tsx b/app/page.tsx
index b254689..5c3d1f6 100644
--- a/app/page.tsx
+++ b/app/page.tsx
@@ -17,7 +17,7 @@ export default function InicialPage() {
MedConnect
{}
-
+
-
+
);
-}
+}
\ No newline at end of file
diff --git a/app/secretary/login/page.tsx b/app/secretary/login/page.tsx
index 3dabba8..fb9310c 100644
--- a/app/secretary/login/page.tsx
+++ b/app/secretary/login/page.tsx
@@ -1,11 +1,31 @@
// Caminho: app/(secretary)/login/page.tsx
import { LoginForm } from "@/components/LoginForm";
+import Link from "next/link"; // Adicionado para o link de "Voltar"
export default function SecretaryLoginPage() {
+ // NOTA: Esta página se tornou obsoleta com a criação do /login central.
+ // O ideal no futuro é deletar esta página e redirecionar os usuários.
+
return (
-
-
+
+
Área da Secretária
+
Acesse o sistema de gerenciamento
+
+ {/* --- ALTERAÇÃO PRINCIPAL AQUI --- */}
+ {/* Chamando o LoginForm unificado sem props desnecessárias */}
+
+ {/* Adicionamos um link de "Voltar" como filho (children) */}
+
+
+
+ Voltar à página inicial
+
+
+
+
+
+
);
-}
+}
\ No newline at end of file
diff --git a/components/LoginForm.tsx b/components/LoginForm.tsx
index 71a6ab4..1176948 100644
--- a/components/LoginForm.tsx
+++ b/components/LoginForm.tsx
@@ -1,223 +1,157 @@
// 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 { cn } from "@/lib/utils"
+
+// Nossos serviços de API centralizados
+import { loginWithEmailAndPassword, api } from "@/services/api";
// 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 { apikey } from "@/services/api.mjs";
-
-// Hook customizado
-import { useToast } from "@/hooks/use-toast";
+import { Button } from "@/components/ui/button"
+import { Input } from "@/components/ui/input"
+import { Label } from "@/components/ui/label"
+import { Card, CardContent } from "@/components/ui/card"
+import { useToast } from "@/hooks/use-toast"
// Ícones
-import { Eye, EyeOff, Mail, Lock, Loader2, UserCheck, Stethoscope, IdCard, Receipt } from "lucide-react";
+import { Eye, EyeOff, Loader2, Mail, Lock } 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;
+ 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
-}
+export function LoginForm({ 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 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",
- },
-};
+ // ==================================================================
+ // LÓGICA DE LOGIN INTELIGENTE E CENTRALIZADA
+ // ==================================================================
+ const handleSubmit = async (e: React.FormEvent) => {
+ e.preventDefault();
+ setIsLoading(true);
+ localStorage.removeItem("token");
+ localStorage.removeItem("user_info");
-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 authData = await loginWithEmailAndPassword(form.email, form.password);
+ const user = authData.user;
+ if (!user || !user.id) {
+ throw new Error("Resposta de autenticação inválida: ID do usuário não encontrado.");
}
- 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 rolesData = await api.get(`/rest/v1/user_roles?user_id=eq.${user.id}&select=role`);
- 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);
+ if (!rolesData || rolesData.length === 0) {
+ throw new Error("Login bem-sucedido, mas nenhum perfil de acesso foi encontrado para este usuário.");
}
- };
- // O JSX do return permanece exatamente o mesmo, preservando seus ajustes.
- return (
-
-
-
-
-
-
- {title}
- {description}
-
-
-
-
- {/* Conteúdo Extra (children) */}
-
- {children ? (
-
-
-
-
-
-
- Novo por aqui?
-
-
- {children}
-
- ) : (
- <>
-
-
- ou
-
-
-
- Voltar à página inicial
-
-
- >
- )}
-
-
-
- );
-}
+ const userRole = rolesData[0].role;
+ const completeUserInfo = { ...user, user_metadata: { ...user.user_metadata, role: userRole } };
+ localStorage.setItem('user_info', JSON.stringify(completeUserInfo));
+
+ let redirectPath = "";
+ switch (userRole) {
+ case "admin":
+ case "manager": redirectPath = "/manager/home"; break;
+ case "medico": redirectPath = "/doctor/medicos"; break;
+ case "secretary": redirectPath = "/secretary/pacientes"; break;
+ case "patient": redirectPath = "/patient/dashboard"; break;
+ case "finance": redirectPath = "/finance/home"; break;
+ }
+
+ if (!redirectPath) {
+ throw new Error(`O perfil de acesso '${userRole}' não é válido para login. Contate o suporte.`);
+ }
+
+ toast({
+ title: "Login bem-sucedido!",
+ description: `Bem-vindo(a)! Redirecionando...`,
+ });
+
+ router.push(redirectPath);
+
+ } catch (error) {
+ localStorage.removeItem("token");
+ localStorage.removeItem("user_info");
+
+ console.error("ERRO DETALHADO NO CATCH:", error);
+
+ toast({
+ title: "Erro no Login",
+ description: error instanceof Error ? error.message : "Ocorreu um erro inesperado.",
+ });
+ } finally {
+ setIsLoading(false);
+ }
+ }
+
+ // ==================================================================
+ // JSX VISUALMENTE RICO E UNIFICADO
+ // ==================================================================
+ return (
+ // Usamos Card e CardContent para manter a consistência, mas o estilo principal
+ // virá da página 'app/login/page.tsx' que envolve este componente.
+
+ {/* Removemos o padding para dar controle à página pai */}
+
+
+ {/* O children permite que a página de login adicione links extras aqui */}
+ {children}
+
+
+ )
+}
\ No newline at end of file
diff --git a/components/doctor-layout.tsx b/components/doctor-layout.tsx
index 767db28..c9134ba 100644
--- a/components/doctor-layout.tsx
+++ b/components/doctor-layout.tsx
@@ -4,7 +4,8 @@ import type React from "react";
import { useState, useEffect } from "react";
import { useRouter, usePathname } from "next/navigation";
import Link from "next/link";
-import Cookies from "js-cookie"; // <-- 1. IMPORTAÇÃO ADICIONADA
+import Cookies from "js-cookie"; // Manteremos para o logout, se necessário
+import { api } from '@/services/api.mjs';
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
@@ -39,23 +40,20 @@ export default function DoctorLayout({ children }: PatientLayoutProps) {
const router = useRouter();
const pathname = usePathname();
- // ==================================================================
- // 2. BLOCO DE SEGURANÇA CORRIGIDO
- // ==================================================================
useEffect(() => {
const userInfoString = localStorage.getItem("user_info");
- const token = Cookies.get("access_token");
+ // --- ALTERAÇÃO PRINCIPAL AQUI ---
+ // Procurando o token no localStorage, onde ele foi realmente salvo.
+ const token = localStorage.getItem("token");
if (userInfoString && token) {
const userInfo = JSON.parse(userInfoString);
- // 3. "TRADUZIMOS" os dados da API para o formato que o layout espera
setDoctorData({
id: userInfo.id || "",
name: userInfo.user_metadata?.full_name || "Doutor(a)",
email: userInfo.email || "",
specialty: userInfo.user_metadata?.specialty || "Especialidade",
- // Campos que não vêm do login, definidos como vazios para não quebrar
phone: userInfo.phone || "",
cpf: "",
crm: "",
@@ -63,35 +61,49 @@ export default function DoctorLayout({ children }: PatientLayoutProps) {
permissions: {},
});
} else {
- // Se faltar o token ou os dados, volta para o login
- router.push("/doctor/login");
+ // Se não encontrar, aí sim redireciona.
+ router.push("/login");
}
}, [router]);
+ // O restante do seu código permanece exatamente o mesmo...
useEffect(() => {
- const handleResize = () => setWindowWidth(window.innerWidth);
- handleResize(); // inicializa com a largura atual
- window.addEventListener("resize", handleResize);
- return () => window.removeEventListener("resize", handleResize);
-}, []);
+ const handleResize = () => setWindowWidth(window.innerWidth);
+ handleResize();
+ window.addEventListener("resize", handleResize);
+ return () => window.removeEventListener("resize", handleResize);
+ }, []);
-useEffect(() => {
- if (isMobile) {
- setSidebarCollapsed(true);
- } else {
- setSidebarCollapsed(false);
- }
-}, [isMobile]);
+ useEffect(() => {
+ if (isMobile) {
+ setSidebarCollapsed(true);
+ } else {
+ setSidebarCollapsed(false);
+ }
+ }, [isMobile]);
const handleLogout = () => {
setShowLogoutDialog(true);
};
- const confirmLogout = () => {
- localStorage.removeItem("doctorData");
+
+ // --- ALTERAÇÃO 2: A função de logout agora é MUITO mais simples ---
+ const confirmLogout = async () => {
+ try {
+ // Chama a função centralizada para fazer o logout no servidor
+ await api.logout();
+ } catch (error) {
+ // O erro já é logado dentro da função api.logout, não precisamos fazer nada aqui
+ } finally {
+ // A responsabilidade do componente é apenas limpar o estado local e redirecionar
+ localStorage.removeItem("user_info");
+ localStorage.removeItem("token");
+ Cookies.remove("access_token"); // Limpeza de segurança
+
setShowLogoutDialog(false);
- router.push("/");
- };
+ router.push("/"); // Redireciona para a home
+ }
+ };
const cancelLogout = () => {
setShowLogoutDialog(false);
@@ -102,30 +114,10 @@ useEffect(() => {
};
const menuItems = [
- {
- href: "#",
- icon: Home,
- label: "Dashboard",
- // Botão para o dashboard do médico
- },
- {
- href: "/doctor/medicos/consultas",
- icon: Calendar,
- label: "Consultas",
- // Botão para página de consultas marcadas do médico atual
- },
- {
- href: "#",
- icon: Clock,
- label: "Editor de Laudo",
- // Botão para página do editor de laudo
- },
- {
- href: "/doctor/medicos",
- icon: User,
- label: "Pacientes",
- // Botão para a página de visualização de todos os pacientes
- },
+ { href: "#", icon: Home, label: "Dashboard" },
+ { href: "/doctor/medicos/consultas", icon: Calendar, label: "Consultas" },
+ { href: "#", icon: Clock, label: "Editor de Laudo" },
+ { href: "/doctor/medicos", icon: User, label: "Pacientes" },
];
if (!doctorData) {
@@ -133,8 +125,8 @@ useEffect(() => {
}
return (
+ // O restante do seu código JSX permanece exatamente o mesmo
- {/* Sidebar para desktop */}
@@ -170,7 +162,6 @@ useEffect(() => {
- {/* Se a sidebar estiver recolhida, o avatar e o texto do usuário também devem ser condensados ou ocultados */}
{!sidebarCollapsed && (
<>
@@ -189,7 +180,7 @@ useEffect(() => {
>
)}
{sidebarCollapsed && (
- {/* Centraliza o avatar quando recolhido */}
+
{doctorData.name
@@ -201,7 +192,6 @@ useEffect(() => {
)}
- {/* Novo botão de sair, usando a mesma estrutura dos itens de menu */}