/** * 로그인 폼 한 벌. * * ★ 로그인 화면과 에디터 진입 관문이 같은 폼을 쓴다. 두 벌로 두면 토큰을 심는 순서 * (signIn → me)가 한쪽에서만 지켜지고, 그 실수는 "로그인은 됐는데 계속 401" 로 나타난다. */ import {useState, type FormEvent, type ReactNode} from 'react'; import {LogIn} from 'lucide-react'; import {googleLogin, login} from '@/api'; import {GoogleSignInButton} from '@/components/auth/GoogleSignInButton'; import {Button} from '@/components/ui/button'; import {Input} from '@/components/ui/input'; import {notifyApiError} from '@/lib/notify'; import {isGoogleLoginEnabled} from '@/lib/googleIdentity'; import {establishSession} from '@/lib/session'; interface SignInFormProps { /** 폼 위에 붙는 제목·설명. 화면마다 하는 말이 다르다. */ header: ReactNode; /** 폼 아래 각주(빌더로 돌아가기 등). */ footer?: ReactNode; submitLabel?: string; onSignedIn?: () => void; } export function SignInForm({header, footer, submitLabel = '로그인', onSignedIn}: SignInFormProps) { const [id, setId] = useState(''); const [password, setPassword] = useState(''); const [isSubmitting, setIsSubmitting] = useState(false); // 구글은 가입과 로그인이 같은 동작이다 — 처음 온 계정이면 백엔드가 그 자리에서 만든다. const handleGoogle = async (credential: string) => { setIsSubmitting(true); try { const res = await googleLogin({credential}); if (res.result?.success === false) { notifyApiError({data: res}, '구글 로그인에 실패했습니다.'); return; } if (!(await establishSession(res, ''))) { notifyApiError({data: res}, '로그인 응답에 토큰이 없습니다.'); return; } onSignedIn?.(); } catch (error) { notifyApiError(error, '구글 로그인에 실패했습니다.'); } finally { setIsSubmitting(false); } }; const handleSubmit = async (event: FormEvent) => { event.preventDefault(); setIsSubmitting(true); try { const res = await login({id, password}); if (res.result?.success === false) { notifyApiError({data: res}, '아이디 또는 비밀번호를 확인해 주세요.'); return; } // 토큰 심는 순서(signIn → me)는 lib/session 한 곳에만 둔다 — 이 파일 맨 위 주석이 // 경고하던 그 중복이다. 로그인 화면·가입 화면·자동 로그인이 전부 같은 함수를 쓴다. if (!(await establishSession(res, id))) { notifyApiError({data: res}, '로그인 응답에 토큰이 없습니다.'); return; } onSignedIn?.(); } catch (error) { notifyApiError(error, '로그인에 실패했습니다.'); } finally { setIsSubmitting(false); } }; return (
); }