177 lines
6.3 KiB
TypeScript
177 lines
6.3 KiB
TypeScript
/**
|
|
* Página de Callback do Magic Link
|
|
* Processa o token do magic link e autentica o usuário
|
|
*/
|
|
|
|
import { useEffect, useState } from "react";
|
|
import { useNavigate } from "react-router-dom";
|
|
import { supabase } from "../lib/supabase";
|
|
import { Loader2, CheckCircle, XCircle } from "lucide-react";
|
|
import toast from "react-hot-toast";
|
|
|
|
export default function AuthCallback() {
|
|
const navigate = useNavigate();
|
|
const [status, setStatus] = useState<"loading" | "success" | "error">(
|
|
"loading"
|
|
);
|
|
const [message, setMessage] = useState("Processando autenticação...");
|
|
|
|
useEffect(() => {
|
|
const handleCallback = async () => {
|
|
try {
|
|
console.log("[AuthCallback] Iniciando processamento");
|
|
|
|
// Verificar se é um token de recovery
|
|
const hash = window.location.hash;
|
|
const hashParams = new URLSearchParams(hash.substring(1));
|
|
const accessToken = hashParams.get("access_token");
|
|
const type = hashParams.get("type");
|
|
|
|
console.log("[AuthCallback] Hash:", hash);
|
|
console.log("[AuthCallback] Type:", type);
|
|
console.log("[AuthCallback] Access Token presente:", !!accessToken);
|
|
|
|
// Se for recovery, redirecionar para página de reset
|
|
if (type === "recovery" && accessToken) {
|
|
console.log(
|
|
"[AuthCallback] ✅ Token de recovery detectado, redirecionando para /reset-password"
|
|
);
|
|
setStatus("success");
|
|
setMessage("Redirecionando para página de redefinição de senha...");
|
|
|
|
// Redirecionar preservando o hash com o token
|
|
setTimeout(() => {
|
|
navigate(`/reset-password${hash}`, { replace: true });
|
|
}, 1000);
|
|
return;
|
|
}
|
|
|
|
console.log("[AuthCallback] Processando magic link normal");
|
|
|
|
// Supabase automaticamente processa os query params
|
|
const {
|
|
data: { session },
|
|
error,
|
|
} = await supabase.auth.getSession();
|
|
|
|
if (error) {
|
|
console.error("[AuthCallback] Erro ao obter sessão:", error);
|
|
throw error;
|
|
}
|
|
|
|
if (!session) {
|
|
throw new Error(
|
|
"Nenhuma sessão encontrada. O link pode ter expirado."
|
|
);
|
|
}
|
|
|
|
console.log("[AuthCallback] Sessão obtida:", {
|
|
user: session.user.email,
|
|
role: session.user.role,
|
|
});
|
|
|
|
// Magic link ou qualquer callback com sessão válida:
|
|
// Salvar tokens diretamente no localStorage
|
|
console.log("[AuthCallback] Salvando tokens e user no localStorage");
|
|
|
|
localStorage.setItem("mediconnect_access_token", session.access_token);
|
|
localStorage.setItem("mediconnect_refresh_token", session.refresh_token);
|
|
localStorage.setItem(
|
|
"mediconnect_user",
|
|
JSON.stringify({
|
|
id: session.user.id,
|
|
email: session.user.email,
|
|
nome: session.user.user_metadata?.full_name || session.user.email,
|
|
role: session.user.user_metadata?.role || "paciente",
|
|
})
|
|
);
|
|
|
|
console.log("[AuthCallback] Autenticação concluída");
|
|
|
|
setStatus("success");
|
|
setMessage("Autenticado com sucesso! Redirecionando...");
|
|
toast.success("Login realizado com sucesso!");
|
|
|
|
// Redirecionar baseado no contexto salvo ou role do usuário
|
|
setTimeout(() => {
|
|
// Verificar se há redirecionamento salvo do magic link
|
|
const savedRedirect = localStorage.getItem("magic_link_redirect");
|
|
|
|
if (savedRedirect) {
|
|
console.log("[AuthCallback] Redirecionando para:", savedRedirect);
|
|
localStorage.removeItem("magic_link_redirect"); // Limpar após uso
|
|
navigate(savedRedirect, { replace: true });
|
|
return;
|
|
}
|
|
|
|
// Fallback: redirecionar baseado no role
|
|
const userRole = session.user.user_metadata?.role || "paciente";
|
|
console.log("[AuthCallback] Redirecionando baseado no role:", userRole);
|
|
|
|
switch (userRole) {
|
|
case "medico":
|
|
navigate("/painel-medico", { replace: true });
|
|
break;
|
|
case "secretaria":
|
|
navigate("/painel-secretaria", { replace: true });
|
|
break;
|
|
case "paciente":
|
|
default:
|
|
navigate("/acompanhamento", { replace: true });
|
|
break;
|
|
}
|
|
}, 1500);
|
|
} catch (err: any) {
|
|
console.error("[AuthCallback] Erro:", err);
|
|
setStatus("error");
|
|
setMessage(err.message || "Erro ao processar autenticação");
|
|
toast.error(err.message || "Erro na autenticação");
|
|
}
|
|
};
|
|
|
|
handleCallback();
|
|
}, [navigate]);
|
|
|
|
return (
|
|
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-white dark:from-gray-900 dark:to-gray-950 flex items-center justify-center p-4">
|
|
<div className="max-w-md w-full bg-white dark:bg-gray-800 rounded-lg shadow-lg p-8 text-center">
|
|
{status === "loading" && (
|
|
<>
|
|
<Loader2 className="w-16 h-16 text-blue-600 dark:text-blue-400 mx-auto mb-4 animate-spin" />
|
|
<h1 className="text-2xl font-bold text-gray-900 dark:text-gray-100 mb-2">
|
|
Autenticando
|
|
</h1>
|
|
<p className="text-gray-600 dark:text-gray-400">{message}</p>
|
|
</>
|
|
)}
|
|
|
|
{status === "success" && (
|
|
<>
|
|
<CheckCircle className="w-16 h-16 text-green-600 dark:text-green-400 mx-auto mb-4" />
|
|
<h1 className="text-2xl font-bold text-gray-900 dark:text-gray-100 mb-2">
|
|
Sucesso!
|
|
</h1>
|
|
<p className="text-gray-600 dark:text-gray-400">{message}</p>
|
|
</>
|
|
)}
|
|
|
|
{status === "error" && (
|
|
<>
|
|
<XCircle className="w-16 h-16 text-red-600 dark:text-red-400 mx-auto mb-4" />
|
|
<h1 className="text-2xl font-bold text-gray-900 dark:text-gray-100 mb-2">
|
|
Erro na Autenticação
|
|
</h1>
|
|
<p className="text-gray-600 dark:text-gray-400 mb-6">{message}</p>
|
|
<button
|
|
onClick={() => navigate("/")}
|
|
className="px-6 py-3 bg-blue-600 text-white rounded-lg font-medium hover:bg-blue-700 transition-colors"
|
|
>
|
|
Voltar ao Início
|
|
</button>
|
|
</>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|