guisilvagomes 9ec07aeea3 Update
2025-10-09 10:09:35 -03:00

424 lines
13 KiB
TypeScript

import React, {
createContext,
useCallback,
useEffect,
useMemo,
useState,
} from "react";
import toast from "react-hot-toast";
import medicoService, { type Medico } from "../services/medicoService";
import authService, {
type UserInfoFullResponse,
} from "../services/authService"; // tokens + user-info
// tokenManager removido no modelo somente Supabase (sem usuário técnico)
// Tipos de roles suportados
export type UserRole =
| "secretaria"
| "medico"
| "paciente"
| "admin"
| "gestor"
| "user"; // Role genérica para pacientes
export interface SessionUserBase {
id: string;
nome: string;
email?: string;
role: UserRole;
roles?: UserRole[];
permissions?: { [k: string]: boolean | undefined };
}
export interface SecretariaUser extends SessionUserBase {
role: "secretaria";
}
export interface MedicoUser extends SessionUserBase {
role: "medico";
crm?: string;
especialidade?: string;
}
export interface PacienteUser extends SessionUserBase {
role: "paciente";
pacienteId?: string;
}
export interface AdminUser extends SessionUserBase {
role: "admin";
}
export type SessionUser =
| SecretariaUser
| MedicoUser
| PacienteUser
| AdminUser
| (SessionUserBase & { role: "gestor" });
interface AuthContextValue {
user: SessionUser | null;
isAuthenticated: boolean;
loading: boolean;
loginSecretaria: (email: string, senha: string) => Promise<boolean>;
loginMedico: (email: string, senha: string) => Promise<boolean>;
loginComEmailSenha: (email: string, senha: string) => Promise<boolean>; // fluxo unificado real
loginPaciente: (paciente: {
id: string;
nome: string;
email?: string;
}) => Promise<boolean>;
logout: () => void;
role: UserRole | null;
roles: UserRole[];
permissions: Record<string, boolean | undefined>;
refreshSession: () => Promise<void>;
}
const AuthContext = createContext<AuthContextValue | undefined>(undefined);
const STORAGE_KEY = "appSession";
interface PersistedSession {
user: SessionUser;
token?: string; // para quando integrar authService real
refreshToken?: string;
savedAt: string;
}
export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({
children,
}) => {
const [user, setUser] = useState<SessionUser | null>(null);
const [loading, setLoading] = useState(true);
// Restaurar sessão do localStorage e verificar token
useEffect(() => {
const restoreSession = async () => {
try {
const raw = localStorage.getItem(STORAGE_KEY);
if (raw) {
const parsed = JSON.parse(raw) as PersistedSession;
if (parsed?.user?.role) {
console.log("[AuthContext] Restaurando sessão:", parsed.user);
// Verificar se há tokens válidos salvos
if (parsed.token) {
console.log("[AuthContext] Restaurando tokens no tokenStore");
// Restaurar tokens no tokenStore
const tokenStore = (await import("../services/tokenStore"))
.default;
tokenStore.setTokens(parsed.token, parsed.refreshToken);
} else {
console.warn("[AuthContext] Sessão encontrada mas sem token. Verificando tokenStore...");
// Verificar se há token no tokenStore (pode ter sido salvo diretamente)
const tokenStore = (await import("../services/tokenStore"))
.default;
const existingToken = tokenStore.getAccessToken();
if (existingToken) {
console.log("[AuthContext] Token encontrado no tokenStore, mantendo sessão");
} else {
console.warn("[AuthContext] Nenhum token encontrado. Sessão pode estar inválida.");
}
}
setUser(parsed.user);
}
} else {
console.log("[AuthContext] Nenhuma sessão salva encontrada");
}
} catch (error) {
console.error("[AuthContext] Erro ao restaurar sessão:", error);
} finally {
setLoading(false);
}
};
void restoreSession();
}, []);
const persist = useCallback((session: PersistedSession) => {
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(session));
} catch {
/* ignore */
}
}, []);
const clearPersisted = useCallback(() => {
try {
localStorage.removeItem(STORAGE_KEY);
} catch {
/* ignore */
}
}, []);
const normalizeRole = (r: string | undefined): UserRole | undefined => {
if (!r) return undefined;
const map: Record<string, UserRole> = {
medico: "medico",
doctor: "medico",
secretaria: "secretaria",
assistant: "secretaria",
paciente: "paciente",
patient: "paciente",
user: "paciente", // Role genérica mapeada para paciente
admin: "admin",
gestor: "gestor",
manager: "gestor",
};
return map[r.toLowerCase()] || undefined;
};
const pickPrimaryRole = (rolesArr: UserRole[]): UserRole => {
const priority: UserRole[] = [
"admin",
"gestor",
"medico",
"secretaria",
"paciente",
];
for (const p of priority) if (rolesArr.includes(p)) return p;
return rolesArr[0] || "paciente";
};
const buildSessionUser = React.useCallback(
(info: UserInfoFullResponse): SessionUser => {
console.log(
"[buildSessionUser] info recebido:",
JSON.stringify(info, null, 2)
);
const rolesNormalized = (info.roles || [])
.map(normalizeRole)
.filter(Boolean) as UserRole[];
console.log("[buildSessionUser] roles normalizadas:", rolesNormalized);
const permissions = info.permissions || {};
const primaryRole = pickPrimaryRole(
rolesNormalized.length
? rolesNormalized
: [normalizeRole((info.roles || [])[0]) || "paciente"]
);
console.log("[buildSessionUser] primaryRole escolhida:", primaryRole);
const base = {
id: info.user?.id || "",
nome:
info.profile?.full_name ||
info.user?.email?.split("@")[0] ||
"Usuário",
email: info.user?.email,
role: primaryRole,
roles: rolesNormalized,
permissions,
} as SessionUserBase;
console.log("[buildSessionUser] SessionUser final:", base);
if (primaryRole === "medico") {
return { ...base, role: "medico" } as MedicoUser;
}
if (primaryRole === "secretaria") {
return { ...base, role: "secretaria" } as SecretariaUser;
}
if (primaryRole === "admin") {
return { ...base, role: "admin" } as AdminUser;
}
if (primaryRole === "gestor") {
return { ...base, role: "gestor" } as SessionUser;
}
return { ...base, role: "paciente" } as PacienteUser;
},
[]
);
// LEGADO: manter até que todos os usuários passem a existir no auth real
const loginSecretaria = useCallback(
async (email: string, senha: string) => {
// Mock atual: validar contra credenciais fixas (pode evoluir para authService.login)
if (email === "secretaria@clinica.com" && senha === "secretaria123") {
const newUser: SecretariaUser = {
id: "sec-1",
nome: "Secretária",
email,
role: "secretaria",
roles: ["secretaria"],
permissions: {},
};
setUser(newUser);
persist({ user: newUser, savedAt: new Date().toISOString() });
toast.success("Login realizado");
return true;
}
toast.error("Credenciais inválidas");
return false;
},
[persist]
);
// LEGADO: usa service de médicos sem validar senha real (apenas existência)
const loginMedico = useCallback(
async (email: string, senha: string) => {
const resp = await medicoService.loginMedico(email, senha);
if (!resp.success || !resp.data) {
toast.error(resp.error || "Erro ao autenticar médico");
return false;
}
const m: Medico = resp.data;
const newUser: MedicoUser = {
id: m.id,
nome: m.nome,
email: m.email,
role: "medico",
crm: m.crm,
especialidade: m.especialidade,
roles: ["medico"],
permissions: {},
};
setUser(newUser);
persist({ user: newUser, savedAt: new Date().toISOString() });
toast.success(`Bem-vindo(a) Dr(a). ${m.nome}`);
return true;
},
[persist]
);
// Fluxo unificado real usando authService + endpoint user-info para mapear role dinâmica
const loginComEmailSenha = useCallback(
async (email: string, senha: string) => {
console.log("[AuthContext] Iniciando login para:", email);
const loginResp = await authService.login({ email, password: senha });
console.log("[AuthContext] Resposta login:", loginResp);
if (!loginResp.success || !loginResp.data) {
console.error("[AuthContext] Login falhou:", loginResp.error);
toast.error(loginResp.error || "Falha no login");
return false;
}
console.log("[AuthContext] Token recebido, buscando user-info...");
// Buscar user-info para descobrir papel
const infoResp = await authService.getUserInfo();
console.log("[AuthContext] Resposta user-info:", infoResp);
if (!infoResp.success || !infoResp.data) {
console.error(
"[AuthContext] Falha ao obter user-info:",
infoResp.error
);
toast.error(infoResp.error || "Falha ao obter user-info");
return false;
}
const sessionUser = buildSessionUser(infoResp.data);
console.log("[AuthContext] Usuário da sessão criado:", sessionUser);
setUser(sessionUser);
persist({
user: sessionUser,
savedAt: new Date().toISOString(),
token: loginResp.data.access_token,
refreshToken: loginResp.data.refresh_token,
});
console.log("[AuthContext] Login completo!");
toast.success("Login realizado");
return true;
},
[persist, buildSessionUser]
);
// Para paciente, aproveitamos fluxo existente: quando o paciente já foi validado externamente no loginPaciente
const loginPaciente = useCallback(
async (paciente: { id: string; nome: string; email?: string }) => {
console.log("[AuthContext] loginPaciente chamado com:", paciente);
const newUser: PacienteUser = {
id: paciente.id,
nome: paciente.nome,
email: paciente.email,
role: "paciente",
pacienteId: paciente.id,
roles: ["paciente"],
permissions: {},
};
console.log("[AuthContext] Usuário criado:", newUser);
setUser(newUser);
persist({ user: newUser, savedAt: new Date().toISOString() });
// Bridge para páginas que ainda leem localStorage("pacienteLogado")
try {
const legacy = {
_id: paciente.id,
nome: paciente.nome,
email: paciente.email ?? "",
cpf: "",
telefone: "",
};
localStorage.setItem("pacienteLogado", JSON.stringify(legacy));
} catch {
// ignore
}
console.log("[AuthContext] Usuário persistido no localStorage");
toast.success(`Bem-vindo(a), ${paciente.nome}`);
return true;
},
[persist]
);
const logout = useCallback(async () => {
try {
const resp = await authService.logout(); // chama /auth/v1/logout (204 esperado)
if (!resp.success && resp.error) {
toast.error(`Falha no logout remoto: ${resp.error}`);
} else {
toast.success("Sessão encerrada no servidor");
}
} catch (e) {
console.warn("Erro inesperado ao executar logout remoto", e);
toast("Logout local (falha remota)");
} finally {
// Limpa contexto local
setUser(null);
clearPersisted();
authService.clearLocalAuth();
try {
localStorage.removeItem("pacienteLogado");
} catch {
// ignore
}
// Modelo somente Supabase: nenhum token técnico para invalidar
}
}, [clearPersisted]);
const refreshSession = useCallback(async () => {
// Futuro: usar refresh token real. Agora apenas revalida estrutura.
try {
const raw = localStorage.getItem(STORAGE_KEY);
if (!raw) return;
const parsed = JSON.parse(raw) as PersistedSession;
if (!parsed?.user?.role) return;
setUser(parsed.user);
} catch {
// ignorar
}
}, []);
const value: AuthContextValue = useMemo(
() => ({
user,
role: user?.role ?? null,
roles: user?.roles || (user?.role ? [user.role] : []),
permissions: user?.permissions || {},
isAuthenticated: !!user,
loading,
loginSecretaria,
loginMedico,
loginComEmailSenha,
loginPaciente,
logout,
refreshSession,
}),
[
user,
loading,
loginSecretaria,
loginMedico,
loginComEmailSenha,
loginPaciente,
logout,
refreshSession,
]
);
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
};
export default AuthContext;