Skip to content

Commit

Permalink
소셜 로그인 기능 추가 (구글, 카카오) (#163)
Browse files Browse the repository at this point in the history
  • Loading branch information
ars-ki-00 authored Oct 6, 2024
1 parent b8701b8 commit 59979a8
Show file tree
Hide file tree
Showing 13 changed files with 140 additions and 18 deletions.
2 changes: 2 additions & 0 deletions apps/snutt-webclient/.env.dev
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions apps/snutt-webclient/.env.mock
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 2 additions & 0 deletions apps/snutt-webclient/.env.prod
Original file line number Diff line number Diff line change
@@ -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
2 changes: 2 additions & 0 deletions apps/snutt-webclient/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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"
},
Expand Down
5 changes: 4 additions & 1 deletion apps/snutt-webclient/src/App.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -180,7 +181,9 @@ export const App = () => {
/>
</BrowserRouter>
) : (
<Landing />
<GoogleOAuthProvider clientId={ENV.GOOGLE_APP_ID}>
<Landing />
</GoogleOAuthProvider>
)}
</TokenManageContext.Provider>
<ReactQueryDevtools />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 ? (
Expand All @@ -18,7 +18,8 @@ export const LayoutProfile = () => {
</ProfileText>
) : (
<ProfileText to="/mypage" data-testid="layout-my-info">
{myInfo?.type === 'success' && `${myInfo.data.localId ?? myInfo.data.facebookName}님`}
{myInfo?.type === 'success' &&
`${myInfo.data.localId ?? myInfo.data.facebookName ?? myInfo.data.email?.split('@')[0]}님`}
</ProfileText>
);
};
Expand Down
2 changes: 2 additions & 0 deletions apps/snutt-webclient/src/contexts/EnvContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<EnvContext | null>(null);
Original file line number Diff line number Diff line change
Expand Up @@ -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 };
Expand Down
2 changes: 2 additions & 0 deletions apps/snutt-webclient/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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',
};

Expand Down
57 changes: 51 additions & 6 deletions apps/snutt-webclient/src/pages/landing/landing-login/index.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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);
Expand All @@ -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 (
<Wrapper className={className}>
<h3>시작하기</h3>
Expand Down Expand Up @@ -91,10 +97,17 @@ export const LandingLogin = ({ className, onSignUp }: Props) => {
</form>
<FBLogin
appId={FACEBOOK_APP_ID}
callback={handleFacebookSignIn}
callback={(userInfo: ReactFacebookLoginInfo) => handleSocialLogin('FACEBOOK', userInfo.accessToken)}
onFailure={({ status }: ReactFacebookFailureResponse) => setErrorMessage(status || '')}
render={({ onClick }) => <FBSignInButton onClick={onClick}>facebook으로 로그인</FBSignInButton>}
/>
<KakaoLogin
token={KAKAO_APP_ID}
onSuccess={({ response }) => handleSocialLogin('KAKAO', response.access_token)}
onFail={(e) => console.log(e)}
render={({ onClick }) => <KakaoSignInButton onClick={onClick}>카카오로 로그인</KakaoSignInButton>}
/>
<GoogleSignInButton onClick={() => googleLogin()}>google로 로그인</GoogleSignInButton>
<EtcWrapper>
<FindWrapper>
<OtherButton data-testid="login-find-id" onClick={() => setFindIdDialogOpen(true)}>
Expand Down Expand Up @@ -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);
}
`;
26 changes: 21 additions & 5 deletions apps/snutt-webclient/src/usecases/authService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<void>;
Expand All @@ -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<void>;
passwordResetCheckEmail(body: { userId: string }): RepositoryResponse<{ email: string }>;
Expand All @@ -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) };
Expand Down
21 changes: 20 additions & 1 deletion packages/snutt-api/src/apis/snutt/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,31 @@ import { SuccessResponse, ErrorResponse } from '../../response';

export const getSnuttApis = ({ callWithToken, callWithoutToken }: GetApiSpecsParameter) =>
({
'POST /auth/login/facebook': ({ body }: { body: { token: string } }) =>
callWithoutToken<SuccessResponse<{ token: string; user_id: string }> | ErrorResponse<403, 4097>>({
method: 'post',
path: `/v1/auth/facebook`,
body,
}),
'POST /auth/login/google': ({ body }: { body: { token: string } }) =>
callWithoutToken<SuccessResponse<{ token: string; user_id: string }> | ErrorResponse<403, 4097>>({
method: 'post',
path: `/v1/auth/login/google`,
body,
}),
'POST /auth/login/kakao': ({ body }: { body: { token: string } }) =>
callWithoutToken<SuccessResponse<{ token: string; user_id: string }> | ErrorResponse<403, 4097>>({
method: 'post',
path: `/v1/auth/login/kakao`,
body,
}),
'POST /auth/login_fb': ({ body }: { body: { fb_id: string; fb_token: string } }) =>
callWithoutToken<SuccessResponse<{ token: string; user_id: string }> | 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<SuccessResponse<{ message: 'ok' }> | ErrorResponse<400, 12303>>({
method: 'post',
Expand Down
10 changes: 10 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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/[email protected]":
version "1.16.1"
resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.16.1.tgz#73db3c48b975eeb06d0006481bde4f5f2d17d1cd"
Expand Down Expand Up @@ -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==

[email protected]:
version "1.3.3"
resolved "https://registry.yarnpkg.com/react-native-animatable/-/react-native-animatable-1.3.3.tgz#a13a4af8258e3bb14d0a9d839917e9bb9274ec8a"
Expand Down

0 comments on commit 59979a8

Please sign in to comment.