105 lines
3.3 KiB
TypeScript
105 lines
3.3 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useRef, useState } from "react";
|
|
|
|
declare global {
|
|
interface Window {
|
|
google?: {
|
|
accounts?: {
|
|
id?: {
|
|
initialize: (options: {
|
|
client_id: string;
|
|
callback: (response: { credential?: string }) => void;
|
|
}) => void;
|
|
renderButton: (
|
|
parent: HTMLElement,
|
|
options: {
|
|
theme?: "outline" | "filled_blue" | "filled_black";
|
|
size?: "large" | "medium" | "small";
|
|
width?: number;
|
|
text?: "signin_with" | "signup_with" | "continue_with" | "signin";
|
|
shape?: "rectangular" | "pill" | "circle" | "square";
|
|
}
|
|
) => void;
|
|
};
|
|
};
|
|
};
|
|
}
|
|
}
|
|
|
|
const GOOGLE_SCRIPT_ID = "google-identity-services";
|
|
const GOOGLE_CLIENT_ID = process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID || "";
|
|
|
|
function loadGoogleScript(): Promise<void> {
|
|
if (typeof window === "undefined") return Promise.resolve();
|
|
if (window.google?.accounts?.id) return Promise.resolve();
|
|
const existing = document.getElementById(GOOGLE_SCRIPT_ID) as HTMLScriptElement | null;
|
|
if (existing) {
|
|
return new Promise((resolve, reject) => {
|
|
existing.addEventListener("load", () => resolve(), { once: true });
|
|
existing.addEventListener("error", () => reject(new Error("Google 登录加载失败")), { once: true });
|
|
});
|
|
}
|
|
return new Promise((resolve, reject) => {
|
|
const script = document.createElement("script");
|
|
script.id = GOOGLE_SCRIPT_ID;
|
|
script.src = "https://accounts.google.com/gsi/client";
|
|
script.async = true;
|
|
script.defer = true;
|
|
script.onload = () => resolve();
|
|
script.onerror = () => reject(new Error("Google 登录加载失败"));
|
|
document.head.appendChild(script);
|
|
});
|
|
}
|
|
|
|
export default function GoogleLoginButton({
|
|
onCredential,
|
|
onError,
|
|
disabled = false,
|
|
}: {
|
|
onCredential: (credential: string) => void;
|
|
onError?: (message: string) => void;
|
|
disabled?: boolean;
|
|
}) {
|
|
const ref = useRef<HTMLDivElement>(null);
|
|
const [ready, setReady] = useState(false);
|
|
|
|
useEffect(() => {
|
|
if (!GOOGLE_CLIENT_ID || !ref.current) return;
|
|
let cancelled = false;
|
|
loadGoogleScript()
|
|
.then(() => {
|
|
if (cancelled || !ref.current || !window.google?.accounts?.id) return;
|
|
ref.current.innerHTML = "";
|
|
window.google.accounts.id.initialize({
|
|
client_id: GOOGLE_CLIENT_ID,
|
|
callback: (response) => {
|
|
if (response.credential) onCredential(response.credential);
|
|
else onError?.("Google 登录失败");
|
|
},
|
|
});
|
|
window.google.accounts.id.renderButton(ref.current, {
|
|
theme: "outline",
|
|
size: "large",
|
|
width: 304,
|
|
text: "continue_with",
|
|
shape: "rectangular",
|
|
});
|
|
setReady(true);
|
|
})
|
|
.catch((error) => onError?.(error instanceof Error ? error.message : "Google 登录加载失败"));
|
|
return () => {
|
|
cancelled = true;
|
|
};
|
|
}, [onCredential, onError]);
|
|
|
|
if (!GOOGLE_CLIENT_ID) return null;
|
|
|
|
return (
|
|
<div className={disabled ? "pointer-events-none opacity-60" : ""}>
|
|
<div ref={ref} className="flex min-h-10 justify-center" />
|
|
{!ready && <div className="h-10 rounded-lg border border-gray-200 dark:border-gray-700" />}
|
|
</div>
|
|
);
|
|
}
|