'use client'; import './style.scss'; import Link from 'next/link'; import { Checkbox } from '@/components/ui/checkbox'; import { Field, FieldGroup, FieldLabel } from "@/components/ui/field"; import { useState, useEffect, useRef } from 'react'; import { useSearchParams } from 'next/navigation'; import { GoogleLogin } from '@react-oauth/google'; import { fetchApi } from '@/lib/utils/client'; import { LoginRequest } from '@/types/request/auth'; import { LoginResponse } from '@/types/response/auth'; import useAuth from '@/hooks/useAuth'; import { useConfigContext } from '@/contexts/configProvider'; export default function Page() { const { login } = useAuth(); const config = useConfigContext(); // 소셜 로그인 on/off (Admin /Config/External) — 값이 없으면(구버전 config) 노출(fail-open), 백엔드가 최종 게이트 const naverEnabled = config?.external?.naverLoginEnabled !== false; const kakaoEnabled = config?.external?.kakaoLoginEnabled !== false; const googleEnabled = config?.external?.googleLoginEnabled !== false; const searchParams = useSearchParams(); const [error, setError] = useState(''); const [loading, setLoading] = useState(false); const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [rememberMe, setRememberMe] = useState(false); const emailRef = useRef(null); const passwordRef = useRef(null); const googleBtnRef = useRef(null); const [googleBtnWidth, setGoogleBtnWidth] = useState(0); useEffect(() => { if (error) { alert(error); setError(''); } }, [error]); // URL ?error=... 처리 — Google OAuth callback 등 외부에서 리다이렉트된 에러 메시지 표시 useEffect(() => { const urlError = searchParams.get('error'); if (urlError) { setError(urlError); // 뒤로가기/새로고침 시 alert 중복 방지를 위해 query 정리 const url = new URL(window.location.href); url.searchParams.delete('error'); window.history.replaceState({}, '', url.toString()); } }, [searchParams]); useEffect(() => { if (googleBtnRef.current) { const observer = new ResizeObserver(entries => { for (const entry of entries) { setGoogleBtnWidth(Math.floor(entry.contentRect.width)); } }); observer.observe(googleBtnRef.current); return () => observer.disconnect(); } }, []); // 로그인 검증 const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); try { if (email.length < 1) { emailRef.current?.focus(); throw new Error('이메일을 입력하세요.'); } if (password.length < 1) { passwordRef.current?.focus(); throw new Error('비밀번호를 입력하세요.'); } await fetchApi('/api/auth/login', { method: 'POST', body: { Email: email, Password: password } as LoginRequest }); login(rememberMe); } catch (err) { if (err instanceof Error) { setError(err.message); } } finally { setLoading(false); } } // 구글 로그인 const handleGoogleLogin = async (credentialResponse: { credential?: string }) => { try { await fetchApi('/api/auth/google-login', { method: 'POST', body: { credential: credentialResponse.credential } }); login(rememberMe); } catch (err) { if (err instanceof Error) { setError(err.message); } } }; const handleGoogleLoginFailed = () => { setError('Google 로그인에 실패했습니다.'); }; return ( <>
로그인
setEmail(e.target.value)} autoComplete="off" autoFocus /> setPassword(e.target.value)} /> {googleEnabled && (
{googleBtnWidth > 0 && ( )}
)} {/* Naver/Kakao 소셜 로그인 — start 라우트가 state 생성 후 각사 authorize 로 리다이렉트. route handler 라 Link prefetch 시 OAuth 시작이 실행되므로 일반 사용. 노출 여부는 Admin /Config/External 의 사용 체크박스(config.external.*LoginEnabled)로 제어 */} {(naverEnabled || kakaoEnabled) && ( )}
아직 회원이 아니신가요?
회원가입 한번으로 커뮤니티에 참여하세요!
회원가입

비밀번호를 잊으셨나요?
비밀번호를 깜박했다면 다시 설정할 수 있어요!
비밀번호 재설정
setRememberMe(checked === true)} /> 로그인 상태 유지
); }