From 01cacc03043294bd74de3794024368196d48fd40 Mon Sep 17 00:00:00 2001 From: "Ahyeon, Jung" <75254185+a-honey@users.noreply.github.com> Date: Sat, 10 Feb 2024 19:49:36 +0900 Subject: [PATCH] =?UTF-8?q?MARA-9=20=EC=86=8C=EC=85=9C=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84=20(#16)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: 로그인 이미지 추가 * feat: 로그인 페이지 마크업 추가 * chore: 환경변수 .gitignore 추가 * feat: 카카오 로그인 코드 받아오기 * feat: 로그인 페이지 이동 HOC * feat: 구글 로그인 URL 추가 * feat: 전역 QueryClientProvider 추가 * feat: 카카오 로그인 api 요청 Network Error * feat: 식자재 불러오기 api 추가 Network Error * fix: 쿼리 키 분리 * feat: 기본 useBaseQuery 추가 * chore: 프로필 경고메시지 atom 분리 * chore: 워크플로우 환경변수 추가 --- .github/workflows/workflow-pr.yml | 8 +++ .github/workflows/workflow.yml | 8 +++ .gitignore | 1 + package.json | 1 + src/api/login/getToken.ts | 31 +++++++++ src/assets/images/img_login_google.svg | 8 +++ src/assets/images/img_login_kakao.svg | 11 ++++ src/assets/images/img_login_monsters.svg | 63 +++++++++++++++++++ src/components/atoms/ExclamationAlertSpan.tsx | 23 +++++++ src/components/atoms/index.ts | 1 + src/components/templates/withLogin.tsx | 28 +++++++++ src/hooks/queries/fridge/index.ts | 1 + .../queries/fridge/useGetIngredientList.ts | 9 +++ src/hooks/queries/queryKeys.ts | 5 ++ src/hooks/queries/useBaseQuery.ts | 19 ++++++ src/pages/_app.tsx | 28 ++++++--- src/pages/fridge/index.tsx | 4 ++ src/pages/login/index.tsx | 61 ++++++++++++++++++ src/pages/mypage/profile/index.tsx | 10 ++- src/types/fridge/index.d.ts | 4 ++ yarn.lock | 12 ++++ 21 files changed, 322 insertions(+), 14 deletions(-) create mode 100644 src/api/login/getToken.ts create mode 100644 src/assets/images/img_login_google.svg create mode 100644 src/assets/images/img_login_kakao.svg create mode 100644 src/assets/images/img_login_monsters.svg create mode 100644 src/components/atoms/ExclamationAlertSpan.tsx create mode 100644 src/components/templates/withLogin.tsx create mode 100644 src/hooks/queries/fridge/index.ts create mode 100644 src/hooks/queries/fridge/useGetIngredientList.ts create mode 100644 src/hooks/queries/queryKeys.ts create mode 100644 src/hooks/queries/useBaseQuery.ts create mode 100644 src/pages/login/index.tsx create mode 100644 src/types/fridge/index.d.ts diff --git a/.github/workflows/workflow-pr.yml b/.github/workflows/workflow-pr.yml index 0f73331..bdb7386 100644 --- a/.github/workflows/workflow-pr.yml +++ b/.github/workflows/workflow-pr.yml @@ -26,5 +26,13 @@ jobs: - name: Install Dependencies run: yarn install --frozen-lockfile + # 환경 변수 추가 + - name: Set Environment Variables + run: | + echo "NEXT_PUBLIC_KAKAO_API_KEY=${{ secrets.NEXT_PUBLIC_KAKAO_API_KEY }}" >> .env + echo "NEXT_PUBLIC_KAKAO_REDIRECT_URI=${{ secrets.NEXT_PUBLIC_KAKAO_REDIRECT_URI }}" >> .env + echo "NEXT_PUBLIC_GOOGLE_API_KEY=${{ secrets.NEXT_PUBLIC_GOOGLE_API_KEY }}" >> .env + echo "NEXT_PUBLIC_GOOGLE_REDIRECT_URI=${{ secrets.NEXT_PUBLIC_GOOGLE_REDIRECT_URI }}" >> .env + - name: Build run: yarn build diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 40b845e..599122f 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -29,6 +29,14 @@ jobs: - name: Install Dependencies run: yarn install --frozen-lockfile + # 환경 변수 추가 + - name: Set Environment Variables + run: | + echo "NEXT_PUBLIC_KAKAO_API_KEY=${{ secrets.NEXT_PUBLIC_KAKAO_API_KEY }}" >> .env + echo "NEXT_PUBLIC_KAKAO_REDIRECT_URI=${{ secrets.NEXT_PUBLIC_KAKAO_REDIRECT_URI }}" >> .env + echo "NEXT_PUBLIC_GOOGLE_API_KEY=${{ secrets.NEXT_PUBLIC_GOOGLE_API_KEY }}" >> .env + echo "NEXT_PUBLIC_GOOGLE_REDIRECT_URI=${{ secrets.NEXT_PUBLIC_GOOGLE_REDIRECT_URI }}" >> .env + - name: Build run: yarn build diff --git a/.gitignore b/.gitignore index fd3dbb5..00bba9b 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,7 @@ yarn-error.log* # local env files .env*.local +.env # vercel .vercel diff --git a/package.json b/package.json index cb988cd..319d0ef 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "@commitlint/config-conventional": "^18.4.3", "@emotion/react": "^11.11.3", "@emotion/styled": "^11.11.0", + "@tanstack/react-query": "^5.18.1", "@typescript-eslint/parser": "^6.19.0", "axios": "^1.6.5", "dayjs": "^1.11.10", diff --git a/src/api/login/getToken.ts b/src/api/login/getToken.ts new file mode 100644 index 0000000..9a752f5 --- /dev/null +++ b/src/api/login/getToken.ts @@ -0,0 +1,31 @@ +import axiosInstance from '../axiosInstance'; + +interface LoginResponseType { + data: { accessToken: string; refreshToken: string; email: string }; +} + +const fetchKaKao = async (code: string): Promise => + await axiosInstance.get(`/users/kakao-login?code=${code}`); + +export const getKaKaoToken: (code: string) => Promise = async (code) => { + fetchKaKao(code) + .then((response: LoginResponseType) => { + localStorage.setItem('token', response.data.accessToken); + }) + .catch((error) => { + console.error(error); + }); +}; + +const fetchGoogle = async (code: string): Promise => + await axiosInstance.get(`/users/google-login?code=${code}`); + +export const getGoogleToken: (code: string) => Promise = async (code) => { + fetchGoogle(code) + .then((response: LoginResponseType) => { + localStorage.setItem('token', response.data.accessToken); + }) + .catch((error) => { + console.error(error); + }); +}; diff --git a/src/assets/images/img_login_google.svg b/src/assets/images/img_login_google.svg new file mode 100644 index 0000000..373a445 --- /dev/null +++ b/src/assets/images/img_login_google.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/assets/images/img_login_kakao.svg b/src/assets/images/img_login_kakao.svg new file mode 100644 index 0000000..507950a --- /dev/null +++ b/src/assets/images/img_login_kakao.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/assets/images/img_login_monsters.svg b/src/assets/images/img_login_monsters.svg new file mode 100644 index 0000000..9af49a3 --- /dev/null +++ b/src/assets/images/img_login_monsters.svg @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/components/atoms/ExclamationAlertSpan.tsx b/src/components/atoms/ExclamationAlertSpan.tsx new file mode 100644 index 0000000..4260a5b --- /dev/null +++ b/src/components/atoms/ExclamationAlertSpan.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import { ExclamationIcon } from '@/assets/icons'; + +interface AlertMessageProps { + message: string; + className?: string; +} + +const ExclamationAlertSpan: React.FC = ({ + message, + className, +}) => { + return ( + + + {message} + + ); +}; + +export default ExclamationAlertSpan; diff --git a/src/components/atoms/index.ts b/src/components/atoms/index.ts index c3e68bf..cedd6a3 100644 --- a/src/components/atoms/index.ts +++ b/src/components/atoms/index.ts @@ -15,3 +15,4 @@ export { default as SortButton } from './SortButton'; export { default as RadioButtonField } from './RadioButtonField'; export { default as Input } from './Input'; export { default as MiniButton } from './MiniButton'; +export { default as ExclamationAlertSpan } from './ExclamationAlertSpan'; diff --git a/src/components/templates/withLogin.tsx b/src/components/templates/withLogin.tsx new file mode 100644 index 0000000..1ad69e5 --- /dev/null +++ b/src/components/templates/withLogin.tsx @@ -0,0 +1,28 @@ +import React, { useEffect } from 'react'; +import { useRouter } from 'next/router'; + +const withLogin = (InnerComponent: React.FC) => { + return () => { + const router = useRouter(); + const token = localStorage.getItem('token'); + + const redirectToLogin: () => Promise = async () => { + if (!token) { + alert('로그인이 필요합니다.'); + try { + await router.push('/login'); + } catch (error) { + console.error('로그인 체크 에러', error); + } + } + }; + + useEffect(() => { + void redirectToLogin(); + }, [token]); + + return token ? : null; + }; +}; + +export default withLogin; diff --git a/src/hooks/queries/fridge/index.ts b/src/hooks/queries/fridge/index.ts new file mode 100644 index 0000000..8c602e7 --- /dev/null +++ b/src/hooks/queries/fridge/index.ts @@ -0,0 +1 @@ +export { default as useGetIngredientList } from './useGetIngredientList'; diff --git a/src/hooks/queries/fridge/useGetIngredientList.ts b/src/hooks/queries/fridge/useGetIngredientList.ts new file mode 100644 index 0000000..d3e126e --- /dev/null +++ b/src/hooks/queries/fridge/useGetIngredientList.ts @@ -0,0 +1,9 @@ +import type { IngredientType } from '@/types/fridge'; +import { queryKeys } from '../queryKeys'; +import { useBaseQuery } from '../useBaseQuery'; + +const useGetIngredientList = () => { + return useBaseQuery(queryKeys.INGREDIENT(), '/ingrs'); +}; + +export default useGetIngredientList; diff --git a/src/hooks/queries/queryKeys.ts b/src/hooks/queries/queryKeys.ts new file mode 100644 index 0000000..d4896fc --- /dev/null +++ b/src/hooks/queries/queryKeys.ts @@ -0,0 +1,5 @@ +export const queryKeys = { + INGREDIENT: (id?: number) => ['ingredient', id] as const, +} as const; + +export type QueryKeys = (typeof queryKeys)[keyof typeof queryKeys]; diff --git a/src/hooks/queries/useBaseQuery.ts b/src/hooks/queries/useBaseQuery.ts new file mode 100644 index 0000000..c3cf378 --- /dev/null +++ b/src/hooks/queries/useBaseQuery.ts @@ -0,0 +1,19 @@ +import axiosInstance from '@/api/axiosInstance'; +import { useQuery } from '@tanstack/react-query'; + +export const fetchData = async (url: string) => { + const response = await axiosInstance.get<{ data: T }>(url); + return response.data; +}; + +export const useBaseQuery = (queryKey: any, url: string) => { + return useQuery({ + queryKey, + queryFn: async () => + await fetchData(url) + .then((res) => res.data) + .catch((error) => { + console.error(error); + }), + }); +}; diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index 02f0d7c..47fa34c 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -3,6 +3,7 @@ import Layout from '@/components/templates/Layout'; import '@/styles/globals.css'; import type { AppProps } from 'next/app'; import { RecoilRoot } from 'recoil'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import 'dayjs/locale/ko'; import dayjs from 'dayjs'; dayjs.locale('ko'); @@ -13,15 +14,26 @@ const theme = extendTheme({ }, }); +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + refetchOnWindowFocus: false, + }, + }, +}); + export default function App({ Component, pageProps }: AppProps): JSX.Element { return ( - - - - - - - - + + + + + + + + + + ); } diff --git a/src/pages/fridge/index.tsx b/src/pages/fridge/index.tsx index 8b8f527..73d827c 100644 --- a/src/pages/fridge/index.tsx +++ b/src/pages/fridge/index.tsx @@ -7,12 +7,16 @@ import { } from '@/components/organisms'; import { type NextPage } from 'next'; import { useState } from 'react'; +import { useGetIngredientList } from '@/hooks/queries/fridge'; const FridgePage: NextPage = () => { const [isOpenIngredientAddModal, setIsOpenIngredientAddModal] = useState(false); const [isOpenFridgeListModal, setIsOpenFridgeListModal] = useState(false); + const data = useGetIngredientList(); + console.log('받아올 데이터', data); + const toggleIsOpenIngredientAddModal: () => void = () => { setIsOpenIngredientAddModal((prev) => !prev); }; diff --git a/src/pages/login/index.tsx b/src/pages/login/index.tsx new file mode 100644 index 0000000..946d9b7 --- /dev/null +++ b/src/pages/login/index.tsx @@ -0,0 +1,61 @@ +import MonstersImg from '@/assets/images/img_login_monsters.svg'; +import KaKaoImg from '@/assets/images/img_login_kakao.svg'; +import GoogleImg from '@/assets/images/img_login_google.svg'; +import { type NextPage } from 'next'; +import { useEffect } from 'react'; +import { getKaKaoToken } from '@/api/login/getToken'; + +const LoginPage: NextPage = () => { + const kakaoURL = `https://kauth.kakao.com/oauth/authorize?client_id=${process.env.NEXT_PUBLIC_KAKAO_API_KEY}&redirect_uri=${process.env.NEXT_PUBLIC_KAKAO_REDIRECT_URI}&response_type=code`; + const googleURL = `https://accounts.google.com/o/oauth2/v2/auth?client_id=${process.env.NEXT_PUBLIC_GOOGLE_API_KEY}&redirect_uri=${process.env.NEXT_PUBLIC_GOOGLE_REDIRECT_URI}&response_type=code&scope=email&access_type=offline`; + + const handleKaKaoClick: () => void = () => { + window.location.href = `${kakaoURL}&type=kakao`; + }; + + const handleGoogleClick: () => void = () => { + window.location.href = `${googleURL}&type=google`; + }; + + useEffect(() => { + const fetchData = async (): Promise => { + const urlParams = new URLSearchParams(window.location.search); + const code = urlParams.get('code'); + // const type = urlParams.get('type'); + // 구글 추가시 타입 redirect_uri 변경 + + if (code) { + await getKaKaoToken(code); + } + }; + + // eslint-disable-next-line @typescript-eslint/no-floating-promises + fetchData(); + }, []); + + return ( +
+
+ +
+ 냉장고 관리, 얼른 시작해봐요 +
+
로고
+
+
+
+
+
SNS 계정으로 로그인
+
+
+
+ + +
+
+
+ ); +}; +export default LoginPage; diff --git a/src/pages/mypage/profile/index.tsx b/src/pages/mypage/profile/index.tsx index 5f84358..423f798 100644 --- a/src/pages/mypage/profile/index.tsx +++ b/src/pages/mypage/profile/index.tsx @@ -1,9 +1,8 @@ import { type NextPage } from 'next'; import Image from 'next/image'; import ProfileImg from '@/assets/profile.png'; -import { Button } from '@/components/atoms'; +import { Button, ExclamationAlertSpan } from '@/components/atoms'; import React, { useCallback, useState } from 'react'; -import { ExclamationIcon } from '@/assets/icons'; import Header from '@/components/organisms/Header'; import { debounceFunction } from '@/utils/debounceUtil'; @@ -70,10 +69,9 @@ const FriendsListPage: NextPage = () => { 사용가능한 닉네임입니다. ) : ( -
- - 중복되는 닉네임이에요. 다시 작성해주세요. -
+ ))}
diff --git a/src/types/fridge/index.d.ts b/src/types/fridge/index.d.ts new file mode 100644 index 0000000..dce6cc7 --- /dev/null +++ b/src/types/fridge/index.d.ts @@ -0,0 +1,4 @@ +export interface IngredientType { + name: string; + category: string; +} diff --git a/yarn.lock b/yarn.lock index 53be205..bd2ca0c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3966,6 +3966,18 @@ resolved "https://registry.yarnpkg.com/@swc/types/-/types-0.1.5.tgz#043b731d4f56a79b4897a3de1af35e75d56bc63a" integrity sha512-myfUej5naTBWnqOCc/MdVOLVjXUXtIA+NpDrDBKJtLLg2shUjBu3cZmB/85RyitKc55+lUUyl7oRfLOvkr2hsw== +"@tanstack/query-core@5.18.1": + version "5.18.1" + resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-5.18.1.tgz#b653ee354b7f4712d53565ccc5c6d8fb83ec866c" + integrity sha512-fYhrG7bHgSNbnkIJF2R4VUXb4lF7EBiQjKkDc5wOlB7usdQOIN4LxxHpDxyE3qjqIst1WBGvDtL48T0sHJGKCw== + +"@tanstack/react-query@^5.18.1": + version "5.18.1" + resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-5.18.1.tgz#fd4e7b87260e82c5277355ad64f0e431a9302e02" + integrity sha512-PdI07BbsahZ+04PxSuDQsQvBWe008eWFk/YYWzt8fvzt2sALUM0TpAJa/DFpqa7+SSo7j1EQR6Jx6znXNHyaXw== + dependencies: + "@tanstack/query-core" "5.18.1" + "@testing-library/dom@^9.3.1": version "9.3.4" resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-9.3.4.tgz#50696ec28376926fec0a1bf87d9dbac5e27f60ce"