From 59979a871170b7952af74214010104900d95b8d9 Mon Sep 17 00:00:00 2001 From: KiWan KIM Date: Sun, 6 Oct 2024 17:46:00 +0900 Subject: [PATCH] =?UTF-8?q?=EC=86=8C=EC=85=9C=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80=20(?= =?UTF-8?q?=EA=B5=AC=EA=B8=80,=20=EC=B9=B4=EC=B9=B4=EC=98=A4)=20(#163)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/snutt-webclient/.env.dev | 2 + apps/snutt-webclient/.env.mock | 1 + apps/snutt-webclient/.env.prod | 2 + apps/snutt-webclient/package.json | 2 + apps/snutt-webclient/src/App.tsx | 5 +- .../layout/layout-profile/index.tsx | 5 +- .../src/contexts/EnvContext.ts | 2 + .../implAuthSnuttApiRepository.ts | 23 +++++++- apps/snutt-webclient/src/main.tsx | 2 + .../src/pages/landing/landing-login/index.tsx | 57 +++++++++++++++++-- .../src/usecases/authService.ts | 26 +++++++-- packages/snutt-api/src/apis/snutt/index.ts | 21 ++++++- yarn.lock | 10 ++++ 13 files changed, 140 insertions(+), 18 deletions(-) diff --git a/apps/snutt-webclient/.env.dev b/apps/snutt-webclient/.env.dev index 67d2290..a266880 100644 --- a/apps/snutt-webclient/.env.dev +++ b/apps/snutt-webclient/.env.dev @@ -1,2 +1,4 @@ VITE_BASE_URL=https://snutt-api-dev.wafflestudio.com VITE_FACEBOOK_APP_ID=1635364463444351 +VITE_GOOGLE_APP_ID=472833977397-62l8mgakgi7ql8m654s7ptrlbn5f1pf6.apps.googleusercontent.com +VITE_KAKAO_APP_ID=b026df5da6ce29f735085608f2c3e260 \ No newline at end of file diff --git a/apps/snutt-webclient/.env.mock b/apps/snutt-webclient/.env.mock index 2cc172c..0f6954c 100644 --- a/apps/snutt-webclient/.env.mock +++ b/apps/snutt-webclient/.env.mock @@ -2,3 +2,4 @@ VITE_BASE_URL= VITE_API_KEY=mock VITE_FACEBOOK_APP_ID=mock VITE_TRUFFLE_API_KEY=mock +VITE_GOOGLE_APP_ID=mock diff --git a/apps/snutt-webclient/.env.prod b/apps/snutt-webclient/.env.prod index ddd9320..744f950 100644 --- a/apps/snutt-webclient/.env.prod +++ b/apps/snutt-webclient/.env.prod @@ -1,2 +1,4 @@ VITE_BASE_URL=https://snutt-api.wafflestudio.com VITE_FACEBOOK_APP_ID=1784457425210381 +VITE_GOOGLE_APP_ID=460308559718-06ghaa0k3jp8hd6kgr51u0nutpfl1j5c.apps.googleusercontent.com +VITE_KAKAO_APP_ID=3cfe5f66ae27a7a40db966aa22979790 \ No newline at end of file diff --git a/apps/snutt-webclient/package.json b/apps/snutt-webclient/package.json index d7bf422..ac384fa 100644 --- a/apps/snutt-webclient/package.json +++ b/apps/snutt-webclient/package.json @@ -16,6 +16,7 @@ "lint": "eslint" }, "dependencies": { + "@react-oauth/google": "^0.12.1", "@sf/snutt-api": "*", "@tanstack/react-query": "5.36.2", "@tanstack/react-query-devtools": "5.36.2", @@ -24,6 +25,7 @@ "react": "18.3.1", "react-dom": "18.3.1", "react-facebook-login": "4.1.1", + "react-kakao-login": "^2.1.1", "react-router-dom": "6.23.1", "styled-components": "6.1.11" }, diff --git a/apps/snutt-webclient/src/App.tsx b/apps/snutt-webclient/src/App.tsx index 9a0a6ab..a91a78b 100644 --- a/apps/snutt-webclient/src/App.tsx +++ b/apps/snutt-webclient/src/App.tsx @@ -1,3 +1,4 @@ +import { GoogleOAuthProvider } from '@react-oauth/google'; import { implSnuttApi } from '@sf/snutt-api'; import { QueryClient, QueryClientProvider, useQuery } from '@tanstack/react-query'; import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; @@ -180,7 +181,9 @@ export const App = () => { /> ) : ( - + + + )} diff --git a/apps/snutt-webclient/src/components/layout/layout-profile/index.tsx b/apps/snutt-webclient/src/components/layout/layout-profile/index.tsx index 71a78b1..e3615d3 100644 --- a/apps/snutt-webclient/src/components/layout/layout-profile/index.tsx +++ b/apps/snutt-webclient/src/components/layout/layout-profile/index.tsx @@ -9,7 +9,7 @@ import { useGuardContext } from '@/hooks/useGuardContext'; export const LayoutProfile = () => { const { data: myInfo } = useMyInfo(); - const isTempUser = myInfo && myInfo.type === 'success' && !myInfo.data.localId && !myInfo.data.facebookName; + const isTempUser = myInfo && myInfo.type === 'success' && !myInfo.data.email && !myInfo.data.facebookName; const isLoginButton = isTempUser; return isLoginButton ? ( @@ -18,7 +18,8 @@ export const LayoutProfile = () => { ) : ( - {myInfo?.type === 'success' && `${myInfo.data.localId ?? myInfo.data.facebookName}님`} + {myInfo?.type === 'success' && + `${myInfo.data.localId ?? myInfo.data.facebookName ?? myInfo.data.email?.split('@')[0]}님`} ); }; diff --git a/apps/snutt-webclient/src/contexts/EnvContext.ts b/apps/snutt-webclient/src/contexts/EnvContext.ts index 4354b61..5cca1f6 100644 --- a/apps/snutt-webclient/src/contexts/EnvContext.ts +++ b/apps/snutt-webclient/src/contexts/EnvContext.ts @@ -7,6 +7,8 @@ export type EnvContext = { APP_ENV: 'prod' | 'dev' | 'mock'; TRUFFLE_API_KEY: string; FACEBOOK_APP_ID: string; + GOOGLE_APP_ID: string; + KAKAO_APP_ID: string; }; export const EnvContext = createContext(null); diff --git a/apps/snutt-webclient/src/infrastructures/implAuthSnuttApiRepository.ts b/apps/snutt-webclient/src/infrastructures/implAuthSnuttApiRepository.ts index 68dc07f..d72ca3a 100644 --- a/apps/snutt-webclient/src/infrastructures/implAuthSnuttApiRepository.ts +++ b/apps/snutt-webclient/src/infrastructures/implAuthSnuttApiRepository.ts @@ -14,10 +14,27 @@ export const implAuthSnuttApiRepository = ({ else return { type: 'error', errcode: data.errcode }; }, signInWithFacebook: async (body) => { - const { status, data } = await snuttApi['POST /auth/login_fb']({ + const { status, data } = await snuttApi['POST /auth/login/facebook']({ body: { - fb_id: body.facebookId, - fb_token: body.facebookToken, + token: body.token, + }, + }); + if (status === 200) return { type: 'success', data }; + else return { type: 'error', errcode: data.errcode }; + }, + signInWithGoogle: async (body) => { + const { status, data } = await snuttApi['POST /auth/login/google']({ + body: { + token: body.token, + }, + }); + if (status === 200) return { type: 'success', data }; + else return { type: 'error', errcode: data.errcode }; + }, + signInWithKakao: async (body) => { + const { status, data } = await snuttApi['POST /auth/login/kakao']({ + body: { + token: body.token, }, }); if (status === 200) return { type: 'success', data }; diff --git a/apps/snutt-webclient/src/main.tsx b/apps/snutt-webclient/src/main.tsx index c0e5cf1..302635d 100644 --- a/apps/snutt-webclient/src/main.tsx +++ b/apps/snutt-webclient/src/main.tsx @@ -16,6 +16,8 @@ async function startApp() { API_KEY: import.meta.env.VITE_API_KEY, FACEBOOK_APP_ID: import.meta.env.VITE_FACEBOOK_APP_ID, TRUFFLE_API_KEY: import.meta.env.VITE_TRUFFLE_API_KEY, + GOOGLE_APP_ID: import.meta.env.VITE_GOOGLE_APP_ID, + KAKAO_APP_ID: import.meta.env.VITE_KAKAO_APP_ID, NODE_ENV: process.env.NODE_ENV as 'development' | 'production', }; diff --git a/apps/snutt-webclient/src/pages/landing/landing-login/index.tsx b/apps/snutt-webclient/src/pages/landing/landing-login/index.tsx index f450bb6..36082dc 100644 --- a/apps/snutt-webclient/src/pages/landing/landing-login/index.tsx +++ b/apps/snutt-webclient/src/pages/landing/landing-login/index.tsx @@ -1,6 +1,8 @@ +import { type TokenResponse, useGoogleLogin } from '@react-oauth/google'; import { useState } from 'react'; import { type ReactFacebookFailureResponse, type ReactFacebookLoginInfo } from 'react-facebook-login'; import FBLogin from 'react-facebook-login/dist/facebook-login-render-props'; +import KakaoLogin from 'react-kakao-login'; import styled from 'styled-components'; import { Button } from '@/components/button'; @@ -15,7 +17,8 @@ type Props = { className?: string; onSignUp: () => void }; export const LandingLogin = ({ className, onSignUp }: Props) => { const { saveToken } = useGuardContext(TokenManageContext); - const { FACEBOOK_APP_ID } = useGuardContext(EnvContext); + const { FACEBOOK_APP_ID, KAKAO_APP_ID } = useGuardContext(EnvContext); + const [id, setId] = useState(''); const [password, setPassword] = useState(''); const [keepSignIn, setKeepSignIn] = useState(false); @@ -33,19 +36,22 @@ export const LandingLogin = ({ className, onSignUp }: Props) => { else setErrorMessage(res.message); }; - const handleFacebookSignIn = async (userInfo: ReactFacebookLoginInfo) => { + const handleSocialLogin = async (provider: 'FACEBOOK' | 'KAKAO' | 'GOOGLE', token: string) => { setErrorMessage(''); const res = await authService.signIn({ - type: 'FACEBOOK', - facebookId: userInfo.id, - facebookToken: userInfo.accessToken, + type: provider, + token: token, }); if (res.type === 'success') saveToken(res.data.token, keepSignIn); else setErrorMessage(res.message); }; + const googleLogin = useGoogleLogin({ + onSuccess: (tokenResponse: TokenResponse) => handleSocialLogin('GOOGLE', tokenResponse.access_token), + }); + return (

시작하기

@@ -91,10 +97,17 @@ export const LandingLogin = ({ className, onSignUp }: Props) => { handleSocialLogin('FACEBOOK', userInfo.accessToken)} onFailure={({ status }: ReactFacebookFailureResponse) => setErrorMessage(status || '')} render={({ onClick }) => facebook으로 로그인} /> + handleSocialLogin('KAKAO', response.access_token)} + onFail={(e) => console.log(e)} + render={({ onClick }) => 카카오로 로그인} + /> + googleLogin()}>google로 로그인 setFindIdDialogOpen(true)}> @@ -244,3 +257,35 @@ const FBSignInButton = styled(Button)` background-color: rgba(60, 93, 212, 0.1); } `; + +const KakaoSignInButton = styled(Button)` + border-radius: 21px; + border: none; + width: 100%; + margin-top: 10px; + height: 34px; + font-size: 13px; + background-color: transparent; + color: #d5b045; + border: 1px solid #d5b045; + + &:hover { + background-color: rgba(60, 93, 212, 0.1); + } +`; + +const GoogleSignInButton = styled(Button)` + border-radius: 21px; + border: none; + width: 100%; + margin-top: 10px; + height: 34px; + font-size: 13px; + background-color: transparent; + color: #6e6e6e; + border: 1px solid #6e6e6e; + + &:hover { + background-color: rgba(60, 93, 212, 0.1); + } +`; diff --git a/apps/snutt-webclient/src/usecases/authService.ts b/apps/snutt-webclient/src/usecases/authService.ts index 9fdb1ec..4685e4d 100644 --- a/apps/snutt-webclient/src/usecases/authService.ts +++ b/apps/snutt-webclient/src/usecases/authService.ts @@ -7,7 +7,9 @@ export interface AuthService { signIn( params: | { type: 'LOCAL'; id: string; password: string } - | { type: 'FACEBOOK'; facebookId: string; facebookToken: string }, + | { type: 'FACEBOOK'; token: string } + | { type: 'GOOGLE'; token: string } + | { type: 'KAKAO'; token: string }, ): UsecaseResponse<{ token: string }>; signUp(body: { id: string; password: string }): UsecaseResponse<{ token: string }>; closeAccount(_: { token: string }): UsecaseResponse; @@ -23,7 +25,9 @@ export const getAuthService = ({ }: { authRepository: { signInWithIdPassword(args: { id: string; password: string }): RepositoryResponse<{ token: string }>; - signInWithFacebook(args: { facebookId: string; facebookToken: string }): RepositoryResponse<{ token: string }>; + signInWithFacebook(args: { token: string }): RepositoryResponse<{ token: string }>; + signInWithGoogle(args: { token: string }): RepositoryResponse<{ token: string }>; + signInWithKakao(args: { token: string }): RepositoryResponse<{ token: string }>; signUpWithIdPassword(body: { id: string; password: string }): RepositoryResponse<{ token: string }>; findId(body: { email: string }): RepositoryResponse; passwordResetCheckEmail(body: { userId: string }): RepositoryResponse<{ email: string }>; @@ -50,9 +54,21 @@ export const getAuthService = ({ else return { type: 'error', message: getErrorMessage(data) }; }, signIn: async (params) => { - const data = await (params.type === 'LOCAL' - ? authRepository.signInWithIdPassword({ id: params.id, password: params.password }) - : authRepository.signInWithFacebook({ facebookId: params.facebookId, facebookToken: params.facebookToken })); + const data = await (async () => { + switch (params.type) { + case 'FACEBOOK': + return await authRepository.signInWithFacebook({ + token: params.token, + }); + case 'GOOGLE': + return await authRepository.signInWithGoogle({ token: params.token }); + case 'KAKAO': + return await authRepository.signInWithKakao({ token: params.token }); + case 'LOCAL': + default: + return await authRepository.signInWithIdPassword({ id: params.id, password: params.password }); + } + })(); if (data.type === 'success') return { type: 'success', data: data.data }; else return { type: 'error', message: getErrorMessage(data) }; diff --git a/packages/snutt-api/src/apis/snutt/index.ts b/packages/snutt-api/src/apis/snutt/index.ts index 3709ec2..a24c04c 100644 --- a/packages/snutt-api/src/apis/snutt/index.ts +++ b/packages/snutt-api/src/apis/snutt/index.ts @@ -3,12 +3,31 @@ import { SuccessResponse, ErrorResponse } from '../../response'; export const getSnuttApis = ({ callWithToken, callWithoutToken }: GetApiSpecsParameter) => ({ + 'POST /auth/login/facebook': ({ body }: { body: { token: string } }) => + callWithoutToken | ErrorResponse<403, 4097>>({ + method: 'post', + path: `/v1/auth/facebook`, + body, + }), + 'POST /auth/login/google': ({ body }: { body: { token: string } }) => + callWithoutToken | ErrorResponse<403, 4097>>({ + method: 'post', + path: `/v1/auth/login/google`, + body, + }), + 'POST /auth/login/kakao': ({ body }: { body: { token: string } }) => + callWithoutToken | ErrorResponse<403, 4097>>({ + method: 'post', + path: `/v1/auth/login/kakao`, + body, + }), 'POST /auth/login_fb': ({ body }: { body: { fb_id: string; fb_token: string } }) => callWithoutToken | ErrorResponse<403, 4097>>({ method: 'post', - path: `/auth/login_fb`, + path: `/v1/auth/login_fb`, body, }), + 'POST /v1/auth/id/find': ({ body }: { body: { email: string } }) => callWithoutToken | ErrorResponse<400, 12303>>({ method: 'post', diff --git a/yarn.lock b/yarn.lock index a3cb4bb..14b1798 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2725,6 +2725,11 @@ dependencies: nanoid "^3.1.23" +"@react-oauth/google@^0.12.1": + version "0.12.1" + resolved "https://registry.yarnpkg.com/@react-oauth/google/-/google-0.12.1.tgz#b76432c3a525e9afe076f787d2ded003fcc1bee9" + integrity sha512-qagsy22t+7UdkYAiT5ZhfM4StXi9PPNvw0zuwNmabrWyMKddczMtBIOARflbaIj+wHiQjnMAsZmzsUYuXeyoSg== + "@remix-run/router@1.16.1": version "1.16.1" resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.16.1.tgz#73db3c48b975eeb06d0006481bde4f5f2d17d1cd" @@ -9150,6 +9155,11 @@ react-is@^18.2.0: resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== +react-kakao-login@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/react-kakao-login/-/react-kakao-login-2.1.1.tgz#00e9f534d18ce500e31c02ef1d751a1c93dfefbd" + integrity sha512-t9htk41/i0zUY7q92mtqdqVZZ018BPi1DgbSVVrPCmuMKhZGJOnZ9OfaKLVPu3sn8QXbJc3dPwqKOiElpb44hQ== + react-native-animatable@1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/react-native-animatable/-/react-native-animatable-1.3.3.tgz#a13a4af8258e3bb14d0a9d839917e9bb9274ec8a"