goauthsocialfrontendtest/goauthsocialfrontendtest/components/YandexLoginButton.tsx
2025-10-24 10:29:41 +05:00

230 lines
6.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use client';
import type { JSX } from "react";
import { useEffect, useMemo, useRef, useState } from "react";
import { assertClientEnv, env } from "@/lib/env";
type ButtonStatus = "initial" | "ready" | "fallback" | "error";
declare global {
interface Window {
YaAuthSuggest?: {
init: (
config: Record<string, unknown>,
origin: string,
options: Record<string, unknown>,
) => Promise<{
handler: {
on: (event: string, callback: () => void) => void;
redirect: () => void;
};
}>;
};
}
}
const YANDEX_SDK_URL =
"https://yastatic.net/s3/passport-sdk/autofill/v1/sdk-suggest.js";
const YANDEX_SDK_ORIGIN =
"https://yastatic.net/s3/passport-sdk/autofill/v1/";
async function requestBackendRedirect(): Promise<string | null> {
if (!env.backendBaseUrl) {
return null;
}
try {
const response = await fetch(
`${env.backendBaseUrl.replace(
/\/$/,
"",
)}/api/v1/auth/authorize/yandex`,
{
method: "GET",
headers: {
Accept: "application/json",
},
credentials: "include",
},
);
if (!response.ok) {
console.error(
"Не удалось получить redirect URL с бэкенда",
response.status,
);
return null;
}
const payload = await response.json();
return payload?.redirect_uri ?? null;
} catch (error) {
console.error("Ошибка при запросе redirect URL", error);
return null;
}
}
export function YandexLoginButton(): JSX.Element {
const containerRef = useRef<HTMLDivElement | null>(null);
const [status, setStatus] = useState<ButtonStatus>("initial");
const [errorMessage, setErrorMessage] = useState<string | null>(null);
const missingEnv = useMemo(() => assertClientEnv(), []);
useEffect(() => {
if (missingEnv.length > 0) {
setErrorMessage(
`Не заданы переменные окружения: ${missingEnv.join(
", ",
)}. Кнопка Яндекс ID будет скрыта.`,
);
setStatus("error");
return;
}
if (!containerRef.current) {
return;
}
const initializeButton = () => {
if (!window.YaAuthSuggest) {
setErrorMessage("Yandex ID SDK недоступен.");
setStatus("fallback");
return;
}
window.YaAuthSuggest.init(
{
client_id: env.yandexClientId,
response_type: "code",
redirect_uri: env.yandexRedirectUri,
},
YANDEX_SDK_ORIGIN,
{
view: "button",
parentId: containerRef.current?.id ?? "",
buttonView: "main",
buttonTheme: "dark",
buttonSize: "l",
buttonBorderRadius: "16",
buttonIcon: "ya",
},
)
.then((result) => {
setStatus("ready");
const { handler } = result;
const host = containerRef.current;
if (handler && typeof handler.on === "function") {
handler.on("click", () => {
try {
handler.redirect();
} catch (error) {
console.error(
"Ошибка redirect через handler, fallback к бэкенду",
error,
);
void handleFallback();
}
});
} else if (host) {
host.addEventListener("click", () => {
if (typeof handler?.redirect === "function") {
handler.redirect();
} else {
void handleFallback();
}
});
}
})
.catch((error) => {
console.error("Ошибка инициализации Yandex ID", error);
setErrorMessage("Не удалось инициализировать кнопку Yandex ID.");
setStatus("fallback");
});
};
const existingScript = document.querySelector<HTMLScriptElement>(
`script[src="${YANDEX_SDK_URL}"]`,
);
if (existingScript?.dataset.loaded === "true") {
initializeButton();
return;
}
const script = existingScript ?? document.createElement("script");
if (!existingScript) {
script.src = YANDEX_SDK_URL;
script.async = true;
script.dataset.loaded = "false";
script.onload = () => {
script.dataset.loaded = "true";
initializeButton();
};
script.onerror = () => {
setErrorMessage("Не удалось загрузить Yandex ID SDK.");
setStatus("fallback");
};
document.body.appendChild(script);
} else if (existingScript.dataset.loaded === "true") {
initializeButton();
} else {
existingScript.addEventListener("load", initializeButton, { once: true });
existingScript.addEventListener(
"error",
() => {
setErrorMessage("Не удалось загрузить Yandex ID SDK.");
setStatus("fallback");
},
{ once: true },
);
}
return () => {
existingScript?.removeEventListener("load", initializeButton);
};
}, [missingEnv]);
const handleFallback = async () => {
setStatus("fallback");
const target = await requestBackendRedirect();
if (target) {
window.location.href = target;
} else {
setErrorMessage(
"Не удалось автоматически перенаправить на авторизацию Яндекс. Попробуйте позже.",
);
setStatus("error");
}
};
return (
<div className="flex flex-col items-center gap-4">
<div
id="ya-oauth-button"
ref={containerRef}
className={
status === "fallback" || status === "error" ? "hidden" : "flex"
}
/>
{(status === "fallback" || status === "error") && (
<button
type="button"
onClick={handleFallback}
className="inline-flex w-full items-center justify-center gap-2 rounded-xl bg-yellow-300 px-5 py-3 text-sm font-semibold text-stone-950 shadow transition hover:bg-yellow-200 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-yellow-400"
>
Продолжить через Яндекс
</button>
)}
{errorMessage && (
<p className="text-center text-sm text-red-300" role="alert">
{errorMessage}
</p>
)}
</div>
);
}