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"