;s
This commit is contained in:
104
app/components/GoogleLoginButton.tsx
Normal file
104
app/components/GoogleLoginButton.tsx
Normal file
@@ -0,0 +1,104 @@
|
||||
"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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user