"구글 로그인 버튼이 없다" 는 지적이 맞았다. 버튼을 LoginPage·SignupPage 에만 붙여 뒀는데,
이 앱에서 사장님이 실제로 로그인 화면을 만나는 자리는 **에디터 진입 관문**(EditorSignInGate →
SignInForm)이다. 정작 거기엔 없었다.
- features/auth/SignInForm: 구글 버튼 추가. 관문·로그인 화면이 같은 폼을 쓰므로 한 곳만 고치면 된다
- lib/googleIdentity: 스크립트를 ?hl=ko 로 받는다. renderButton 의 locale 옵션은 안 먹었다 —
'ko'·'ko_KR' 둘 다 'Continue with Google' 이 그대로 나왔다(실측)
- 버튼 문구는 signin_with('Google 계정으로 로그인'). 가입 화면만 signup_with 로 둔다.
문구 자체는 고를 수 없다 — 구글 브랜드 가이드라 GIS 가 주는 번역을 그대로 쓴다
브라우저 확인(localhost:80): 로그인 화면에 'Google 계정으로 로그인' 한글 노출.
tsc·eslint·vite build 통과.
79 lines
3.0 KiB
TypeScript
79 lines
3.0 KiB
TypeScript
import {useEffect, useRef, useState} from 'react';
|
|
import {GOOGLE_CLIENT_ID, isGoogleLoginEnabled, loadGoogleIdentity} from '@/lib/googleIdentity';
|
|
|
|
/**
|
|
* 구글이 그려 주는 버튼을 그대로 쓴다.
|
|
*
|
|
* ★ 우리가 직접 만든 버튼을 쓰지 않는 이유: 구글 브랜드 가이드가 로고·문구·비율을 정해 두고,
|
|
* 그걸 어긋나게 그리면 심사에서 걸린다. renderButton 이 그 규격을 지켜 준다.
|
|
* ★ 스크립트가 안 오면(광고 차단기·사내망) 빈 칸이 남는다 — 안내 문구로 바꿔 준다.
|
|
* "눌러도 아무 일이 없는 자리"가 화면에 남는 게 제일 나쁘다.
|
|
*/
|
|
export function GoogleSignInButton({
|
|
onCredential,
|
|
text = 'signin_with',
|
|
}: {
|
|
onCredential: (credential: string) => void;
|
|
text?: 'continue_with' | 'signin_with' | 'signup_with';
|
|
}) {
|
|
const holder = useRef<HTMLDivElement>(null);
|
|
const [failed, setFailed] = useState(false);
|
|
|
|
// 콜백은 ref 로 들고 간다 — GIS 는 initialize 시점의 함수를 붙잡고 있어서,
|
|
// 의존성에 넣으면 렌더마다 버튼을 다시 그리게 된다.
|
|
const handler = useRef(onCredential);
|
|
handler.current = onCredential;
|
|
|
|
useEffect(() => {
|
|
if (!isGoogleLoginEnabled()) return;
|
|
let alive = true;
|
|
|
|
loadGoogleIdentity()
|
|
.then((api) => {
|
|
if (!alive || !holder.current) return;
|
|
api.initialize({
|
|
client_id: GOOGLE_CLIENT_ID,
|
|
callback: (res) => {
|
|
if (res.credential) handler.current(res.credential);
|
|
},
|
|
// 화면을 열자마자 마지막 계정으로 자동 로그인되는 동작은 끈다 —
|
|
// 계정이 여러 개인 사람이 원하지 않은 계정으로 들어간다.
|
|
auto_select: false,
|
|
});
|
|
api.renderButton(holder.current, {
|
|
type: 'standard',
|
|
theme: 'outline',
|
|
size: 'large',
|
|
shape: 'rectangular',
|
|
text,
|
|
// ★ 'ko' 로는 영문('Continue with Google')이 그대로 나왔다. 지역까지 줘야 한국어다.
|
|
// 문구는 우리가 못 정한다 — 구글 브랜드 가이드라 GIS 가 주는 번역을 그대로 쓴다.
|
|
locale: 'ko_KR',
|
|
logo_alignment: 'center',
|
|
// GIS 는 숫자 px 만 받는다(최대 400). 컨테이너 폭이 잡히기 전이면 최소값으로 그린다.
|
|
width: holder.current.offsetWidth || 320,
|
|
});
|
|
})
|
|
.catch(() => {
|
|
if (alive) setFailed(true);
|
|
});
|
|
|
|
return () => {
|
|
alive = false;
|
|
};
|
|
}, [text]);
|
|
|
|
if (!isGoogleLoginEnabled()) return null;
|
|
|
|
if (failed) {
|
|
return (
|
|
<p className="text-center text-[11px] leading-relaxed text-muted-foreground">
|
|
구글 로그인을 불러오지 못했습니다. 아이디/비밀번호로 로그인해 주세요.
|
|
</p>
|
|
);
|
|
}
|
|
|
|
// 높이를 미리 잡아 둔다 — 버튼이 뒤늦게 그려지면서 아래 내용이 밀리지 않게.
|
|
return <div ref={holder} className="flex h-10 w-full items-center justify-center" />;
|
|
}
|