playreel/frontend/components/google-sign-in.tsx
2026-09-15 16:00:54 +09:00

55 lines
1.8 KiB
TypeScript

"use client";
/** 구글이 그려주는 버튼. 우리가 모양을 흉내 내면 브랜드 규정에 걸린다 */
import { useEffect, useRef, useState } from "react";
import {
fetchConfig, loadGoogleIdentity, type GoogleButtonOptions,
} from "@/lib/auth";
export default function GoogleSignIn({ onCredential, text = "signin_with" }: {
onCredential: (credential: string) => void;
text?: GoogleButtonOptions["text"];
}) {
const host = useRef<HTMLDivElement>(null);
const [error, setError] = useState<string | null>(null);
// 콜백이 매 렌더마다 바뀌어도 구글에 다시 등록하지 않게 최신 것만 들고 있는다
const latest = useRef(onCredential);
latest.current = onCredential;
useEffect(() => {
let cancelled = false;
(async () => {
const config = await fetchConfig();
if (!config.enabled) throw new Error("구글 로그인이 설정되지 않았습니다");
const google = await loadGoogleIdentity();
if (cancelled || !host.current) return;
google.initialize({
client_id: config.client_id,
callback: (response) => {
if (response.credential) latest.current(response.credential);
},
auto_select: false,
cancel_on_tap_outside: true,
});
google.renderButton(host.current, {
type: "standard", theme: "filled_black", size: "large",
shape: "pill", text, logo_alignment: "center", width: 320,
});
})().catch((failure) => {
if (!cancelled) setError((failure as Error).message);
});
return () => { cancelled = true; };
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [text]);
if (error) {
return <p style={{ color: "#ff7a7a", fontSize: "var(--text-sm)", margin: 0 }}>{error}</p>;
}
return <div ref={host} style={{ display: "flex", justifyContent: "center" }} />;
}