forked from RiseUP/riseup-squad21
tentando atualizar com força
This commit is contained in:
parent
5b280c7d31
commit
4fcfad6c81
@ -25,6 +25,7 @@ interface UserFormData {
|
|||||||
papel: string;
|
papel: string;
|
||||||
senha: string;
|
senha: string;
|
||||||
confirmarSenha: string;
|
confirmarSenha: string;
|
||||||
|
cpf : string
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultFormData: UserFormData = {
|
const defaultFormData: UserFormData = {
|
||||||
@ -34,6 +35,7 @@ const defaultFormData: UserFormData = {
|
|||||||
papel: "",
|
papel: "",
|
||||||
senha: "",
|
senha: "",
|
||||||
confirmarSenha: "",
|
confirmarSenha: "",
|
||||||
|
cpf : ""
|
||||||
};
|
};
|
||||||
|
|
||||||
const cleanNumber = (value: string): string => value.replace(/\D/g, "");
|
const cleanNumber = (value: string): string => value.replace(/\D/g, "");
|
||||||
@ -88,6 +90,7 @@ export default function NovoUsuarioPage() {
|
|||||||
phone: formData.telefone || null,
|
phone: formData.telefone || null,
|
||||||
role: formData.papel,
|
role: formData.papel,
|
||||||
password: formData.senha,
|
password: formData.senha,
|
||||||
|
cpf : formData.cpf
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log("📤 Enviando payload:", payload);
|
console.log("📤 Enviando payload:", payload);
|
||||||
@ -229,6 +232,19 @@ export default function NovoUsuarioPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="cpf">Cpf *</Label>
|
||||||
|
<Input
|
||||||
|
id="cpf"
|
||||||
|
type="cpf"
|
||||||
|
value={formData.cpf}
|
||||||
|
onChange={(e) => handleInputChange("cpf", e.target.value)}
|
||||||
|
placeholder="xxx.xxx.xxx-xx"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div className="flex justify-end gap-4 pt-6 border-t mt-6">
|
<div className="flex justify-end gap-4 pt-6 border-t mt-6">
|
||||||
<Link href="/manager/usuario">
|
<Link href="/manager/usuario">
|
||||||
<Button type="button" variant="outline" disabled={isSaving}>
|
<Button type="button" variant="outline" disabled={isSaving}>
|
||||||
@ -253,4 +269,4 @@ export default function NovoUsuarioPage() {
|
|||||||
</div>
|
</div>
|
||||||
</ManagerLayout>
|
</ManagerLayout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -17,7 +17,7 @@ export default function InicialPage() {
|
|||||||
<header className="bg-card shadow-md py-4 px-6 flex justify-between items-center">
|
<header className="bg-card shadow-md py-4 px-6 flex justify-between items-center">
|
||||||
<h1 className="text-2xl font-bold text-primary">MediConnect</h1>
|
<h1 className="text-2xl font-bold text-primary">MediConnect</h1>
|
||||||
<nav className="flex space-x-6 text-muted-foreground font-medium">
|
<nav className="flex space-x-6 text-muted-foreground font-medium">
|
||||||
<a href="#home" className="hover:text-primary"><Link href="/cadastro">Home</Link></a>
|
<Link href="/cadastro" className="hover:text-primary"> Home</Link>
|
||||||
<a href="#about" className="hover:text-primary">Sobre</a>
|
<a href="#about" className="hover:text-primary">Sobre</a>
|
||||||
<a href="#departments" className="hover:text-primary">Departamentos</a>
|
<a href="#departments" className="hover:text-primary">Departamentos</a>
|
||||||
<a href="#doctors" className="hover:text-primary">Médicos</a>
|
<a href="#doctors" className="hover:text-primary">Médicos</a>
|
||||||
|
|||||||
@ -4,24 +4,16 @@
|
|||||||
import type React from "react"
|
import type React from "react"
|
||||||
import { useState } from "react"
|
import { useState } from "react"
|
||||||
import { useRouter } from "next/navigation"
|
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.mjs";
|
|
||||||
|
|
||||||
|
// Nossos serviços de API centralizados e limpos
|
||||||
|
import { login, api } from "@/services/api.mjs";
|
||||||
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent } from "@/components/ui/card";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
import { useToast } from "@/hooks/use-toast";
|
import { useToast } from "@/hooks/use-toast";
|
||||||
|
import { Eye, EyeOff, Mail, Lock, Loader2 } from "lucide-react";
|
||||||
|
|
||||||
import { Eye, EyeOff, Mail, Lock, Loader2, UserCheck, Stethoscope, IdCard, Receipt } from "lucide-react";
|
|
||||||
|
|
||||||
interface LoginFormProps {
|
interface LoginFormProps {
|
||||||
children?: React.ReactNode
|
children?: React.ReactNode
|
||||||
@ -39,9 +31,46 @@ export function LoginForm({ children }: LoginFormProps) {
|
|||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const { toast } = useToast()
|
const { toast } = useToast()
|
||||||
|
|
||||||
// ==================================================================
|
// --- NOVOS ESTADOS PARA CONTROLE DE MÚLTIPLOS PERFIS ---
|
||||||
// LÓGICA DE LOGIN INTELIGENTE E CENTRALIZADA
|
const [userRoles, setUserRoles] = useState<string[]>([]);
|
||||||
// ==================================================================
|
const [authenticatedUser, setAuthenticatedUser] = useState<any>(null);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* --- NOVA FUNÇÃO ---
|
||||||
|
* Finaliza o login com o perfil de dashboard escolhido e redireciona.
|
||||||
|
*/
|
||||||
|
const handleRoleSelection = (selectedDashboardRole: string) => {
|
||||||
|
const user = authenticatedUser;
|
||||||
|
if (!user) {
|
||||||
|
toast({ title: "Erro de Sessão", description: "Não foi possível encontrar os dados do usuário. Tente novamente.", variant: "destructive" });
|
||||||
|
setUserRoles([]); // Volta para a tela de login
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const completeUserInfo = { ...user, user_metadata: { ...user.user_metadata, role: selectedDashboardRole } };
|
||||||
|
localStorage.setItem('user_info', JSON.stringify(completeUserInfo));
|
||||||
|
|
||||||
|
let redirectPath = "";
|
||||||
|
switch (selectedDashboardRole) {
|
||||||
|
case "manager": redirectPath = "/manager/home"; break;
|
||||||
|
case "doctor": redirectPath = "/doctor/medicos"; break;
|
||||||
|
case "secretary": redirectPath = "/secretary/pacientes"; break;
|
||||||
|
case "patient": redirectPath = "/patient/dashboard"; break;
|
||||||
|
case "finance": redirectPath = "/finance/home"; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (redirectPath) {
|
||||||
|
toast({ title: `Entrando como ${selectedDashboardRole}...` });
|
||||||
|
router.push(redirectPath);
|
||||||
|
} else {
|
||||||
|
toast({ title: "Erro", description: "Perfil selecionado inválido.", variant: "destructive" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* --- FUNÇÃO ATUALIZADA ---
|
||||||
|
* Lida com a submissão do formulário, busca os perfis e decide o próximo passo.
|
||||||
|
*/
|
||||||
const handleSubmit = async (e: React.FormEvent) => {
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
@ -49,110 +78,138 @@ export function LoginForm({ children }: LoginFormProps) {
|
|||||||
localStorage.removeItem("user_info");
|
localStorage.removeItem("user_info");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const authData = await loginWithEmailAndPassword(form.email, form.password);
|
// A chamada de login continua a mesma
|
||||||
|
const authData = await login();
|
||||||
const user = authData.user;
|
const user = authData.user;
|
||||||
if (!user || !user.id) {
|
if (!user || !user.id) {
|
||||||
throw new Error("Resposta de autenticação inválida: ID do usuário não encontrado.");
|
throw new Error("Resposta de autenticação inválida.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Armazena o usuário para uso posterior na seleção de perfil
|
||||||
|
setAuthenticatedUser(user);
|
||||||
|
|
||||||
|
// A busca de roles também continua a mesma, usando nosso 'api.get'
|
||||||
const rolesData = await api.get(`/rest/v1/user_roles?user_id=eq.${user.id}&select=role`);
|
const rolesData = await api.get(`/rest/v1/user_roles?user_id=eq.${user.id}&select=role`);
|
||||||
|
|
||||||
if (!rolesData || rolesData.length === 0) {
|
if (!rolesData || rolesData.length === 0) {
|
||||||
throw new Error("Login bem-sucedido, mas nenhum perfil de acesso foi encontrado para este usuário.");
|
throw new Error("Nenhum perfil de acesso foi encontrado para este usuário.");
|
||||||
}
|
}
|
||||||
|
|
||||||
const userRole = rolesData[0].role;
|
const rolesFromApi: string[] = rolesData.map((r: any) => r.role);
|
||||||
const completeUserInfo = { ...user, user_metadata: { ...user.user_metadata, role: userRole } };
|
|
||||||
localStorage.setItem('user_info', JSON.stringify(completeUserInfo));
|
|
||||||
|
|
||||||
let redirectPath = "";
|
// --- AQUI COMEÇA A NOVA LÓGICA DE DECISÃO ---
|
||||||
switch (userRole) {
|
|
||||||
case "admin":
|
// Caso 1: Usuário é ADMIN, mostra todos os dashboards possíveis.
|
||||||
case "manager": redirectPath = "/manager/home"; break;
|
if (rolesFromApi.includes('admin')) {
|
||||||
case "medico": redirectPath = "/doctor/medicos"; break;
|
setUserRoles(["manager", "doctor", "secretary", "patient", "finance"]);
|
||||||
case "secretary": redirectPath = "/secretary/pacientes"; break;
|
setIsLoading(false); // Para o loading para mostrar a tela de seleção
|
||||||
case "patient": redirectPath = "/patient/dashboard"; break;
|
return;
|
||||||
case "finance": redirectPath = "/finance/home"; break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!redirectPath) {
|
// Mapeia os roles da API para os perfis de dashboard que o usuário pode acessar
|
||||||
throw new Error(`O perfil de acesso '${userRole}' não é válido para login. Contate o suporte.`);
|
const displayRoles = new Set<string>();
|
||||||
}
|
rolesFromApi.forEach(role => {
|
||||||
|
switch (role) {
|
||||||
toast({
|
case 'gestor':
|
||||||
title: "Login bem-sucedido!",
|
displayRoles.add('manager');
|
||||||
description: `Bem-vindo(a)! Redirecionando...`,
|
displayRoles.add('finance');
|
||||||
|
break;
|
||||||
|
case 'medico':
|
||||||
|
displayRoles.add('doctor');
|
||||||
|
break;
|
||||||
|
case 'secretaria':
|
||||||
|
displayRoles.add('secretary');
|
||||||
|
break;
|
||||||
|
case 'patient': // Mapeamento de 'patient' (ou outro nome que você use para paciente)
|
||||||
|
displayRoles.add('patient');
|
||||||
|
break;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
router.push(redirectPath);
|
const finalRoles = Array.from(displayRoles);
|
||||||
|
|
||||||
|
// Caso 2: Se o usuário tem apenas UM perfil de dashboard, redireciona direto.
|
||||||
|
if (finalRoles.length === 1) {
|
||||||
|
handleRoleSelection(finalRoles[0]);
|
||||||
|
}
|
||||||
|
// Caso 3: Se tem múltiplos perfis (ex: 'gestor'), mostra a tela de seleção.
|
||||||
|
else {
|
||||||
|
setUserRoles(finalRoles);
|
||||||
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
localStorage.removeItem("token");
|
localStorage.removeItem("token");
|
||||||
localStorage.removeItem("user_info");
|
localStorage.removeItem("user_info");
|
||||||
|
|
||||||
console.error("ERRO DETALHADO NO CATCH:", error);
|
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
title: "Erro no Login",
|
title: "Erro no Login",
|
||||||
description: error instanceof Error ? error.message : "Ocorreu um erro inesperado.",
|
description: error instanceof Error ? error.message : "Ocorreu um erro inesperado.",
|
||||||
|
variant: "destructive",
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
// Apenas para o loading se não houver redirecionamento ou seleção de perfil
|
||||||
|
if (userRoles.length === 0) {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
// ==================================================================
|
// --- JSX ATUALIZADO COM RENDERIZAÇÃO CONDICIONAL ---
|
||||||
// JSX VISUALMENTE RICO E UNIFICADO
|
|
||||||
// ==================================================================
|
|
||||||
return (
|
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.
|
|
||||||
<Card className="w-full bg-transparent border-0 shadow-none">
|
<Card className="w-full bg-transparent border-0 shadow-none">
|
||||||
<CardContent className="p-0"> {/* Removemos o padding para dar controle à página pai */}
|
<CardContent className="p-0">
|
||||||
<form onSubmit={handleSubmit} className="space-y-6">
|
{userRoles.length === 0 ? (
|
||||||
<div className="space-y-2">
|
// VISÃO 1: Formulário de Login (se nenhum perfil foi carregado ainda)
|
||||||
<Label htmlFor="email">E-mail</Label>
|
<form onSubmit={handleSubmit} className="space-y-6">
|
||||||
<div className="relative">
|
<div className="space-y-2">
|
||||||
<Mail className="absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground w-5 h-5" />
|
<Label htmlFor="email">E-mail</Label>
|
||||||
<Input
|
<div className="relative">
|
||||||
id="email"
|
<Mail className="absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground w-5 h-5" />
|
||||||
type="email"
|
<Input
|
||||||
placeholder="seu.email@exemplo.com"
|
id="email" type="email" placeholder="seu.email@exemplo.com"
|
||||||
value={form.email}
|
value={form.email} onChange={(e) => setForm({ ...form, email: e.target.value })}
|
||||||
onChange={(e) => setForm({ ...form, email: e.target.value })}
|
className="pl-10 h-11" required disabled={isLoading} autoComplete="username"
|
||||||
className="pl-10 h-11"
|
/>
|
||||||
required
|
</div>
|
||||||
disabled={isLoading}
|
</div>
|
||||||
autoComplete="username" // Boa prática de acessibilidade
|
<div className="space-y-2">
|
||||||
/>
|
<Label htmlFor="password">Senha</Label>
|
||||||
|
<div className="relative">
|
||||||
|
<Lock className="absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground w-5 h-5" />
|
||||||
|
<Input
|
||||||
|
id="password" type={showPassword ? "text" : "password"} placeholder="Digite sua senha"
|
||||||
|
value={form.password} onChange={(e) => setForm({ ...form, password: e.target.value })}
|
||||||
|
className="pl-10 pr-12 h-11" required disabled={isLoading} autoComplete="current-password"
|
||||||
|
/>
|
||||||
|
<button type="button" onClick={() => setShowPassword(!showPassword)} className="absolute right-2 top-1/2 -translate-y-1/2 h-8 w-8 p-0 text-muted-foreground hover:text-foreground" disabled={isLoading}>
|
||||||
|
{showPassword ? <EyeOff className="w-5 h-5" /> : <Eye className="w-5 h-5" />}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Button type="submit" className="w-full h-11 text-base font-semibold" disabled={isLoading}>
|
||||||
|
{isLoading ? <Loader2 className="w-5 h-5 animate-spin" /> : "Entrar"}
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
) : (
|
||||||
|
// VISÃO 2: Tela de Seleção de Perfil (se múltiplos perfis foram encontrados)
|
||||||
|
<div className="space-y-4 animate-in fade-in-50">
|
||||||
|
<h3 className="text-lg font-medium text-center text-foreground">Você tem múltiplos perfis</h3>
|
||||||
|
<p className="text-sm text-muted-foreground text-center">Selecione com qual perfil deseja entrar:</p>
|
||||||
|
<div className="flex flex-col space-y-3 pt-2">
|
||||||
|
{userRoles.map((role) => (
|
||||||
|
<Button
|
||||||
|
key={role}
|
||||||
|
variant="outline"
|
||||||
|
className="h-11 text-base"
|
||||||
|
onClick={() => handleRoleSelection(role)}
|
||||||
|
>
|
||||||
|
Entrar como: {role.charAt(0).toUpperCase() + role.slice(1)}
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
)}
|
||||||
<Label htmlFor="password">Senha</Label>
|
|
||||||
<div className="relative">
|
|
||||||
<Lock className="absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground w-5 h-5" />
|
|
||||||
<Input
|
|
||||||
id="password"
|
|
||||||
type={showPassword ? "text" : "password"}
|
|
||||||
placeholder="Digite sua senha"
|
|
||||||
value={form.password}
|
|
||||||
onChange={(e) => setForm({ ...form, password: e.target.value })}
|
|
||||||
className="pl-10 pr-12 h-11"
|
|
||||||
required
|
|
||||||
disabled={isLoading}
|
|
||||||
autoComplete="current-password" // Boa prática de acessibilidade
|
|
||||||
/>
|
|
||||||
<button type="button" onClick={() => setShowPassword(!showPassword)} className="absolute right-2 top-1/2 -translate-y-1/2 h-8 w-8 p-0 text-muted-foreground hover:text-foreground" disabled={isLoading}>
|
|
||||||
{showPassword ? <EyeOff className="w-5 h-5" /> : <Eye className="w-5 h-5" />}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Button type="submit" className="w-full h-11 text-base font-semibold" disabled={isLoading}>
|
|
||||||
{isLoading ? <Loader2 className="w-5 h-5 animate-spin" /> : "Entrar"}
|
|
||||||
</Button>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
{/* O children permite que a página de login adicione links extras aqui */}
|
|
||||||
{children}
|
{children}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@ -120,13 +120,13 @@ export default function DoctorLayout({ children }: PatientLayoutProps) {
|
|||||||
// Botão para o dashboard do médico
|
// Botão para o dashboard do médico
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: "/doctor/medicos/consultas",
|
href: "/doctor/consultas",
|
||||||
icon: Calendar,
|
icon: Calendar,
|
||||||
label: "Consultas",
|
label: "Consultas",
|
||||||
// Botão para página de consultas marcadas do médico atual
|
// Botão para página de consultas marcadas do médico atual
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: "#",
|
href: "/doctor/medicos/editorlaudo",
|
||||||
icon: Clock,
|
icon: Clock,
|
||||||
label: "Editor de Laudo",
|
label: "Editor de Laudo",
|
||||||
// Botão para página do editor de laudo
|
// Botão para página do editor de laudo
|
||||||
|
|||||||
@ -95,11 +95,11 @@ export default function ManagerLayout({ children }: ManagerLayoutProps) {
|
|||||||
const cancelLogout = () => setShowLogoutDialog(false);
|
const cancelLogout = () => setShowLogoutDialog(false);
|
||||||
|
|
||||||
const menuItems = [
|
const menuItems = [
|
||||||
{ href: "#dashboard", icon: Home, label: "Dashboard" },
|
{ href: "/manager/dashboard", icon: Home, label: "Dashboard" },
|
||||||
{ href: "#reports", icon: Calendar, label: "Relatórios gerenciais" },
|
{ href: "#", icon: Calendar, label: "Relatórios gerenciais" },
|
||||||
{ href: "#users", icon: User, label: "Gestão de Usuários" },
|
{ href: "/manager/usuario", icon: User, label: "Gestão de Usuários" },
|
||||||
{ href: "#doctors", icon: User, label: "Gestão de Médicos" },
|
{ href: "/manager/home", icon: User, label: "Gestão de Médicos" },
|
||||||
{ href: "#settings", icon: Calendar, label: "Configurações" },
|
{ href: "#", icon: Calendar, label: "Configurações" },
|
||||||
];
|
];
|
||||||
|
|
||||||
if (!managerData) {
|
if (!managerData) {
|
||||||
|
|||||||
42
hooks/useAuth.ts
Normal file
42
hooks/useAuth.ts
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
// Caminho: hooks/useAuth.ts
|
||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import Cookies from 'js-cookie';
|
||||||
|
|
||||||
|
// Uma interface genérica para as informações do usuário que pegamos do localStorage
|
||||||
|
interface UserInfo {
|
||||||
|
id: string;
|
||||||
|
email: string;
|
||||||
|
user_metadata: {
|
||||||
|
full_name?: string;
|
||||||
|
role?: string; // O perfil escolhido no login
|
||||||
|
specialty?: string;
|
||||||
|
department?: string;
|
||||||
|
};
|
||||||
|
// Adicione outros campos que possam existir
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useAuth() {
|
||||||
|
const [user, setUser] = useState<UserInfo | null>(null);
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const userInfoString = localStorage.getItem('user_info');
|
||||||
|
const token = Cookies.get('access_token');
|
||||||
|
|
||||||
|
if (userInfoString && token) {
|
||||||
|
try {
|
||||||
|
const userInfo = JSON.parse(userInfoString);
|
||||||
|
setUser(userInfo);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Erro ao parsear user_info do localStorage", error);
|
||||||
|
router.push('/'); // Redireciona se os dados estiverem corrompidos
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Se não houver token ou info, redireciona para a página inicial/login
|
||||||
|
router.push('/');
|
||||||
|
}
|
||||||
|
}, [router]);
|
||||||
|
|
||||||
|
return user; // Retorna o usuário logado ou null enquanto carrega/redireciona
|
||||||
|
}
|
||||||
179
services/api.mjs
179
services/api.mjs
@ -1,65 +1,13 @@
|
|||||||
// Caminho: [seu-caminho]/services/api.mjs
|
// Caminho: services/api.mjs
|
||||||
|
|
||||||
const BASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co";
|
// As suas variáveis de ambiente já estão corretas no arquivo .env.local
|
||||||
const API_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ";
|
const BASE_URL = process.env.NEXT_PUBLIC_SUPABASE_URL;
|
||||||
|
const API_KEY = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
|
||||||
|
|
||||||
export async function loginWithEmailAndPassword(email, password) {
|
/**
|
||||||
const response = await fetch(`${BASE_URL}/auth/v1/token?grant_type=password`, {
|
* Função de login que o seu formulário vai chamar.
|
||||||
method: "POST",
|
* Ela autentica e salva o token no localStorage.
|
||||||
headers: {
|
*/
|
||||||
"Content-Type": "application/json",
|
|
||||||
"apikey": API_KEY,
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ email, password }),
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(data.error_description || "Credenciais inválidas.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.access_token && typeof window !== 'undefined') {
|
|
||||||
// Padronizando para salvar o token no localStorage
|
|
||||||
localStorage.setItem("token", data.access_token);
|
|
||||||
}
|
|
||||||
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- NOVA FUNÇÃO DE LOGOUT CENTRALIZADA ---
|
|
||||||
async function logout() {
|
|
||||||
const token = localStorage.getItem("token");
|
|
||||||
if (!token) return; // Se não há token, não há o que fazer
|
|
||||||
|
|
||||||
try {
|
|
||||||
await fetch(`${BASE_URL}/auth/v1/logout`, {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"apikey": API_KEY,
|
|
||||||
"Authorization": `Bearer ${token}`,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
// Mesmo que a chamada falhe, o logout no cliente deve continuar.
|
|
||||||
// O token pode já ter expirado no servidor, por exemplo.
|
|
||||||
console.error("Falha ao invalidar token no servidor (isso pode ser normal se o token já expirou):", error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function request(endpoint, options = {}) {
|
|
||||||
const token = typeof window !== 'undefined' ? localStorage.getItem("token") : null;
|
|
||||||
|
|
||||||
const headers = {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
"apikey": API_KEY,
|
|
||||||
...(token ? { "Authorization": `Bearer ${token}` } : {}),
|
|
||||||
...options.headers,
|
|
||||||
};
|
|
||||||
const API_KEY =
|
|
||||||
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ";
|
|
||||||
|
|
||||||
export const apikey = API_KEY;
|
|
||||||
let loginPromise = null;
|
let loginPromise = null;
|
||||||
|
|
||||||
export async function login() {
|
export async function login() {
|
||||||
@ -93,89 +41,68 @@ export async function login() {
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function request(endpoint, options = {}) {
|
/**
|
||||||
if (!loginPromise) loginPromise = login();
|
* Função de logout que o seu DashboardLayout vai chamar.
|
||||||
|
*/
|
||||||
|
async function logout() {
|
||||||
|
const token = localStorage.getItem("token");
|
||||||
|
if (!token) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await loginPromise;
|
await fetch(`${BASE_URL}/auth/v1/logout`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"apikey": API_KEY,
|
||||||
|
"Authorization": `Bearer ${token}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("⚠️ Falha ao autenticar:", error);
|
console.error("Falha ao invalidar token no servidor (pode ser normal se o token já expirou):", error);
|
||||||
} finally {
|
} finally {
|
||||||
loginPromise = null;
|
// Limpa os dados do cliente independentemente do resultado do servidor
|
||||||
|
localStorage.removeItem("token");
|
||||||
|
localStorage.removeItem("user_info");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let token =
|
/**
|
||||||
typeof window !== "undefined" ? localStorage.getItem("token") : null;
|
* Função genérica e centralizada para fazer requisições autenticadas.
|
||||||
|
* Ela pega o token do localStorage automaticamente.
|
||||||
if (!token) {
|
*/
|
||||||
console.warn("⚠️ Token não encontrado, refazendo login...");
|
async function request(endpoint, options = {}) {
|
||||||
const data = await login();
|
const token = typeof window !== 'undefined' ? localStorage.getItem("token") : null;
|
||||||
token = data.access_token;
|
|
||||||
}
|
|
||||||
|
|
||||||
const headers = {
|
const headers = {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
apikey: API_KEY,
|
"apikey": API_KEY,
|
||||||
Authorization: `Bearer ${token}`,
|
// Adiciona o cabeçalho de autorização apenas se o token existir
|
||||||
|
...(token && { "Authorization": `Bearer ${token}` }),
|
||||||
...options.headers,
|
...options.headers,
|
||||||
};
|
};
|
||||||
|
|
||||||
const fullUrl =
|
const response = await fetch(`${BASE_URL}${endpoint}`, { ...options, headers });
|
||||||
endpoint.startsWith("/rest/v1") || endpoint.startsWith("/functions/")
|
|
||||||
? `${BASE_URL}${endpoint}`
|
|
||||||
: `${BASE_URL}/rest/v1${endpoint}`;
|
|
||||||
|
|
||||||
console.log("🌐 Requisição para:", fullUrl, "com headers:", headers);
|
|
||||||
|
|
||||||
const response = await fetch(fullUrl, {
|
|
||||||
...options,
|
|
||||||
headers,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
let errorBody;
|
|
||||||
try {
|
|
||||||
errorBody = await response.json();
|
|
||||||
} catch (e) {
|
|
||||||
errorBody = await response.text();
|
|
||||||
}
|
|
||||||
throw new Error(`Erro HTTP: ${response.status} - ${JSON.stringify(errorBody)}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response.status === 204) return {};
|
|
||||||
return await response.json();
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Erro na requisição:", error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Adicionamos a função de logout ao nosso objeto de API exportado
|
|
||||||
export const api = {
|
|
||||||
get: (endpoint, options) => request(endpoint, { method: "GET", ...options }),
|
|
||||||
post: (endpoint, data, options) => request(endpoint, { method: "POST", body: JSON.stringify(data), ...options }),
|
|
||||||
patch: (endpoint, data, options) => request(endpoint, { method: "PATCH", body: JSON.stringify(data), ...options }),
|
|
||||||
delete: (endpoint, options) => request(endpoint, { method: "DELETE", ...options }),
|
|
||||||
logout: logout, // <-- EXPORTANDO A NOVA FUNÇÃO
|
|
||||||
};
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const msg = await response.text();
|
const errorBody = await response.json().catch(() => response.text());
|
||||||
console.error("❌ Erro HTTP:", response.status, msg);
|
console.error("Erro na requisição:", response.status, errorBody);
|
||||||
throw new Error(`Erro HTTP: ${response.status} - Detalhes: ${msg}`);
|
throw new Error(`Erro na API: ${errorBody.message || JSON.stringify(errorBody)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const contentType = response.headers.get("content-type");
|
// Se a resposta for 204 No Content (como em um DELETE), não tenta fazer o parse do JSON
|
||||||
if (!contentType || !contentType.includes("application/json")) return {};
|
if (response.status === 204) {
|
||||||
return await response.json();
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Exportamos um objeto 'api' com os métodos que os componentes vão usar.
|
||||||
export const api = {
|
export const api = {
|
||||||
get: (endpoint, options) => request(endpoint, { method: "GET", ...options }),
|
get: (endpoint, options) => request(endpoint, { method: "GET", ...options }),
|
||||||
post: (endpoint, data) =>
|
post: (endpoint, data, options) => request(endpoint, { method: "POST", body: JSON.stringify(data), ...options }),
|
||||||
request(endpoint, { method: "POST", body: JSON.stringify(data) }),
|
patch: (endpoint, data, options) => request(endpoint, { method: "PATCH", body: JSON.stringify(data), ...options }),
|
||||||
patch: (endpoint, data) =>
|
delete: (endpoint, options) => request(endpoint, { method: "DELETE", ...options }),
|
||||||
request(endpoint, { method: "PATCH", body: JSON.stringify(data) }),
|
logout: logout, // Exportando a função de logout
|
||||||
delete: (endpoint) => request(endpoint, { method: "DELETE" }),
|
};
|
||||||
};
|
|
||||||
|
|
||||||
@ -3,7 +3,7 @@ import { api } from "./api.mjs";
|
|||||||
export const doctorsService = {
|
export const doctorsService = {
|
||||||
list: () => api.get("/rest/v1/doctors"),
|
list: () => api.get("/rest/v1/doctors"),
|
||||||
getById: (id) => api.get(`/rest/v1/doctors?id=eq.${id}`).then(data => data[0]),
|
getById: (id) => api.get(`/rest/v1/doctors?id=eq.${id}`).then(data => data[0]),
|
||||||
create: (data) => api.post("/rest/v1/doctors", data),
|
create: (data) => api.post("/functions/v1/create-doctor", data),
|
||||||
update: (id, data) => api.patch(`/rest/v1/doctors?id=eq.${id}`, data),
|
update: (id, data) => api.patch(`/rest/v1/doctors?id=eq.${id}`, data),
|
||||||
delete: (id) => api.delete(`/rest/v1/doctors?id=eq.${id}`),
|
delete: (id) => api.delete(`/rest/v1/doctors?id=eq.${id}`),
|
||||||
};
|
};
|
||||||
@ -8,7 +8,8 @@ export const usersService = {
|
|||||||
|
|
||||||
async create_user(data) {
|
async create_user(data) {
|
||||||
// continua usando a Edge Function corretamente
|
// continua usando a Edge Function corretamente
|
||||||
return await api.post(`/functions/v1/user-create`, data);
|
return await api.post(`/functions/v1/create-user-with-password
|
||||||
|
`, data);
|
||||||
},
|
},
|
||||||
|
|
||||||
// 🚀 Busca dados completos do usuário direto do banco
|
// 🚀 Busca dados completos do usuário direto do banco
|
||||||
@ -52,4 +53,4 @@ export const usersService = {
|
|||||||
permissions,
|
permissions,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
Loading…
x
Reference in New Issue
Block a user