diff --git a/.github/workflows/workflow-pr.yml b/.github/workflows/workflow-pr.yml index bdb7386..e7c9c9a 100644 --- a/.github/workflows/workflow-pr.yml +++ b/.github/workflows/workflow-pr.yml @@ -29,6 +29,7 @@ jobs: # 환경 변수 추가 - name: Set Environment Variables run: | + echo "NEXT_PUBLIC_BASE_URI=${{ secrets.NEXT_PUBLIC_BASE_URI }}" >> .env 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 diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1 @@ +{} diff --git a/env.d.ts b/env.d.ts new file mode 100644 index 0000000..f9fe038 --- /dev/null +++ b/env.d.ts @@ -0,0 +1,9 @@ +declare namespace NodeJS { + interface ProcessEnv { + NEXT_PUBLIC_BASE_URI: string; + NEXT_PUBLIC_KAKAO_API_KEY: string; + NEXT_PUBLIC_KAKAO_REDIRECT_URI: string; + NEXT_PUBLIC_GOOGLE_API_KEY: string; + NEXT_PUBLIC_GOOGLE_REDIRECT_URI: string; + } +} diff --git a/package.json b/package.json index 319d0ef..916dcef 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "next": "14.0.3", "react": "^18", "react-dom": "^18", + "react-error-boundary": "^4.0.12", "recoil": "^0.7.7" }, "devDependencies": { diff --git a/src/api/axiosInstance.ts b/src/api/axiosInstance.ts index 7700f30..dd0fbbc 100644 --- a/src/api/axiosInstance.ts +++ b/src/api/axiosInstance.ts @@ -7,7 +7,7 @@ export interface ApiResponseDTO { } const axiosInstance = axios.create({ - baseURL: 'https://localhost:3000', + baseURL: process.env.NEXT_PUBLIC_BASE_URI, timeout: 5000, headers: { 'Content-Type': 'application/json', diff --git a/src/api/login/getToken.ts b/src/api/login/getToken.ts deleted file mode 100644 index 9a752f5..0000000 --- a/src/api/login/getToken.ts +++ /dev/null @@ -1,31 +0,0 @@ -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/logos/text_logo_l.svg b/src/assets/logos/text_logo_l.svg new file mode 100644 index 0000000..f4e7d4b --- /dev/null +++ b/src/assets/logos/text_logo_l.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/assets/logos/text_logo_m.svg b/src/assets/logos/text_logo_m.svg new file mode 100644 index 0000000..dc2dfcf --- /dev/null +++ b/src/assets/logos/text_logo_m.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/assets/lottie.gif b/src/assets/lottie.gif new file mode 100644 index 0000000..1957240 Binary files /dev/null and b/src/assets/lottie.gif differ diff --git a/src/components/atoms/ModalBottom.tsx b/src/components/atoms/ModalBottom.tsx deleted file mode 100644 index 961bbd0..0000000 --- a/src/components/atoms/ModalBottom.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import React from 'react'; - -interface ModalBoxProps { - children: React.ReactNode; - blackClickHandler: () => void; -} - -const ModalBox: React.FC = ({ children, blackClickHandler }) => { - const handleWhiteContentClick: (e: React.MouseEvent) => void = (e) => { - if (e.target === e.currentTarget) { - blackClickHandler(); - } - e.stopPropagation(); - }; - - return ( -
-
-
-
-
- {children} -
-
- ); -}; - -export default ModalBox; diff --git a/src/components/atoms/ModalContainer.tsx b/src/components/atoms/ModalContainer.tsx new file mode 100644 index 0000000..123a11c --- /dev/null +++ b/src/components/atoms/ModalContainer.tsx @@ -0,0 +1,16 @@ +import React from 'react'; + +interface ModalBoxProps { + children: React.ReactNode; +} + +const ModalContainer: React.FC = ({ children }) => { + return ( +
+
+ {children} +
+ ); +}; + +export default ModalContainer; diff --git a/src/components/atoms/index.ts b/src/components/atoms/index.ts index cedd6a3..c7206d7 100644 --- a/src/components/atoms/index.ts +++ b/src/components/atoms/index.ts @@ -4,7 +4,7 @@ export { default as Container } from './Container'; export { default as GrayBox } from './GrayBox'; export { default as IngredientDateTag } from './IngredientDateTag'; export { default as BorderTab } from './BorderTab'; -export { default as ModalBottom } from './ModalBottom'; +export { default as ModalContainer } from './ModalContainer'; export { default as ModalCenter } from './ModalCenter'; export { default as Toggle } from './Toggle'; export { default as ToastMessage } from './ToastMessage'; diff --git a/src/components/molecules/FriendsFridgeItem.tsx b/src/components/molecules/FriendsFridgeItem.tsx index 3854a08..936df9e 100644 --- a/src/components/molecules/FriendsFridgeItem.tsx +++ b/src/components/molecules/FriendsFridgeItem.tsx @@ -16,12 +16,20 @@ const FriendsFridgeItem: React.FC = ({ linkTo, }) => { return ( -
+
- {`누군가의 + {`누군가의
-
+
{name}
냉장고 저장 목록 {ingredientCount}개 @@ -31,7 +39,7 @@ const FriendsFridgeItem: React.FC = ({ -
+ ); }; diff --git a/src/components/organisms/FridgeListModal.tsx b/src/components/organisms/FridgeListModal.tsx index 048326a..d09deaa 100644 --- a/src/components/organisms/FridgeListModal.tsx +++ b/src/components/organisms/FridgeListModal.tsx @@ -1,4 +1,4 @@ -import { Button, ModalBottom } from '../atoms'; +import { Button, ModalContainer } from '../atoms'; import { PlusIcon, TrashcanIcon } from '@/assets/icons'; import React, { useState } from 'react'; @@ -6,13 +6,12 @@ import { FridgeListItem } from '../molecules'; const FridgeListModal: React.FC<{ isMyFridgeList?: boolean; - toggleIsOpenFridgeListModal: () => void; -}> = ({ toggleIsOpenFridgeListModal, isMyFridgeList }) => { +}> = ({ isMyFridgeList }) => { const [currentFridgeName, setCurrentFridgeName] = useState('기본 냉장고'); const FRIDGE_NAME_LIST = ['기본 냉장고', '김치 냉장고', '주류 냉장고']; return ( - +
냉장고 목록
@@ -41,7 +40,7 @@ const FridgeListModal: React.FC<{
- + ); }; diff --git a/src/components/organisms/Header.tsx b/src/components/organisms/Header.tsx index 00313ed..6a5ad7b 100644 --- a/src/components/organisms/Header.tsx +++ b/src/components/organisms/Header.tsx @@ -32,7 +32,7 @@ const Header: React.FC<{ return (
{headerLeft ?? ( )} -
-

- {typeof headerTitle === 'string' ? headerTitle : ''} -

-
+ {!headerLeft && ( +
+

+ {typeof headerTitle === 'string' ? headerTitle : ''} +

+
+ )} {headerRight ??
}
); diff --git a/src/components/organisms/IngredientAddModal.tsx b/src/components/organisms/IngredientAddModal.tsx index cb2f6cd..d416610 100644 --- a/src/components/organisms/IngredientAddModal.tsx +++ b/src/components/organisms/IngredientAddModal.tsx @@ -1,11 +1,12 @@ import { BoxIcon, CalendarIcon, FreezerIcon, MemoIcon } from '@/assets/icons'; -import { Button, ModalBottom, Toggle } from '@/components/atoms'; +import { Button, Toggle } from '@/components/atoms'; import { Counter, IngredientAddItemContainer } from '../molecules'; import React, { useState } from 'react'; import { AppleIcon } from '../atoms/IngredientIcons'; import useCount from '@/hooks/useCount'; import useToast from '@/hooks/useToast'; +import ModalContainer from '../atoms/ModalContainer'; const IngredientAddModal: React.FC<{ toggleIsOpenIngredientAddModal: () => void; @@ -27,7 +28,7 @@ const IngredientAddModal: React.FC<{ }; return ( - +
@@ -88,7 +89,7 @@ const IngredientAddModal: React.FC<{ onClick={handleSubmit} />
- + ); }; diff --git a/src/components/organisms/OrderListModal.tsx b/src/components/organisms/OrderListModal.tsx index f9a8d7e..e60af25 100644 --- a/src/components/organisms/OrderListModal.tsx +++ b/src/components/organisms/OrderListModal.tsx @@ -1,15 +1,14 @@ -import { Button, ModalBottom, Radio } from '../atoms'; +import { Button, ModalContainer, Radio } from '../atoms'; import React from 'react'; const OrderListModal: React.FC<{ currentOrder: string; - toggleIsOpenOrderListModal: () => void; -}> = ({ toggleIsOpenOrderListModal, currentOrder }) => { +}> = ({ currentOrder }) => { const ORDER_LIST = ['등록순', '이름순']; return ( - +
{ORDER_LIST.map((order) => (
@@ -19,7 +18,7 @@ const OrderListModal: React.FC<{ ))}
+
+ ); +}; + +export default ErrorFallback; diff --git a/src/components/templates/SuspenseFallback.tsx b/src/components/templates/SuspenseFallback.tsx new file mode 100644 index 0000000..da0a170 --- /dev/null +++ b/src/components/templates/SuspenseFallback.tsx @@ -0,0 +1,15 @@ +import Image from 'next/image'; +import React from 'react'; +import LottieGif from '@/assets/lottie.gif'; + +const SuspenseFallback: React.FC = () => { + return ( +
+
+ 로딩중 +
+
+ ); +}; + +export default SuspenseFallback; diff --git a/src/components/templates/index.ts b/src/components/templates/index.ts index c946b91..b1a7b9e 100644 --- a/src/components/templates/index.ts +++ b/src/components/templates/index.ts @@ -1,3 +1,5 @@ export { default as Layout } from './Layout'; export { default as FriendListTemplate } from './FriendListTemplate'; export { default as AddFriendTemplate } from './AddFriendTemplate'; +export { default as ErrorFallback } from './ErrorFallback'; +export { default as SuspenseFallback } from './SuspenseFallback'; diff --git a/src/components/templates/withLogin.tsx b/src/components/templates/withLogin.tsx index 1ad69e5..a722b8c 100644 --- a/src/components/templates/withLogin.tsx +++ b/src/components/templates/withLogin.tsx @@ -1,14 +1,16 @@ import React, { useEffect } from 'react'; import { useRouter } from 'next/router'; +import useToast from '@/hooks/useToast'; const withLogin = (InnerComponent: React.FC) => { return () => { const router = useRouter(); const token = localStorage.getItem('token'); + const { showToast } = useToast(); const redirectToLogin: () => Promise = async () => { if (!token) { - alert('로그인이 필요합니다.'); + showToast('로그인이 필요합니다.', 'info'); try { await router.push('/login'); } catch (error) { diff --git a/src/hooks/queries/fridge/useGetIngredientList.ts b/src/hooks/queries/fridge/useGetIngredientList.ts index d3e126e..c98760a 100644 --- a/src/hooks/queries/fridge/useGetIngredientList.ts +++ b/src/hooks/queries/fridge/useGetIngredientList.ts @@ -3,6 +3,8 @@ import { queryKeys } from '../queryKeys'; import { useBaseQuery } from '../useBaseQuery'; const useGetIngredientList = () => { + // const testApiEndpoint = 'https://jsonplaceholder.typicode.com/todos'; + return useBaseQuery(queryKeys.INGREDIENT(), '/ingrs'); }; diff --git a/src/hooks/queries/login/index.ts b/src/hooks/queries/login/index.ts new file mode 100644 index 0000000..cd9fc0e --- /dev/null +++ b/src/hooks/queries/login/index.ts @@ -0,0 +1 @@ +export { default as useGetKakaoToken } from './useGetKakaoToken'; diff --git a/src/hooks/queries/login/useGetKakaoToken.ts b/src/hooks/queries/login/useGetKakaoToken.ts new file mode 100644 index 0000000..8b1d9d3 --- /dev/null +++ b/src/hooks/queries/login/useGetKakaoToken.ts @@ -0,0 +1,14 @@ +import { queryKeys } from '../queryKeys'; +import { useBaseQuery } from '../useBaseQuery'; + +const useGetKakaoToken = (code: string | null = '') => { + const { data } = useBaseQuery<{ data: { accessToken: string } }>( + queryKeys.KAKAO(), + `/users/kakao-login?code=${code}`, + ); + if (data) { + localStorage.setItem('token', data.data.accessToken); + } +}; + +export default useGetKakaoToken; diff --git a/src/hooks/queries/queryKeys.ts b/src/hooks/queries/queryKeys.ts index d4896fc..670e47a 100644 --- a/src/hooks/queries/queryKeys.ts +++ b/src/hooks/queries/queryKeys.ts @@ -1,5 +1,6 @@ export const queryKeys = { - INGREDIENT: (id?: number) => ['ingredient', id] as const, + INGREDIENT: (id?: number) => (id ? ['ingredient', id] : ['ingredient']), + KAKAO: () => ['kakao'], } as const; export type QueryKeys = (typeof queryKeys)[keyof typeof queryKeys]; diff --git a/src/hooks/queries/useBaseQuery.ts b/src/hooks/queries/useBaseQuery.ts index c3cf378..61eb4ae 100644 --- a/src/hooks/queries/useBaseQuery.ts +++ b/src/hooks/queries/useBaseQuery.ts @@ -1,5 +1,5 @@ import axiosInstance from '@/api/axiosInstance'; -import { useQuery } from '@tanstack/react-query'; +import { useSuspenseQuery } from '@tanstack/react-query'; export const fetchData = async (url: string) => { const response = await axiosInstance.get<{ data: T }>(url); @@ -7,13 +7,8 @@ export const fetchData = async (url: string) => { }; export const useBaseQuery = (queryKey: any, url: string) => { - return useQuery({ + return useSuspenseQuery({ queryKey, - queryFn: async () => - await fetchData(url) - .then((res) => res.data) - .catch((error) => { - console.error(error); - }), + queryFn: async () => await fetchData(url).then((res) => res.data), }); }; diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index 47fa34c..3f8cb95 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -3,9 +3,16 @@ 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 { + QueryClient, + QueryClientProvider, + QueryErrorResetBoundary, +} from '@tanstack/react-query'; +import React from 'react'; import 'dayjs/locale/ko'; import dayjs from 'dayjs'; +import { ErrorBoundary } from 'react-error-boundary'; +import { ErrorFallback, SuspenseFallback } from '@/components/templates'; dayjs.locale('ko'); const theme = extendTheme({ @@ -26,14 +33,22 @@ const queryClient = new QueryClient({ export default function App({ Component, pageProps }: AppProps): JSX.Element { return ( - - - - - - - - + + {({ reset }) => ( + + }> + + + + + + + + + + + )} + ); } diff --git a/src/pages/fridge/index.tsx b/src/pages/fridge/index.tsx index 73d827c..4b9423e 100644 --- a/src/pages/fridge/index.tsx +++ b/src/pages/fridge/index.tsx @@ -6,44 +6,80 @@ import { IngredientAddModal, } from '@/components/organisms'; import { type NextPage } from 'next'; -import { useState } from 'react'; -import { useGetIngredientList } from '@/hooks/queries/fridge'; +import { + Modal, + ModalOverlay, + ModalBody, + ModalContent, + useDisclosure, +} from '@chakra-ui/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 { + isOpen: isOpenIngredientAddModal, + onOpen: onOpenIngredientAddModal, + onClose: onCloseIngredientAddModal, + } = useDisclosure(); - const toggleIsOpenIngredientAddModal: () => void = () => { - setIsOpenIngredientAddModal((prev) => !prev); - }; - - const toggleIsOpenFridgeListModal: () => void = () => { - setIsOpenFridgeListModal((prev) => !prev); - }; + const { + isOpen: isOpenFridgeListModal, + onOpen: onOpenFridgeListModal, + onClose: onCloseFridgeListModal, + } = useDisclosure(); + // const data = useGetIngredientList(); + // console.log('받아올 데이터', data); return ( <> - {isOpenFridgeListModal && ( - - )} - - {isOpenIngredientAddModal && ( - - )} + + + + + + + + + + + + + + + +
diff --git a/src/pages/friend/[id]/index.tsx b/src/pages/friend/[id]/index.tsx index a20b89a..727e4fa 100644 --- a/src/pages/friend/[id]/index.tsx +++ b/src/pages/friend/[id]/index.tsx @@ -5,29 +5,46 @@ import { } from '@/components/organisms'; import Header from '@/components/organisms/Header'; import { type NextPage } from 'next'; -import { useState } from 'react'; - +import { + Modal, + ModalOverlay, + ModalBody, + ModalContent, + useDisclosure, +} from '@chakra-ui/react'; const FriendIdPage: NextPage = () => { - const [isOpenFridgeListModal, setIsOpenFridgeListModal] = useState(false); - - const toggleIsOpenFridgeListModal: () => void = () => { - setIsOpenFridgeListModal((prev) => !prev); - }; + const { + isOpen: isOpenFridgeListModal, + onOpen: onOpenFridgeListModal, + onClose: onCloseFridgeListModal, + } = useDisclosure(); return ( <> - {isOpenFridgeListModal && ( - - )} + + + + + + + +
- +
diff --git a/src/pages/friends/index.tsx b/src/pages/friends/index.tsx index c3c22a1..c314a56 100644 --- a/src/pages/friends/index.tsx +++ b/src/pages/friends/index.tsx @@ -4,30 +4,50 @@ import { OrderListModal, } from '@/components/organisms'; import Header from '@/components/organisms/Header'; +import { + Modal, + ModalBody, + ModalContent, + ModalOverlay, + useDisclosure, +} from '@chakra-ui/react'; import { type NextPage } from 'next'; -import { useState } from 'react'; const FriendsPage: NextPage = () => { - const [isOpenOrderListModal, setIsOpenOrderListModal] = useState(false); - - const toggleIsOpenOrderListModal: () => void = () => { - setIsOpenOrderListModal((prev) => !prev); - }; + const { + isOpen: isOpenOrderListModal, + onOpen: onOpenOrderListModal, + onClose: onCloseOrderListModal, + } = useDisclosure(); return ( <> - {isOpenOrderListModal && ( - - )} + + + + + + + +
diff --git a/src/pages/home/index.tsx b/src/pages/home/index.tsx index 4b4f364..a3180ad 100644 --- a/src/pages/home/index.tsx +++ b/src/pages/home/index.tsx @@ -1,5 +1,6 @@ import { type NextPage } from 'next'; import MyFridgeImg from '@/assets/images/img_home_my.svg'; +import LogoTextImg from '@/assets/logos/text_logo_m.svg'; import FriendsFridgeImg from '@/assets/images/img_home_friend.svg'; import { GreenArrowButton } from '@/components/atoms'; import { NearExpirationWarnBox, SvgAndTextBox } from '@/components/molecules'; @@ -14,7 +15,7 @@ const Home: NextPage = () => {
} + headerLeft={} headerRight={
diff --git a/src/pages/login/index.tsx b/src/pages/login/index.tsx index 946d9b7..59d3cc3 100644 --- a/src/pages/login/index.tsx +++ b/src/pages/login/index.tsx @@ -1,9 +1,9 @@ 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 LogoTextImg from '@/assets/logos/text_logo_l.svg'; import { type NextPage } from 'next'; -import { useEffect } from 'react'; -import { getKaKaoToken } from '@/api/login/getToken'; +import { useGetKakaoToken } from '@/hooks/queries/login'; 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`; @@ -17,21 +17,15 @@ const LoginPage: NextPage = () => { 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 변경 + const urlParams = + typeof window !== 'undefined' + ? new URLSearchParams(window.location.search) + : null; + const code = urlParams?.get('code'); - if (code) { - await getKaKaoToken(code); - } - }; - - // eslint-disable-next-line @typescript-eslint/no-floating-promises - fetchData(); - }, []); + if (code) { + useGetKakaoToken(code); + } return (
{
냉장고 관리, 얼른 시작해봐요
-
로고
+
@@ -51,8 +45,8 @@ const LoginPage: NextPage = () => {
- +
diff --git a/yarn.lock b/yarn.lock index bd2ca0c..198fa33 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10097,6 +10097,13 @@ react-element-to-jsx-string@^15.0.0: is-plain-object "5.0.0" react-is "18.1.0" +react-error-boundary@^4.0.12: + version "4.0.12" + resolved "https://registry.yarnpkg.com/react-error-boundary/-/react-error-boundary-4.0.12.tgz#59f8f1dbc53bbbb34fc384c8db7cf4082cb63e2c" + integrity sha512-kJdxdEYlb7CPC1A0SeUY38cHpjuu6UkvzKiAmqmOFL21VRfMhOcWxTCBgLVCO0VEMh9JhFNcVaXlV4/BTpiwOA== + dependencies: + "@babel/runtime" "^7.12.5" + react-fast-compare@3.2.2: version "3.2.2" resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.2.tgz#929a97a532304ce9fee4bcae44234f1ce2c21d49"