import { useEffect, useRef, useState } from "react";
import { Link, useNavigate } from "react-router-dom";
import { toast } from "sonner";
import { AppHeader } from "../components/layout/AppHeader";
import { UsersIcon } from "../components/ui/Icons";
import { OrcidLogo } from "../components/ui/OrcidLogo";
import { Spinner } from "../components/ui/Spinner";
import { getInitials } from "../utils/formatters";
import { formatOrcidInput, isValidOrcid } from "../utils/orcid";
import { getOrcidAuthorizeUrl, searchResearcher } from "../services/api";
import { useAuth } from "../contexts/AuthContext";
import { AUTH_MESSAGE_TYPE, AUTH_ERROR_TYPE } from "../contexts/AuthContext";
import Footer from "../components/layout/Footer";
/**
* Entry view: login con ORCID iD + búsqueda individual anónima +
* buscador grupal para múltiples investigadores.
*
* Flujo de login:
* - abre popup OAuth → sandbox.orcid.org → /callback
* - recibe JWT → cierra popup → estado actualizado aquí.
*/
export function LandingPage() {
const navigate = useNavigate();
const { isAuthenticated, userName, userOrcidId } = useAuth();
// If the JWT doesn't carry the name (ORCID sandbox omits it), fetch it
// lazily from the public researcher endpoint so the identity block is correct.
const [resolvedName, setResolvedName] = useState(null);
useEffect(() => {
if (!isAuthenticated || !userOrcidId) { setResolvedName(null); return; }
if (userName) { setResolvedName(userName); return; }
let cancelled = false;
searchResearcher(userOrcidId)
.then((bundle) => { if (!cancelled) setResolvedName(bundle.researcher?.name ?? null); })
.catch(() => {});
return () => { cancelled = true; };
}, [isAuthenticated, userOrcidId, userName]);
const displayName = resolvedName ?? userName;
const [orcidInput, setOrcidInput] = useState("");
const [error, setError] = useState("");
const [validating, setValidating] = useState(false);
const [loginLoading, setLoginLoading] = useState(false);
// Group search state
const [groupTags, setGroupTags] = useState([]);
const [groupRawInput, setGroupRawInput] = useState("");
const [groupError, setGroupError] = useState("");
const [groupLoading, setGroupLoading] = useState(false);
const groupInputRef = useRef(null);
// Cleanup refs for popup polling interval
const popupRef = useRef(null);
const popupTimerRef = useRef(null);
// Clean up popup polling on unmount
useEffect(() => {
return () => {
if (popupTimerRef.current) clearInterval(popupTimerRef.current);
};
}, []);
function handleOrcidChange(event) {
setOrcidInput(formatOrcidInput(event.target.value));
if (error) setError("");
}
async function handleValidate() {
if (!isValidOrcid(orcidInput)) {
setError(
"Formato inválido. El ORCID iD debe tener la estructura: 0000-0002-1234-5678",
);
return;
}
setValidating(true);
try {
const bundle = await searchResearcher(orcidInput);
navigate(`/dashboard/${orcidInput}`, { state: { bundle } });
} catch (err) {
toast.error("No se pudo buscar el ORCID iD", {
description: err?.message ?? "Inténtalo de nuevo en unos segundos.",
});
} finally {
setValidating(false);
}
}
function handleOrcidLogin() {
setLoginLoading(true);
const authorizeUrl = getOrcidAuthorizeUrl();
const popup = window.open(
authorizeUrl,
"orcid_oauth",
"width=600,height=700,scrollbars=yes,resizable=yes",
);
if (!popup || popup.closed) {
// El navegador bloqueó el popup → hacemos redirect completo
setLoginLoading(false);
window.location.href = authorizeUrl;
return;
}
popupRef.current = popup;
// Escuchamos el postMessage que AuthCallbackPage envía al completar
function handleMessage(event) {
if (event.origin !== window.location.origin) return;
if (event.data?.type === AUTH_MESSAGE_TYPE) {
cleanup();
setLoginLoading(false);
toast.success("Sesión iniciada con ORCID", {
description: "Ya puedes ver qué publicaciones son nuevas para ti.",
});
} else if (event.data?.type === AUTH_ERROR_TYPE) {
cleanup();
setLoginLoading(false);
toast.error("No se pudo iniciar sesión", {
description: event.data.error ?? "Inténtalo de nuevo.",
});
}
}
window.addEventListener("message", handleMessage);
// Detectamos si el usuario cierra el popup manualmente antes de autenticar
popupTimerRef.current = setInterval(() => {
if (popup.closed) {
cleanup();
setLoginLoading(false);
}
}, 500);
function cleanup() {
window.removeEventListener("message", handleMessage);
if (popupTimerRef.current) {
clearInterval(popupTimerRef.current);
popupTimerRef.current = null;
}
}
}
/**
* Splits a raw string on comma/space/newline separators, promotes valid
* ORCIDs to tags (deduplicating against existing ones), and returns any
* leftover invalid tokens joined by a space so the user can correct them.
*/
function commitRawInput(raw) {
const parts = raw.split(/[\s,\n]+/).map((s) => s.trim()).filter(Boolean);
const valid = parts.filter(isValidOrcid);
const invalid = parts.filter((p) => !isValidOrcid(p));
if (valid.length > 0) {
setGroupTags((prev) => [...new Set([...prev, ...valid])]);
if (groupError) setGroupError("");
}
return invalid.join(" ");
}
function handleGroupTagKeyDown(event) {
const { key } = event;
if (key === "Enter" || key === "," || key === " ") {
event.preventDefault();
const leftover = commitRawInput(groupRawInput);
setGroupRawInput(leftover);
} else if (key === "Backspace" && groupRawInput === "" && groupTags.length > 0) {
setGroupTags((prev) => prev.slice(0, -1));
}
}
function handleGroupRawChange(event) {
setGroupRawInput(event.target.value);
if (groupError) setGroupError("");
}
function handleGroupPaste(event) {
event.preventDefault();
const pasted = event.clipboardData.getData("text");
const combined = groupRawInput ? `${groupRawInput} ${pasted}` : pasted;
const leftover = commitRawInput(combined);
setGroupRawInput(leftover);
}
function removeGroupTag(tag) {
setGroupTags((prev) => prev.filter((t) => t !== tag));
if (groupError) setGroupError("");
}
async function handleGroupSearch() {
if (groupTags.length === 0) {
setGroupError("Introduce al menos un ORCID iD válido.");
return;
}
setGroupError("");
setGroupLoading(true);
try {
navigate("/group", { state: { orcidIds: groupTags } });
} finally {
setGroupLoading(false);
}
}
function handleKeyDown(event) {
if (event.key === "Enter") handleValidate();
}
return (
{/* ── Hero ── */}
Tus publicaciones, listas para depositar.
Conecta tu ORCID y descárgalas en XML cuando quieras.
{/* ── Two-column grid ── */}
{/* ── Left: individual search + login ── */}
{isAuthenticated ? (
{getInitials(displayName ?? userOrcidId ?? "?")}
{displayName ?? "Mi Perfil"}
{userOrcidId ?? "—"}
Verás publicaciones nuevas marcadas en el dashboard
) : (
<>
Actualizamos tus publicaciones automáticamente cada mes.
>
)}
{isAuthenticated ? "TU ORCID iD" : "O INTRODUCE TU ORCID iD"}
{error && (
{error}
)}
{isAuthenticated
? "Busca un investigador o usa «Cerrar sesión» arriba."
: "Pulsa «Iniciar sesión» para autenticarte, o «Buscar» de forma anónima."}
{/* ── Right: group search ── */}
Búsqueda grupal de investigadores
Pega varios ORCID iDs separados por comas, espacios o saltos de
línea para buscar y comparar varios investigadores a la vez.
{/* ── Tag input area ── */}
groupInputRef.current?.focus()}
className={`flex min-h-[120px] cursor-text flex-wrap content-start gap-1.5 overflow-y-auto rounded-lg border px-3 py-2.5 transition-colors ${
groupError
? "border-border-danger"
: "border-surface-border-strong focus-within:border-brand-accent"
}`}
>
{groupTags.map((tag) => (
{tag}
))}
{groupError && (
{groupError}
)}
);
}
export default LandingPage;