Skip to content

Commit

Permalink
Feature/#75 로그인 기능 (#210)
Browse files Browse the repository at this point in the history
* feat : 로그인 기능

#75

* fix : fetcher 내부에서 에러 발생 시에도 response return

#75

* fix : redirect 대신 router 사용

#75

* feat : git action에 .env 생성 스크립트 추가

#75

* chore : 테스트용 스크립트 제거

#75
  • Loading branch information
jasper200207 authored May 14, 2024
1 parent 1f65f22 commit 5249ed1
Show file tree
Hide file tree
Showing 7 changed files with 110 additions and 22 deletions.
8 changes: 8 additions & 0 deletions .github/workflows/s3-develop-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@ jobs:
- name: Install Dependencies
run: npm ci

- name: Create .env File
env:
PREFIX: 'NEXT_PUBLIC_'
SECRETS: ${{ toJson(secrets) }}
run: |
touch .env && \
jq -r 'to_entries|map("\(.key)=\(.value|tostring)")|.[]' <<< "$SECRETS" | grep $PREFIX > .env
- name: Build
run: npm run build

Expand Down
43 changes: 25 additions & 18 deletions src/app/api/fetcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,24 +31,31 @@ export const fetcher = (options?: FetcherOptions) => {
const { baseUrl, headers, interceptors } = { ...defaultOptions, ...options };

return async (...props: FetchProps) => {
let [url, config] = props;
if (interceptors?.request) {
[url, config] = await interceptors.request(props);
}
if (config?.body && typeof config.body === 'object') {
config.body = JSON.stringify(config.body);
}
const fullUrl = url.startsWith('http') ? url : `${baseUrl}${url}`;
let response = await fetch(fullUrl, {
...(config as RequestInit),
headers: {
...headers,
...(config?.headers || {}),
},
});
if (interceptors?.response) {
response = await interceptors.response(response);
try {
let [url, config] = props;
if (interceptors?.request) {
[url, config] = await interceptors.request(props);
}
if (config?.body && typeof config.body === 'object') {
config.body = JSON.stringify(config.body);
}
const fullUrl = url.startsWith('http') ? url : `${baseUrl}${url}`;
let response = await fetch(fullUrl, {
...(config as RequestInit),
headers: {
...headers,
...(config?.headers || {}),
},
});
if (interceptors?.response) {
response = await interceptors.response(response);
}
return await response.json();
} catch (error) {
let message = '';
if (error instanceof Error) message = error.message;
else message = String(error);
return { ok: false, body: { message } };
}
return response.json();
};
};
14 changes: 14 additions & 0 deletions src/app/api/login.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/* eslint-disable import/prefer-default-export */
import { fetcher } from '@/app/api/fetcher';

const loginFetcher = fetcher();

const postGoogleLoginFetch = () => {
return async (code: string | null) =>
loginFetcher('/login/google', {
method: 'POST',
body: { code },
});
};

export { postGoogleLoginFetch };
32 changes: 32 additions & 0 deletions src/app/oauth2/code/google/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
'use client';

import { useSetAtom } from 'jotai';
import { useRouter } from 'next/navigation';

import { postGoogleLoginFetch } from '@/app/api/login';
import { userAtom } from '@/atom';

const Page = ({ searchParams }: { searchParams: { code: string } }) => {
const { code } = searchParams;
const router = useRouter();

const setUser = useSetAtom(userAtom);

const login = postGoogleLoginFetch();
login(code).then((res) => {
if (res?.ok) {
setUser({
memberId: res.body?.memberId,
token: res.body?.token,
isLogin: true,
});
} else {
alert(res.body.message);
}
router.replace('/');
});

return <div />;
};

export default Page;
9 changes: 8 additions & 1 deletion src/app/providers.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
'use client';

import { ChakraProvider } from '@chakra-ui/react';
import { Provider, createStore } from 'jotai';

import theme from '@/theme';

const store = createStore();

const Providers = ({ children }: { children: React.ReactNode }) => {
return <ChakraProvider theme={theme}>{children}</ChakraProvider>;
return (
<ChakraProvider theme={theme}>
<Provider store={store}>{children}</Provider>
</ChakraProvider>
);
};

export default Providers;
8 changes: 8 additions & 0 deletions src/atom.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/* eslint-disable import/prefer-default-export */
import { atomWithStorage } from 'jotai/utils';

export const userAtom = atomWithStorage('user', {
memberId: 0,
token: '',
isLogin: false,
});
18 changes: 15 additions & 3 deletions src/containers/main/GoogleLoginButton/index.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,32 @@
import { Image, Button, Box } from '@chakra-ui/react';
import { useAtomValue } from 'jotai';

const isLogin = true;
import { userAtom } from '@/atom';

const GOOGLE_LOGIN_URL =
'https://accounts.google.com/o/oauth2/v2/auth?' +
`client_id=${process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID}&` +
`redirect_uri=${process.env.NEXT_PUBLIC_GOOGLE_REDIRECT_URL}&` +
`response_type=code&` +
`scope=${process.env.NEXT_PUBLIC_GOOGLE_SCOPE}`;

const GoogleLoginButton = () => {
if (isLogin) {
return <Box h="16" />;
const user = useAtomValue(userAtom);

if (user.isLogin) {
return <Box h={{ base: '8', lg: '10', '2xl': '14' }} />;
}
return (
<Button
as="a"
justifyContent="start"
w="fit-content"
h={{ base: '8', lg: '10', '2xl': '14' }}
p="0"
_hover={{ opacity: '0.8' }}
_active={{ opacity: '0.8' }}
bgColor="transparent"
href={GOOGLE_LOGIN_URL}
>
<Image h="100%" alt="google_sign_in" src="/images/google_sign_in.png" />
</Button>
Expand Down

0 comments on commit 5249ed1

Please sign in to comment.