diff --git a/.github/workflows/s3-develop-deploy.yml b/.github/workflows/s3-develop-deploy.yml index abeeb178..a0f2d42a 100644 --- a/.github/workflows/s3-develop-deploy.yml +++ b/.github/workflows/s3-develop-deploy.yml @@ -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 diff --git a/src/app/api/fetcher.ts b/src/app/api/fetcher.ts index 6358acf1..ddcb3fa5 100644 --- a/src/app/api/fetcher.ts +++ b/src/app/api/fetcher.ts @@ -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(); }; }; diff --git a/src/app/api/login.ts b/src/app/api/login.ts new file mode 100644 index 00000000..14a02bb5 --- /dev/null +++ b/src/app/api/login.ts @@ -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 }; diff --git a/src/app/oauth2/code/google/page.tsx b/src/app/oauth2/code/google/page.tsx new file mode 100644 index 00000000..ebb3c020 --- /dev/null +++ b/src/app/oauth2/code/google/page.tsx @@ -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
; +}; + +export default Page; diff --git a/src/app/providers.tsx b/src/app/providers.tsx index 1e494dcf..523e91d9 100644 --- a/src/app/providers.tsx +++ b/src/app/providers.tsx @@ -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 {children}; + return ( + + {children} + + ); }; export default Providers; diff --git a/src/atom.ts b/src/atom.ts new file mode 100644 index 00000000..d7dec039 --- /dev/null +++ b/src/atom.ts @@ -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, +}); diff --git a/src/containers/main/GoogleLoginButton/index.tsx b/src/containers/main/GoogleLoginButton/index.tsx index 21e53736..674a609a 100644 --- a/src/containers/main/GoogleLoginButton/index.tsx +++ b/src/containers/main/GoogleLoginButton/index.tsx @@ -1,13 +1,24 @@ 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 ; + const user = useAtomValue(userAtom); + + if (user.isLogin) { + return ; } return (