From d71cec4709fec5113dc9c99be3005c72255e533f Mon Sep 17 00:00:00 2001 From: Hyojin Ahn Date: Tue, 23 Jun 2026 16:19:13 -0400 Subject: [PATCH] =?UTF-8?q?Google=20Workspace=20OAuth2=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/auth/pages/Login.jsx | 40 ++++++++++++++++++++++++-- src/features/crm/CsHome/CsHomePage.jsx | 4 +-- src/index.css | 28 ++++++++++++++++++ 3 files changed, 68 insertions(+), 4 deletions(-) diff --git a/src/auth/pages/Login.jsx b/src/auth/pages/Login.jsx index d149810..a807fc7 100644 --- a/src/auth/pages/Login.jsx +++ b/src/auth/pages/Login.jsx @@ -1,8 +1,15 @@ -import { useState } from 'react' +import { useEffect, useState } from 'react' import { fetchWithCred } from '../../lib/api' import { authUrl } from '../../lib/endpoints' import { useAuth } from '../../app/providers/AuthProvider' +// OAuth2 성공 핸들러가 실패 시 ?login_error=코드 로 돌려보낸다. +const LOGIN_ERROR_MESSAGES = { + not_registered: '등록되지 않은 계정입니다. 관리자에게 문의하세요.', + domain_not_allowed: 'greenoilinc.com 계정으로만 로그인할 수 있습니다.', + inactive: '비활성화된 계정입니다. 관리자에게 문의하세요.', +} + export default function Login() { const { login } = useAuth() @@ -13,6 +20,19 @@ export default function Login() { const [loading, setLoading] = useState(false) const [error, setError] = useState(null) + // 리다이렉트로 돌아온 OAuth 로그인 에러 표시 + useEffect(() => { + const params = new URLSearchParams(window.location.search) + const code = params.get('login_error') + if (code) { + setError(LOGIN_ERROR_MESSAGES[code] ?? '로그인에 실패했습니다.') + // 주소창에서 쿼리 제거 (새로고침 시 에러가 남지 않도록) + params.delete('login_error') + const clean = window.location.pathname + (params.toString() ? `?${params}` : '') + window.history.replaceState({}, '', clean) + } + }, []) + const handleChange = (e) => { setForm({ ...form, @@ -22,7 +42,7 @@ export default function Login() { const handleLogin = async (e) => { // enter 키에 페이지 리로드막고 직접 처리 - e.preventDefault() + e.preventDefault() try { setLoading(true) setError(null) @@ -48,6 +68,11 @@ export default function Login() { } } + const handleGoogleLogin = () => { + // fetch 아님! 전체 페이지 이동 (Google 리다이렉트 흐름) + window.location.href = authUrl('/oauth2/authorization/google') + } + return (

GREEN FIELD

@@ -80,6 +105,17 @@ export default function Login() { > {loading ? 'Signing in...' : 'Login'} + +
또는
+ +
) } diff --git a/src/features/crm/CsHome/CsHomePage.jsx b/src/features/crm/CsHome/CsHomePage.jsx index 0ca36bc..c1bd562 100644 --- a/src/features/crm/CsHome/CsHomePage.jsx +++ b/src/features/crm/CsHome/CsHomePage.jsx @@ -146,8 +146,8 @@ export default function CsHomePage() { {custErr && 고객 요약 미연동 · {custErr}}
- - + +
diff --git a/src/index.css b/src/index.css index 8083666..6ad5448 100644 --- a/src/index.css +++ b/src/index.css @@ -120,3 +120,31 @@ button { text-align: center; } +/* google login */ +.login-divider { + display: flex; + align-items: center; + text-align: center; + color: #9aa0a6; + font-size: 12px; +} +.login-divider::before, +.login-divider::after { + content: ''; + flex: 1; + border-bottom: 1px solid #e3e5e8; +} +.login-divider span { + padding: 0 10px; +} + +.login-google { + background: white; + color: #3c4043; + border: 1px solid #dadce0; +} +.login-google:disabled { + background: #f5f5f5; + color: #9aa0a6; + cursor: not-allowed; +} \ No newline at end of file