diff --git a/client/package-lock.json b/client/package-lock.json index e0b340e8..1290273b 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -1564,8 +1564,7 @@ "@types/prop-types": { "version": "15.7.3", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz", - "integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==", - "dev": true + "integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==" }, "@types/qs": { "version": "6.9.5", @@ -1577,7 +1576,6 @@ "version": "16.9.56", "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.56.tgz", "integrity": "sha512-gIkl4J44G/qxbuC6r2Xh+D3CGZpJ+NdWTItAPmZbR5mUS+JQ8Zvzpl0ea5qT/ZT3ZNTUcDKUVqV3xBE8wv/DyQ==", - "dev": true, "requires": { "@types/prop-types": "*", "csstype": "^3.0.2" @@ -3613,8 +3611,7 @@ "csstype": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.5.tgz", - "integrity": "sha512-uVDi8LpBUKQj6sdxNaTetL6FpeCqTjOvAQuQUa/qAqq8oOd4ivkbhgnqayl0dnPal8Tb/yB1tF+gOvCBiicaiQ==", - "dev": true + "integrity": "sha512-uVDi8LpBUKQj6sdxNaTetL6FpeCqTjOvAQuQUa/qAqq8oOd4ivkbhgnqayl0dnPal8Tb/yB1tF+gOvCBiicaiQ==" }, "cyclist": { "version": "1.0.1", @@ -7763,6 +7760,15 @@ "scheduler": "^0.20.1" } }, + "react-google-login": { + "version": "5.1.25", + "resolved": "https://registry.npmjs.org/react-google-login/-/react-google-login-5.1.25.tgz", + "integrity": "sha512-N7SZkjTEX9NsC3hywXs68SPJWAHo6M19Bs1OIFPirG0yomGF7KnbKjSqoiIfxz1V7fKAt8bkfBAzogwnGWYTeQ==", + "requires": { + "@types/react": "*", + "prop-types": "^15.6.0" + } + }, "react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", diff --git a/client/package.json b/client/package.json index fe342dda..0b4c617d 100644 --- a/client/package.json +++ b/client/package.json @@ -17,6 +17,7 @@ "react": "^17.0.1", "react-custom-scroll": "^4.2.1", "react-dom": "^17.0.1", + "react-google-login": "^5.1.25", "react-redux": "^7.2.2", "react-router-dom": "^5.2.0", "redux": "^4.0.5", diff --git a/client/src/api/index.ts b/client/src/api/index.ts index 05da6718..1febeb50 100644 --- a/client/src/api/index.ts +++ b/client/src/api/index.ts @@ -58,7 +58,8 @@ instance.interceptors.response.use( console.log('요청 취소', err); } else { if (err.response.status === 401) { - if (err.response.config.url !== '/api/auth/login') { + const { url } = err.response.config; + if (url !== '/api/auth/login' || url !== '/api/oauth/google/signup') { window.location.href = '/login'; } } diff --git a/client/src/components/LoginBox/LoginBox.tsx b/client/src/components/LoginBox/LoginBox.tsx index 6f867d74..f61db0ff 100644 --- a/client/src/components/LoginBox/LoginBox.tsx +++ b/client/src/components/LoginBox/LoginBox.tsx @@ -4,17 +4,21 @@ import { useDispatch } from 'react-redux'; import { darken } from 'polished'; import { Link } from 'react-router-dom'; import isEmail from 'validator/es/lib/isEmail'; -import { loginRequest } from '@/store/modules/auth.slice'; +import { loginRequest, loginSuccess } from '@/store/modules/auth.slice'; import { FormButton, FormInput as Input, FormLabel as Label } from '@/styles/shared/form'; -import { WarningIcon } from '@/components'; +import { WarningIcon, GoogleLogoIcon } from '@/components'; import { IconBox, WarningText } from '@/components/EmailBox/EmailBox'; import { flex } from '@/styles/mixin'; import { useAuthState } from '@/hooks'; import LoadingSvg from '@/public/icon/loading.svg'; +import { GoogleLogin } from 'react-google-login'; +import config from '@/config'; +import { authService } from '@/services'; const Container = styled.div` width: 100%; height: 100%; + ${flex('center', 'flex-start', 'column')}; `; const Title = styled.div` @@ -25,9 +29,15 @@ const Title = styled.div` color: #453841; `; +const DescText = styled.div` + color: ${(props) => props.theme.color.black4}; + font-size: 1rem; + margin-bottom: 2rem; +`; + const Form = styled.form` width: 25rem; - margin: 1.5rem auto; + margin: 0 auto 1.5rem auto; `; const SignupButton = styled(FormButton)` @@ -62,6 +72,46 @@ const LoadingButton = styled(FormButton)` ${flex()} `; +const LineWithText = styled.div` + width: 25rem; + ${flex()}; + position: relative; + margin: 1rem 0; +`; + +const LineText = styled.div` + padding: 0 20px; + padding-bottom: 3px; + margin-top: 0.5rem; + flex-shrink: 0; + font-weight: bold; + font-size: 0.9rem; + color: ${(props) => props.theme.color.black8}; +`; + +const Line = styled.hr` + border: none; + border-top: 1px solid ${(props) => props.theme.color.lightGray1}; + flex: 1; +`; + +const GoogleLoginBox = styled.div` + ${flex()}; + flex-shrink: 0; + border: 2px solid ${(props) => props.theme.color.googleColor}; + border-radius: 5px; + width: 25rem; + height: 50px; + cursor: pointer; +`; + +const GoogleText = styled.span` + color: ${(props) => props.theme.color.googleColor}; + font-weight: 800; + font-size: 1.2rem; + margin-left: 0.8rem; +`; + const LoginBox: React.FC = () => { const dispatch = useDispatch(); @@ -91,6 +141,21 @@ const LoginBox: React.FC = () => { dispatch(loginRequest({ email, pw })); }; + const handleGoogleOAuthSuccess = async (res: any) => { + const { data, status } = await authService.signupWithGoogleOAuth({ + accessToken: res.accessToken, + }); + const { accessToken, refreshToken, user } = data; + if (status === 200) { + localStorage.setItem('accessToken', accessToken); + localStorage.setItem('refreshToken', refreshToken); + localStorage.setItem('userId', user.id); + dispatch( + loginSuccess({ accessToken, refreshToken, userId: user.id ? Number(user.id) : null }), + ); + } + }; + useEffect(() => { if (loginState.err?.response?.status === 401) { setLoginFailed(true); @@ -99,8 +164,28 @@ const LoginBox: React.FC = () => { return ( + 로그인 + 로그인하려면 사용하는 Google 계정이나 이메일 주소로 계속해 주세요. + ( + + + Google로 계속 + + )} + onSuccess={handleGoogleOAuthSuccess} + onFailure={(res: any) => { + alert('로그인 실패'); + }} + /> + + + 또는 + +
- 로그인