From 31107f4fc58afac74a5b413fa00a9929d283ec22 Mon Sep 17 00:00:00 2001 From: "Ahyeon, Jung" <75254185+a-honey@users.noreply.github.com> Date: Wed, 28 Feb 2024 11:47:15 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EC=8B=9D=EC=9E=90=EC=9E=AC=20=EB=AC=B4?= =?UTF-8?q?=ED=95=9C=EC=8A=A4=ED=81=AC=EB=A1=A4,=20=EB=A6=AC=ED=94=84?= =?UTF-8?q?=EB=A0=88=EC=8B=9C=ED=86=A0=ED=81=B0,=20=ED=94=84=EB=A1=9C?= =?UTF-8?q?=ED=95=84=20=EB=B3=80=EA=B2=BD,=20=EB=A1=9C=ED=8B=B0=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20(#34)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 친구 목록 EmptyBox 추가 * fix: 기타 * feat: 식자재 무한스크롤 * feat: 토큰 확인 후 로그인 이동 * feat: 마이페이지 나눔 내역 * feat: 프로필 수정 * feat: 식자재 이름 변경 * feat: 식자재 직접 추가하기 * fix: 날짜 수정 * feat: 구글 로그인 추가 에러 * fix: 디데이 조건 수정 * fix: 수정 배경 삭제 * chore: 기타 * fix: 무한스크롤 미반영으로 캐시 삭제 * feat: 리프레시 토큰으로 엑세스 토큰 받아오기 cors 에러 추정 * feat: 로티 변경 * feat: 스크롤 중 로티 아톰 * fix: 빌드 수정 --- package.json | 1 - src/api/axiosInstance.ts | 18 +- src/assets/lottie.json | 317 ------------------ src/assets/lottie.webp | Bin 0 -> 10930 bytes src/components/atoms/IngredientDateTag.tsx | 4 +- src/components/atoms/Lottie.tsx | 13 +- src/components/molecules/FridgeListItem.tsx | 1 + .../molecules/IngredientItemBox.tsx | 2 +- src/components/organisms/FridgeBoard.tsx | 71 ++-- .../organisms/FriendsFridgeList.tsx | 24 +- src/components/organisms/IngredientModal.tsx | 72 +++- src/hooks/queries/fridge/index.ts | 1 + .../queries/fridge/useDeleteIngredientById.ts | 2 + .../queries/fridge/useGetFridgeContentById.ts | 46 +-- src/hooks/queries/fridge/usePostFridge.ts | 1 - src/hooks/queries/fridge/usePostIngredient.ts | 2 +- .../queries/fridge/usePostNewIngredient.ts | 17 + .../queries/fridge/usePutIngredientById.ts | 7 +- src/hooks/queries/login/index.ts | 1 + src/hooks/queries/login/useGetGoogleToken.ts | 23 ++ src/hooks/queries/mypage/index.ts | 2 + src/hooks/queries/mypage/useGetMe.ts | 4 +- src/hooks/queries/mypage/useGetMyShares.ts | 20 ++ src/hooks/queries/mypage/usePutMe.ts | 25 ++ src/hooks/queries/queryKeys.ts | 6 + src/hooks/queries/useBaseInfiniteQuery.ts | 7 +- src/hooks/queries/useBaseMutation.ts | 4 +- src/pages/_app.tsx | 1 + src/pages/fridge/add/index.tsx | 76 ++++- src/pages/fridge/index.tsx | 17 +- src/pages/friend/[id]/index.tsx | 2 +- src/pages/login/index.tsx | 22 +- src/pages/mypage/account/index.tsx | 8 + src/pages/mypage/index.tsx | 7 +- src/pages/mypage/profile/index.tsx | 45 ++- src/pages/mypage/share/index.tsx | 119 +++++++ 36 files changed, 532 insertions(+), 456 deletions(-) delete mode 100644 src/assets/lottie.json create mode 100644 src/assets/lottie.webp create mode 100644 src/hooks/queries/fridge/usePostNewIngredient.ts create mode 100644 src/hooks/queries/login/useGetGoogleToken.ts create mode 100644 src/hooks/queries/mypage/useGetMyShares.ts create mode 100644 src/hooks/queries/mypage/usePutMe.ts create mode 100644 src/pages/mypage/share/index.tsx diff --git a/package.json b/package.json index 245878a..e81db38 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,6 @@ "eslint-plugin-react": "^7.33.2", "framer-motion": "^11.0.3", "lodash": "^4.17.21", - "lottie-react": "^2.4.0", "next": "14.0.3", "react": "^18", "react-dom": "^18", diff --git a/src/api/axiosInstance.ts b/src/api/axiosInstance.ts index f1798bd..376f605 100644 --- a/src/api/axiosInstance.ts +++ b/src/api/axiosInstance.ts @@ -27,7 +27,6 @@ axiosInstance.interceptors.response.use( const originalRequest = error.config; if (error.response?.status === 401 && !originalRequest._retry) { - /* originalRequest._retry = true; const refreshToken = @@ -36,22 +35,21 @@ axiosInstance.interceptors.response.use( : null; try { - const refreshResponse = await axios.post('/users/kakao-login', { - refreshToken, - }); - - if (typeof window !== 'undefined') { - localStorage.setItem('accessToken', refreshResponse.data.accessToken); + originalRequest.headers['Refresh-Token'] = refreshToken; + const res = await axiosInstance(originalRequest); + const newAccessToken = res.headers['new-access-token']; + if (newAccessToken) { + localStorage.setItem('accessToken', newAccessToken); } - originalRequest.headers.Authorization = `Bearer ${refreshResponse.data.accessToken}`; + originalRequest.headers.Authorization = `Bearer ${newAccessToken}`; return await axiosInstance(originalRequest); } catch (refreshError) { console.error('Error refreshing token:', refreshError); throw refreshError; } - */ - window.location.href = '/login'; + + // window.location.href = '/login'; } return await Promise.reject(error); diff --git a/src/assets/lottie.json b/src/assets/lottie.json deleted file mode 100644 index 53297b3..0000000 --- a/src/assets/lottie.json +++ /dev/null @@ -1,317 +0,0 @@ -{ - "v": "5.5.8", - "fr": 29.9700012207031, - "ip": 2.00000008146167, - "op": 42.0000017106951, - "w": 500, - "h": 300, - "nm": "컴포지션 2", - "ddd": 0, - "assets": [], - "layers": [ - { - "ddd": 0, - "ind": 1, - "ty": 4, - "nm": "모양 레이어 3", - "sr": 1, - "ks": { - "o": { "a": 0, "k": 100, "ix": 11 }, - "r": { "a": 0, "k": 0, "ix": 10 }, - "p": { - "a": 1, - "k": [ - { - "i": { "x": 0.833, "y": 0.833 }, - "o": { "x": 0.133, "y": 0.133 }, - "t": 0, - "s": [350, 220, 0], - "to": [0, 0, 0], - "ti": [0, 0, 0] - }, - { - "i": { "x": 0.382, "y": 1 }, - "o": { "x": 0.63, "y": 0 }, - "t": 12, - "s": [350, 220, 0], - "to": [0, -17.301, 0], - "ti": [0, 0, 0] - }, - { - "i": { "x": 0.09, "y": 1 }, - "o": { "x": 0.294, "y": 0 }, - "t": 27, - "s": [350, 116.195, 0], - "to": [0, 0, 0], - "ti": [0, -17.301, 0] - }, - { - "i": { "x": 0.861, "y": 0.861 }, - "o": { "x": 0.167, "y": 0.167 }, - "t": 42.5, - "s": [350, 220, 0], - "to": [0, 0, 0], - "ti": [0, 0, 0] - }, - { "t": 47.0000019143492, "s": [350, 220, 0] } - ], - "ix": 2 - }, - "a": { "a": 0, "k": [0, 0, 0], "ix": 1 }, - "s": { "a": 0, "k": [98, 98, 100], "ix": 6 } - }, - "ao": 0, - "shapes": [ - { - "ty": "gr", - "it": [ - { - "d": 1, - "ty": "el", - "s": { "a": 0, "k": [45.688, 45.688], "ix": 2 }, - "p": { "a": 0, "k": [0, 0], "ix": 3 }, - "nm": "타원 패스 1", - "mn": "ADBE Vector Shape - Ellipse", - "hd": false - }, - { - "ty": "fl", - "c": { "a": 0, "k": [0.3216, 0.7725, 0.651, 1], "ix": 4 }, - "o": { "a": 0, "k": 100, "ix": 5 }, - "r": 1, - "bm": 0, - "nm": "ì¹  1", - "mn": "ADBE Vector Graphic - Fill", - "hd": false - }, - { - "ty": "tr", - "p": { "a": 0, "k": [1, 0], "ix": 2 }, - "a": { "a": 0, "k": [0, 0], "ix": 1 }, - "s": { "a": 0, "k": [100, 100], "ix": 3 }, - "r": { "a": 0, "k": 0, "ix": 6 }, - "o": { "a": 0, "k": 100, "ix": 7 }, - "sk": { "a": 0, "k": 0, "ix": 4 }, - "sa": { "a": 0, "k": 0, "ix": 5 }, - "nm": "변형" - } - ], - "nm": "타원 1", - "np": 3, - "cix": 2, - "bm": 0, - "ix": 1, - "mn": "ADBE Vector Group", - "hd": false - } - ], - "ip": 0, - "op": 60.0000024438501, - "st": 0, - "bm": 0 - }, - { - "ddd": 0, - "ind": 2, - "ty": 4, - "nm": "모양 레이어 1", - "sr": 1, - "ks": { - "o": { "a": 0, "k": 100, "ix": 11 }, - "r": { "a": 0, "k": 0, "ix": 10 }, - "p": { - "a": 1, - "k": [ - { - "i": { "x": 0.833, "y": 0.833 }, - "o": { "x": 0.133, "y": 0.133 }, - "t": 0, - "s": [250, 220, 0], - "to": [0, 0, 0], - "ti": [0, 0, 0] - }, - { - "i": { "x": 0.528, "y": 1 }, - "o": { "x": 0.698, "y": 0 }, - "t": 7, - "s": [250, 220, 0], - "to": [0, -16.134, 0], - "ti": [0, 0, 0] - }, - { - "i": { "x": 0.152, "y": 1 }, - "o": { "x": 0.284, "y": 0 }, - "t": 22, - "s": [250, 123.195, 0], - "to": [0, 0, 0], - "ti": [0, -16.134, 0] - }, - { - "i": { "x": 0.409, "y": 0.409 }, - "o": { "x": 0.167, "y": 0.167 }, - "t": 39, - "s": [250, 220, 0], - "to": [0, 0, 0], - "ti": [0, 0, 0] - }, - { "t": 47.0000019143492, "s": [250, 220, 0] } - ], - "ix": 2 - }, - "a": { "a": 0, "k": [0, 0, 0], "ix": 1 }, - "s": { "a": 0, "k": [98, 98, 100], "ix": 6 } - }, - "ao": 0, - "shapes": [ - { - "ty": "gr", - "it": [ - { - "d": 1, - "ty": "el", - "s": { "a": 0, "k": [45.688, 45.688], "ix": 2 }, - "p": { "a": 0, "k": [0, 0], "ix": 3 }, - "nm": "타원 패스 1", - "mn": "ADBE Vector Shape - Ellipse", - "hd": false - }, - { - "ty": "fl", - "c": { "a": 0, "k": [0.3216, 0.7725, 0.651, 1], "ix": 4 }, - "o": { "a": 0, "k": 100, "ix": 5 }, - "r": 1, - "bm": 0, - "nm": "ì¹  1", - "mn": "ADBE Vector Graphic - Fill", - "hd": false - }, - { - "ty": "tr", - "p": { "a": 0, "k": [1, 0], "ix": 2 }, - "a": { "a": 0, "k": [0, 0], "ix": 1 }, - "s": { "a": 0, "k": [100, 100], "ix": 3 }, - "r": { "a": 0, "k": 0, "ix": 6 }, - "o": { "a": 0, "k": 100, "ix": 7 }, - "sk": { "a": 0, "k": 0, "ix": 4 }, - "sa": { "a": 0, "k": 0, "ix": 5 }, - "nm": "변형" - } - ], - "nm": "타원 1", - "np": 3, - "cix": 2, - "bm": 0, - "ix": 1, - "mn": "ADBE Vector Group", - "hd": false - } - ], - "ip": 0, - "op": 60.0000024438501, - "st": 0, - "bm": 0 - }, - { - "ddd": 0, - "ind": 3, - "ty": 4, - "nm": "모양 레이어 2", - "sr": 1, - "ks": { - "o": { "a": 0, "k": 100, "ix": 11 }, - "r": { "a": 0, "k": 0, "ix": 10 }, - "p": { - "a": 1, - "k": [ - { - "i": { "x": 0.833, "y": 0.833 }, - "o": { "x": 0.133, "y": 0.133 }, - "t": 0, - "s": [150, 220, 0], - "to": [0, 0, 0], - "ti": [0, 0, 0] - }, - { - "i": { "x": 0.726, "y": 1 }, - "o": { "x": 0.728, "y": 0 }, - "t": 2, - "s": [150, 220, 0], - "to": [0, -12.801, 0], - "ti": [0, 0, 0] - }, - { - "i": { "x": 0.254, "y": 1 }, - "o": { "x": 0.263, "y": 0 }, - "t": 18, - "s": [150, 143.195, 0], - "to": [0, 0, 0], - "ti": [0, -12.801, 0] - }, - { - "i": { "x": 0.861, "y": 0.861 }, - "o": { "x": 0.167, "y": 0.167 }, - "t": 35, - "s": [150, 220, 0], - "to": [0, 0, 0], - "ti": [0, 0, 0] - }, - { "t": 47.0000019143492, "s": [150, 220, 0] } - ], - "ix": 2 - }, - "a": { "a": 0, "k": [0, 0, 0], "ix": 1 }, - "s": { "a": 0, "k": [98, 98, 100], "ix": 6 } - }, - "ao": 0, - "shapes": [ - { - "ty": "gr", - "it": [ - { - "d": 1, - "ty": "el", - "s": { "a": 0, "k": [45.688, 45.688], "ix": 2 }, - "p": { "a": 0, "k": [0, 0], "ix": 3 }, - "nm": "타원 패스 1", - "mn": "ADBE Vector Shape - Ellipse", - "hd": false - }, - { - "ty": "fl", - "c": { "a": 0, "k": [0.3216, 0.7725, 0.651, 1], "ix": 4 }, - "o": { "a": 0, "k": 100, "ix": 5 }, - "r": 1, - "bm": 0, - "nm": "ì¹  1", - "mn": "ADBE Vector Graphic - Fill", - "hd": false - }, - { - "ty": "tr", - "p": { "a": 0, "k": [1, 0], "ix": 2 }, - "a": { "a": 0, "k": [0, 0], "ix": 1 }, - "s": { "a": 0, "k": [100, 100], "ix": 3 }, - "r": { "a": 0, "k": 0, "ix": 6 }, - "o": { "a": 0, "k": 100, "ix": 7 }, - "sk": { "a": 0, "k": 0, "ix": 4 }, - "sa": { "a": 0, "k": 0, "ix": 5 }, - "nm": "변형" - } - ], - "nm": "타원 1", - "np": 3, - "cix": 2, - "bm": 0, - "ix": 1, - "mn": "ADBE Vector Group", - "hd": false - } - ], - "ip": 0, - "op": 60.0000024438501, - "st": 0, - "bm": 0 - } - ], - "markers": [] -} diff --git a/src/assets/lottie.webp b/src/assets/lottie.webp new file mode 100644 index 0000000000000000000000000000000000000000..1ed9c4dc110ede6cfff2b927037130de8330593b GIT binary patch literal 10930 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 pZNTZot-fO@D = ({ dDay }) => { let textDay; // dDay에 따라서 className 설정 - if (dDay <= 0) { + if (dDay < 0) { className = 'bg-gray1 text-gray6'; backgroundColor = ''; textDay = `D+${Math.abs(dDay)}`; @@ -31,7 +31,7 @@ const IngredientDateTag: React.FC = ({ dDay }) => { return (
{textDay} diff --git a/src/components/atoms/Lottie.tsx b/src/components/atoms/Lottie.tsx index ad32cf5..68ea74f 100644 --- a/src/components/atoms/Lottie.tsx +++ b/src/components/atoms/Lottie.tsx @@ -1,11 +1,16 @@ import React from 'react'; -// import Lottie from 'lottie-react'; -// import animationData from './../../assets/lottie.json'; +import animationData from './../../assets/lottie.webp'; +import Image from 'next/image'; const LottieComponent = () => { return ( - // -
로딩중
+ 로딩중 ); }; diff --git a/src/components/molecules/FridgeListItem.tsx b/src/components/molecules/FridgeListItem.tsx index dded0fe..d8e9c2b 100644 --- a/src/components/molecules/FridgeListItem.tsx +++ b/src/components/molecules/FridgeListItem.tsx @@ -41,6 +41,7 @@ const FridgeListItem: React.FC = ({
{isEditingFridgeName && id === newFridgeName.id ? ( { handleNewFridgeName(id, e.target.value); diff --git a/src/components/molecules/IngredientItemBox.tsx b/src/components/molecules/IngredientItemBox.tsx index 320a081..5ddc984 100644 --- a/src/components/molecules/IngredientItemBox.tsx +++ b/src/components/molecules/IngredientItemBox.tsx @@ -34,7 +34,7 @@ const IngredientItemBox: React.FC<{
{data?.name ?? ''}
- {`${addDate.getFullYear()}년 ${addDate.getMonth() + 1}월 ${addDate.getDay()}일 저장`} + {`${addDate.getFullYear()}년 ${addDate.getMonth() + 1}월 ${addDate.getDate()}일 저장`}
diff --git a/src/components/organisms/FridgeBoard.tsx b/src/components/organisms/FridgeBoard.tsx index cf65fbf..9813274 100644 --- a/src/components/organisms/FridgeBoard.tsx +++ b/src/components/organisms/FridgeBoard.tsx @@ -1,5 +1,5 @@ -import React, { useState } from 'react'; -import { Container } from '@/components/atoms'; +import React, { useRef, useState } from 'react'; +import { Container, Lottie } from '@/components/atoms'; import { EmptyBox, FridgeTab, IngredientItemBox } from '@/components/molecules'; import { IngredientModal } from '.'; import { @@ -10,15 +10,25 @@ import { useDisclosure, } from '@chakra-ui/react'; import { useGetFridgeContentById } from '@/hooks/queries/fridge'; +import { useObserver } from '@/hooks/useObserver'; +import { useRouter } from 'next/router'; -const FridgeBoard: React.FC<{ fridgeId: number }> = ({ fridgeId }) => { +const FridgeBoard: React.FC = () => { + const bottom = useRef(null); + const router = useRouter(); const [detailIngredientId, setDetailIngredientId] = useState(0); const [currentTabName, setCurrentTabName] = useState<'냉장' | '냉동'>('냉장'); + const { fridgeid: fridgeId } = router.query; - const data = useGetFridgeContentById( - Number(fridgeId), - currentTabName === '냉장' ? 'FREEZING' : 'REFRIGERATION', - )?.content; + const { + data: ingredients, + fetchNextPage: fetchIngredientNextPage, + isFetchingNextPage: isFetchingIngredientNextPage, + refetch: ingredientsRefetch, + } = useGetFridgeContentById({ + id: Number(fridgeId), + sort: currentTabName === '냉장' ? 'FREEZING' : 'REFRIGERATION', + }); const { isOpen: isOpenIngredientModal, @@ -35,6 +45,17 @@ const FridgeBoard: React.FC<{ fridgeId: number }> = ({ fridgeId }) => { onOpenIngredientModal(); }; + const onIntersect: IntersectionObserverCallback = ([entry]) => { + if (entry.isIntersecting) { + void fetchIngredientNextPage(); + } + }; + + useObserver({ + target: bottom, + onIntersect, + }); + return ( <> {isOpenIngredientModal && ( @@ -57,6 +78,7 @@ const FridgeBoard: React.FC<{ fridgeId: number }> = ({ fridgeId }) => { @@ -68,21 +90,26 @@ const FridgeBoard: React.FC<{ fridgeId: number }> = ({ fridgeId }) => { currentTabName={currentTabName} handleTabNameChange={handleTabNameChange} /> - {data && data.length !== 0 ? ( -
- {data.map((ingredient) => ( - - ))} -
- ) : ( -
- -
- )} +
+ {ingredients?.pages.map(({ content }) => + content && content.length > 0 ? ( + content.map((ingredient) => ( + + )) + ) : ( +
+ +
+ ), + )} + {isFetchingIngredientNextPage ? :
} +
); diff --git a/src/components/organisms/FriendsFridgeList.tsx b/src/components/organisms/FriendsFridgeList.tsx index 146fc63..22355c9 100644 --- a/src/components/organisms/FriendsFridgeList.tsx +++ b/src/components/organisms/FriendsFridgeList.tsx @@ -1,14 +1,17 @@ import { Container } from '../atoms'; import { AngleIcon } from '@/assets/icons'; -import { FriendsFridgeItem } from '../molecules'; +import { EmptyBox, FriendsFridgeItem } from '../molecules'; import React from 'react'; -import { useGetMyFriendsCount } from '@/hooks/queries/mypage'; +import { useGetCount } from '@/hooks/queries/mypage'; const FriendsFridgeList: React.FC<{ toggleIsOpenOrderListModal: () => void; }> = ({ toggleIsOpenOrderListModal }) => { - const count = useGetMyFriendsCount(); + const count = useGetCount()?.friendCount; + + const data = ['hi']; + return (
@@ -30,9 +33,18 @@ const FriendsFridgeList: React.FC<{
- - - + {data && data.length !== 0 ? ( + data.map((friend) => ( + + )) + ) : ( + + )}
diff --git a/src/components/organisms/IngredientModal.tsx b/src/components/organisms/IngredientModal.tsx index 2d8269a..9a56370 100644 --- a/src/components/organisms/IngredientModal.tsx +++ b/src/components/organisms/IngredientModal.tsx @@ -1,13 +1,14 @@ import { BoxIcon, CalendarIcon, + EditIcon, FreezerIcon, MemoIcon, TrashcanIcon, } from '@/assets/icons'; import { Button, Toggle } from '@/components/atoms'; import { Counter, IngredientAddItemContainer } from '../molecules'; -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; import useToast from '@/hooks/useToast'; import ModalContainer from '../atoms/ModalContainer'; import { @@ -20,12 +21,24 @@ import Image from 'next/image'; import type { PostIngredientBodyType } from '@/hooks/queries/fridge/usePostIngredient'; import { useRouter } from 'next/router'; import usePutIngredientById from '@/hooks/queries/fridge/usePutIngredientById'; +import axiosInstance from '@/api/axiosInstance'; +import { queryClient } from '@/pages/_app'; const IngredientModal: React.FC<{ id: number; isDetailModal?: boolean; + ingredientsRefetch?: any; + categoryImage?: string; + category?: string; toggleIsOpenIngredientModal: () => void; -}> = ({ id, toggleIsOpenIngredientModal, isDetailModal = false }) => { +}> = ({ + id, + categoryImage, + toggleIsOpenIngredientModal, + isDetailModal = false, + category, + ingredientsRefetch, +}) => { const router = useRouter(); const today = new Date(); @@ -36,6 +49,8 @@ const IngredientModal: React.FC<{ const onSuccess = () => { toggleIsOpenIngredientModal(); showToast('식자재 추가가 완료되었습니다.', 'success'); + ingredientsRefetch(); + queryClient.invalidateQueries({ queryKey: ['my_fridge'] }); }; const postIngredient = usePostIngredient( @@ -44,18 +59,22 @@ const IngredientModal: React.FC<{ name as string, ); - const data = isDetailModal - ? useGetMyIngredient(id) - : useGetIngredientById(id); + const data = + id === 0 + ? null + : isDetailModal + ? useGetMyIngredient(id) + : useGetIngredientById(id); const expirationDate = new Date(today); expirationDate.setDate(today.getDate() + (data?.expirationDays ?? 0)); + const [isEditingName, setIsEditingName] = useState(false); const [reqBody, setReqBody] = useState({ refrigeratorId: Number(fridgeid), ingredientId: id, - name: data?.name ?? '', - quantity: data?.quantity ?? 0, + name: data?.name ?? category ?? '', + quantity: data?.quantity ?? 1, location: data?.location ?? 'FREEZING', memo: '', addDate: today, @@ -69,11 +88,13 @@ const IngredientModal: React.FC<{ id, Number(fridgeid), reqBody?.location, + ingredientsRefetch, ); const putIngredient = usePutIngredientById( id, Number(fridgeid), reqBody?.location, + ingredientsRefetch, ); const [isInFreezer, setIsInFreezer] = useState( @@ -91,17 +112,50 @@ const IngredientModal: React.FC<{ }); }; + useEffect(() => { + const fetchData = async () => { + const res = await axiosInstance.post('/ingrs', { + category, + name: reqBody.name, + iconImage: categoryImage, + expirationDays: 0, + }); + + setReqBody((prev) => ({ ...prev, ingredientId: res.data.data })); + }; + if (id === 0) fetchData(); + }, []); return (
{data?.name -
{data?.name}
+ {isEditingName ? ( + { + setReqBody((prev) => ({ + ...prev, + name: e.target.value, + })); + }} + /> + ) : ( +
{reqBody.name}
+ )} +
void, ) => { const onSuccess = () => { void queryClient.invalidateQueries(); + if (fn) fn(); }; return useBaseMutation( queryKeys.MY_FRIDGE_CONTENT(fridgeId, location), diff --git a/src/hooks/queries/fridge/useGetFridgeContentById.ts b/src/hooks/queries/fridge/useGetFridgeContentById.ts index 6632beb..e59661b 100644 --- a/src/hooks/queries/fridge/useGetFridgeContentById.ts +++ b/src/hooks/queries/fridge/useGetFridgeContentById.ts @@ -1,31 +1,31 @@ -import type { IngredientDetailType } from '@/types/fridge'; import { queryKeys } from '../queryKeys'; -import { fetchData } from '../useBaseQuery'; -import { useQuery } from '@tanstack/react-query'; +import type { LocationEnum } from '@/types/common'; +import { useBaseInfiniteQuery } from '../useBaseInfiniteQuery'; interface FridgeContentType { - content: IngredientDetailType[]; + ingredientDetailId: number; + iconImage: string; + name: string; + quantity: 0; + location: LocationEnum; + memo: string; + addDate: string; + expirationDate: string; + isDeleted: true; } - -const useGetFridgeContentById = ( - id: number, - location: 'REFRIGERATION' | 'FREEZING', -) => { - // 무한스크롤 or useSuspenseQuery로 변경 해야함 - const { data } = useQuery({ - queryKey: queryKeys.MY_FRIDGE_CONTENT(id, location), - queryFn: async () => { - return await fetchData( - `/ingrs/detail/refrig/${id}?location=${location}`, - true, - ); - }, - enabled: id !== 0 && !isNaN(id), +const useGetFridgeContentById = ({ + sort, + id, +}: { + sort: LocationEnum; + id: number; +}) => { + const data = useBaseInfiniteQuery({ + queryKey: queryKeys.MY_FRIDGE_CONTENT(id, sort), + url: `/ingrs/detail/refrig/${id}`, + params: { location: sort }, }); - - if (!data?.data) return; - - return data?.data; + return data; }; export default useGetFridgeContentById; diff --git a/src/hooks/queries/fridge/usePostFridge.ts b/src/hooks/queries/fridge/usePostFridge.ts index 30f5774..131831b 100644 --- a/src/hooks/queries/fridge/usePostFridge.ts +++ b/src/hooks/queries/fridge/usePostFridge.ts @@ -8,7 +8,6 @@ interface PostFridgeBodyType { const usePostFridge = () => { const onSuccess = (data: PostFridgeBodyType) => { - console.log(data); void queryClient.invalidateQueries(); }; return useBaseMutation( diff --git a/src/hooks/queries/fridge/usePostIngredient.ts b/src/hooks/queries/fridge/usePostIngredient.ts index 71cece1..1214888 100644 --- a/src/hooks/queries/fridge/usePostIngredient.ts +++ b/src/hooks/queries/fridge/usePostIngredient.ts @@ -17,8 +17,8 @@ export interface PostIngredientBodyType { const usePostIngredient = (fn: () => void, fridgeid: string, name: string) => { const router = useRouter(); const onSuccess = () => { - fn(); void router.push(`/fridge?fridgeid=${fridgeid}&name=${name}`); + fn(); }; return useBaseMutation( queryKeys.INGREDIENTS(), diff --git a/src/hooks/queries/fridge/usePostNewIngredient.ts b/src/hooks/queries/fridge/usePostNewIngredient.ts new file mode 100644 index 0000000..0973806 --- /dev/null +++ b/src/hooks/queries/fridge/usePostNewIngredient.ts @@ -0,0 +1,17 @@ +import { queryKeys } from '../queryKeys'; +import { useBaseMutation } from '../useBaseMutation'; + +export interface PostNewIngredientBodyType { + category: string; + name: string; + iconImage: string; + expirationDays: number; +} + +const usePostNewIngredient = () => { + return useBaseMutation( + queryKeys.INGREDIENTS(), + `/ingrs`, + ); +}; +export default usePostNewIngredient; diff --git a/src/hooks/queries/fridge/usePutIngredientById.ts b/src/hooks/queries/fridge/usePutIngredientById.ts index 8be4829..b5ab0fe 100644 --- a/src/hooks/queries/fridge/usePutIngredientById.ts +++ b/src/hooks/queries/fridge/usePutIngredientById.ts @@ -17,12 +17,17 @@ const usePutIngredientById = ( id: number, fridgeId: number, location: string, + fn?: () => void, ) => { const onSuccess = () => { void queryClient.invalidateQueries(); + if (fn) fn(); }; return useBaseMutation( - queryKeys.MY_FRIDGE_CONTENT(fridgeId, location), + [ + ...queryKeys.MY_FRIDGE_CONTENT(fridgeId, location), + ...queryKeys.MY_INGREDIENT_ID(id), + ], `/ingrs/detail/${id}`, onSuccess, 'PUT', diff --git a/src/hooks/queries/login/index.ts b/src/hooks/queries/login/index.ts index ffcc97c..931ad3b 100644 --- a/src/hooks/queries/login/index.ts +++ b/src/hooks/queries/login/index.ts @@ -1,2 +1,3 @@ export { default as useGetKakaoToken } from './useGetKakaoToken'; +export { default as useGetGoogleToken } from './useGetGoogleToken'; export { default as usePostUser } from './usePostUser'; diff --git a/src/hooks/queries/login/useGetGoogleToken.ts b/src/hooks/queries/login/useGetGoogleToken.ts new file mode 100644 index 0000000..4a9d13d --- /dev/null +++ b/src/hooks/queries/login/useGetGoogleToken.ts @@ -0,0 +1,23 @@ +import { useRouter } from 'next/router'; +import { queryKeys } from '../queryKeys'; +import { useBaseQuery } from '../useBaseQuery'; + +const useGetGoogleToken = (code: string | null = '') => { + const router = useRouter(); + const { data } = useBaseQuery<{ + accessToken: string; + refreshToken: string; + googleEmail: string; + }>(queryKeys.GOOGLE(), `/users/google-login?code=${code}`, true); + + if (data?.data?.accessToken === undefined) { + void router.push(`/mypage/profile?googleEmail=${data?.data?.googleEmail}`); + } + if (data?.data) { + localStorage.setItem('accessToken', data.data.accessToken); + localStorage.setItem('refreshToken', data.data.refreshToken); + void router.push('/home'); + } +}; + +export default useGetGoogleToken; diff --git a/src/hooks/queries/mypage/index.ts b/src/hooks/queries/mypage/index.ts index cd1240b..edf7abf 100644 --- a/src/hooks/queries/mypage/index.ts +++ b/src/hooks/queries/mypage/index.ts @@ -2,3 +2,5 @@ export { default as useGetMe } from './useGetMe'; export { default as useGetMyFriendsCount } from './useGetMyFriendsCount'; export { default as useGetMyIngredientsCount } from './useGetMyIngredientsCount'; export { default as useGetCount } from './useGetCount'; +export { default as useGetMyShares } from './useGetMyShares'; +export { default as usePutMe } from './usePutMe'; diff --git a/src/hooks/queries/mypage/useGetMe.ts b/src/hooks/queries/mypage/useGetMe.ts index 1b9c47e..a578eaa 100644 --- a/src/hooks/queries/mypage/useGetMe.ts +++ b/src/hooks/queries/mypage/useGetMe.ts @@ -3,7 +3,7 @@ import { queryKeys } from '../queryKeys'; import { useBaseQuery } from '../useBaseQuery'; interface ResType { - nickName: string; + nickname: string; kakaoId: number; kakaoEmail: string; googleEmail: string | null; @@ -14,7 +14,7 @@ const useGetMe = () => { const { data } = useBaseQuery(queryKeys.ME(), `/users/me`, true); if (!data?.data) return { - nickName: '', + nickname: '', kakaoId: 0, kakaoEmail: '', googleEmail: null, diff --git a/src/hooks/queries/mypage/useGetMyShares.ts b/src/hooks/queries/mypage/useGetMyShares.ts new file mode 100644 index 0000000..9f0bae3 --- /dev/null +++ b/src/hooks/queries/mypage/useGetMyShares.ts @@ -0,0 +1,20 @@ +import type { ShareSortType, ShareStatusType } from '@/types/friendship'; + +import type { ShareData } from '@/types/share'; +import { queryKeys } from '../queryKeys'; +import { useBaseInfiniteQuery } from '../useBaseInfiniteQuery'; + +const useGetMyShares = ({ + sort, + status, +}: { + sort: ShareSortType; + status: ShareStatusType; +}) => + useBaseInfiniteQuery({ + queryKey: queryKeys.MY_SHARES(sort, status), + url: `/shares/created`, + params: { status }, + }); + +export default useGetMyShares; diff --git a/src/hooks/queries/mypage/usePutMe.ts b/src/hooks/queries/mypage/usePutMe.ts new file mode 100644 index 0000000..8429653 --- /dev/null +++ b/src/hooks/queries/mypage/usePutMe.ts @@ -0,0 +1,25 @@ +import type { ProfileEnum } from '@/types/common'; +import { queryKeys } from '../queryKeys'; +import { useBaseMutation } from '../useBaseMutation'; +import { queryClient } from '@/pages/_app'; +import { useRouter } from 'next/router'; + +export interface ProfileBodyType { + nickname: string; + profileImage: ProfileEnum; +} + +const usePutMe = () => { + const router = useRouter(); + const onSuccess = () => { + void queryClient.invalidateQueries(); + router.push('/mypage'); + }; + return useBaseMutation( + queryKeys.ME(), + `/users`, + onSuccess, + 'PUT', + ); +}; +export default usePutMe; diff --git a/src/hooks/queries/queryKeys.ts b/src/hooks/queries/queryKeys.ts index 2cbe173..a91ba14 100644 --- a/src/hooks/queries/queryKeys.ts +++ b/src/hooks/queries/queryKeys.ts @@ -21,11 +21,17 @@ export const queryKeys = { INGREDIENTS: () => ['my-ingredient'], INGREDIENTS_RECENT: () => ['my-ingredient', 'recent'], KAKAO: () => ['kakao'], + GOOGLE: () => ['google'], SHARES: (sort: ShareSortType, status: ShareStatusType) => [ 'shares', sort, status, ], + MY_SHARES: (sort: ShareSortType, status: ShareStatusType) => [ + 'my-shares', + sort, + status, + ], ME: () => ['my-info'], FRIENDSHIPS: (sort: FriendshipSortType) => ['friendship', sort], DELETE_FRIENDSHIP: () => ['deleteFriendship'], diff --git a/src/hooks/queries/useBaseInfiniteQuery.ts b/src/hooks/queries/useBaseInfiniteQuery.ts index 7757855..f6d874a 100644 --- a/src/hooks/queries/useBaseInfiniteQuery.ts +++ b/src/hooks/queries/useBaseInfiniteQuery.ts @@ -43,9 +43,12 @@ export const useBaseInfiniteQuery = ({ return useInfiniteQuery({ queryKey, - queryFn: async (context: QueryFunctionContext) => - await fetchData(context), + queryFn: async (context: QueryFunctionContext) => { + const data = await fetchData(context); + return data; + }, initialPageParam: INITIAL_PAGE_PARAM, getNextPageParam: (res) => getNextOffset(res), + staleTime: 0, }); }; diff --git a/src/hooks/queries/useBaseMutation.ts b/src/hooks/queries/useBaseMutation.ts index e865ce1..9b1c25b 100644 --- a/src/hooks/queries/useBaseMutation.ts +++ b/src/hooks/queries/useBaseMutation.ts @@ -14,7 +14,7 @@ export const fetchData = async (url: string, body: T, method: string) => { export const useBaseMutation = ( mutationKey: any, url: string, - onSuccess: (any: any) => void, + onSuccess?: (any: any) => void, method: 'POST' | 'PUT' | 'DELETE' = 'POST', ) => { return useMutation({ @@ -22,7 +22,7 @@ export const useBaseMutation = ( mutationFn: async (body: T) => { const response = await fetchData(url, body, method); - onSuccess(response.data); + if (onSuccess) onSuccess(response.data); }, }); }; diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index 47f5788..9d73b61 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -25,6 +25,7 @@ export const queryClient = new QueryClient({ defaultOptions: { queries: { staleTime: 0, + gcTime: 0, retry: false, refetchOnWindowFocus: false, }, diff --git a/src/pages/fridge/add/index.tsx b/src/pages/fridge/add/index.tsx index 35faa8c..e912db3 100644 --- a/src/pages/fridge/add/index.tsx +++ b/src/pages/fridge/add/index.tsx @@ -14,7 +14,25 @@ import { import Draggable from 'react-draggable'; import type { DraggableEvent } from 'react-draggable'; +// 임시 식자재 추가 disapper +const CATEGORY_COUNT: Record = { + 야채: 11, + 과일: 6, + 고기: 4, + 수산물: 4, + 유제품: 4, + 면: 2, + 인스턴트: 5, + 반찬: 1, + '빵/디저트/과자': 1, + '음료/주류': 3, + '조미료/양념/소스': 4, + 양곡: 2, + 견과: 3, +}; const FridgePage: NextPage = () => { + const [category, setCategory] = useState(''); + const [categoryImage, setCategoryImage] = useState(''); const [ingredientId, setIngredientId] = useState(null); const { isOpen: isOpenIngredientModal, @@ -80,6 +98,8 @@ const FridgePage: NextPage = () => { > @@ -125,24 +145,44 @@ const FridgePage: NextPage = () => { >
    - {items.ingredientGroupList.map((item) => ( -
  • { - setIngredientId(item.id); - onOpenIngredientModal(); - }} - className="flex flex-col items-center" - > - {item.name} -
    {item.name}
    -
  • - ))} + {items.ingredientGroupList + .slice(0, CATEGORY_COUNT[items.category]) + .map((item) => ( +
  • { + setIngredientId(item.id); + onOpenIngredientModal(); + }} + className="flex flex-col items-center" + > + {item.name} +
    {item.name}
    +
  • + ))} +
  • { + setIngredientId(0); + setCategory(items.category); + setCategoryImage(items.ingredientGroupList[0].iconImage); + onOpenIngredientModal(); + }} + className="flex flex-col items-center" + > + {items.category} +
    직접 추가
    +
))} diff --git a/src/pages/fridge/index.tsx b/src/pages/fridge/index.tsx index 73a3e3a..2f44a6d 100644 --- a/src/pages/fridge/index.tsx +++ b/src/pages/fridge/index.tsx @@ -15,6 +15,9 @@ import { import { useGetMe } from '@/hooks/queries/mypage'; import { useRouter } from 'next/router'; import { useEffect } from 'react'; +import { EmptyBox } from '@/components/molecules'; +import { Container } from '@/components/atoms'; +import withLogin from '@/components/templates/withLogin'; const FridgePage: NextPage = () => { const router = useRouter(); @@ -24,7 +27,7 @@ const FridgePage: NextPage = () => { onClose: onCloseFridgeListModal, } = useDisclosure(); - const { nickName } = useGetMe(); + const { nickname } = useGetMe(); const { fridgeid: fridgeId } = router.query; @@ -62,14 +65,20 @@ const FridgePage: NextPage = () => { className={`flex flex-col min-h-screen p-0 pl-20 pr-20 pb-20 bg-gray1`} > - + {fridgeId ? ( + + ) : ( + + + + )}
); }; -export default FridgePage; +export default withLogin(FridgePage); diff --git a/src/pages/friend/[id]/index.tsx b/src/pages/friend/[id]/index.tsx index 412c4d7..c78fba1 100644 --- a/src/pages/friend/[id]/index.tsx +++ b/src/pages/friend/[id]/index.tsx @@ -66,7 +66,7 @@ const FriendIdPage: NextPage = () => { userName={nickname} toggleIsOpenFridgeListModal={onOpenFridgeListModal} /> - +
diff --git a/src/pages/login/index.tsx b/src/pages/login/index.tsx index 59d3cc3..72f656c 100644 --- a/src/pages/login/index.tsx +++ b/src/pages/login/index.tsx @@ -3,9 +3,12 @@ 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 { useGetKakaoToken } from '@/hooks/queries/login'; +import { useGetGoogleToken, useGetKakaoToken } from '@/hooks/queries/login'; +import { useRouter } from 'next/router'; const LoginPage: NextPage = () => { + const router = useRouter(); + 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`; @@ -17,14 +20,13 @@ const LoginPage: NextPage = () => { window.location.href = `${googleURL}&type=google`; }; - const urlParams = - typeof window !== 'undefined' - ? new URLSearchParams(window.location.search) - : null; - const code = urlParams?.get('code'); + const { code, scope } = router.query; - if (code) { - useGetKakaoToken(code); + if (code && !scope) { + useGetKakaoToken(code as string); + } + if (code && scope) { + useGetGoogleToken(code as string); } return ( @@ -41,7 +43,9 @@ const LoginPage: NextPage = () => {
-
SNS 계정으로 로그인
+
+ SNS 계정으로 로그인 +
diff --git a/src/pages/mypage/account/index.tsx b/src/pages/mypage/account/index.tsx index e8773c7..9f9e04c 100644 --- a/src/pages/mypage/account/index.tsx +++ b/src/pages/mypage/account/index.tsx @@ -1,9 +1,11 @@ import Header from '@/components/organisms/Header'; +import { useGetMe } from '@/hooks/queries/mypage'; import useLogout from '@/hooks/useLogout'; import { type NextPage } from 'next'; const FriendsListPage: NextPage = () => { const logout = useLogout(); + const data = useGetMe(); return (
@@ -11,6 +13,12 @@ const FriendsListPage: NextPage = () => {
+
+
연결된계정
+
+ {data.kakaoEmail ?? data.googleEmail ?? ''} +
+
diff --git a/src/pages/mypage/index.tsx b/src/pages/mypage/index.tsx index d4491c6..89a6827 100644 --- a/src/pages/mypage/index.tsx +++ b/src/pages/mypage/index.tsx @@ -17,6 +17,7 @@ import { } from '@/assets/icons'; import { useGetCount, useGetMe } from '@/hooks/queries/mypage'; import { returnProfileImg } from '@/utils/returnProfileImg'; +import withLogin from '@/components/templates/withLogin'; const GENERAGE_NAV_LIST = [ { @@ -34,7 +35,7 @@ const GENERAGE_NAV_LIST = [ svgComponent: , linkTo: '/mypage/friendship', }, - { name: '나눔 내역', svgComponent: , linkTo: '' }, + { name: '나눔 내역', svgComponent: , linkTo: '/mypage/share' }, ]; const ETC_NAV_LIST = [ @@ -62,7 +63,7 @@ const Mypage: NextPage = () => { /> )} - {data?.nickName ?? '닉네임을 입력해주세요.'} + {data?.nickname ?? '닉네임을 입력해주세요.'}
@@ -86,4 +87,4 @@ const Mypage: NextPage = () => { ); }; -export default Mypage; +export default withLogin(Mypage); diff --git a/src/pages/mypage/profile/index.tsx b/src/pages/mypage/profile/index.tsx index 08c431e..9d70ce1 100644 --- a/src/pages/mypage/profile/index.tsx +++ b/src/pages/mypage/profile/index.tsx @@ -6,7 +6,7 @@ import type { FormEvent } from 'react'; import Header from '@/components/organisms/Header'; import { debounceFunction } from '@/utils/debounceUtil'; import usePostUser from '@/hooks/queries/login/usePostUser'; -import { useGetMe } from '@/hooks/queries/mypage'; +import { useGetMe, usePutMe } from '@/hooks/queries/mypage'; import type { ProfileEnum } from '@/types/common'; import axiosInstance from '@/api/axiosInstance'; import { returnProfileImg } from '@/utils/returnProfileImg'; @@ -31,16 +31,18 @@ const PROFILES: Array<{ string: ProfileEnum; pointColor: string }> = [ ]; const ProfilePage: NextPage = () => { - const [selectedProfile, setSelectedProfile] = useState('BLUE'); - const [nickname, setNickname] = useState(''); - const [isNicknameAvailable, setIsNicknameAvailable] = useState(false); - const [isNicknameChecked, setIsNicknameChecked] = useState(false); - const MyInfo = useGetMe(); - if (MyInfo.nickName) { - setNickname(MyInfo.nickName); - } + const [selectedProfile, setSelectedProfile] = useState('BLUE'); + const [nickname, setNickname] = useState(MyInfo?.nickname ?? ''); + const [isNicknameAvailable, setIsNicknameAvailable] = useState< + null | boolean + >(null); + const [isNicknameChecked, setIsNicknameChecked] = useState( + null, + ); + + const putMe = usePutMe(); const postUser = usePostUser(); @@ -53,6 +55,11 @@ const ProfilePage: NextPage = () => { }; const nickNameCheckResult = async (nickName: string) => { + if (MyInfo?.nickname === nickName) { + setIsNicknameAvailable(null); + setIsNicknameChecked(null); + return; + } try { const res = await axiosInstance.get<{ message: 'string'; @@ -84,13 +91,17 @@ const ProfilePage: NextPage = () => { const kakaoId = urlParams?.get('kakaoId'); const kakaoEmail = urlParams?.get('kakaoEmail'); - postUser.mutate({ - nickName: nickname, - kakaoId: Number(kakaoId ?? MyInfo.kakaoId), - kakaoEmail: kakaoEmail ?? MyInfo.kakaoEmail, - googleEmail: null, - profileImage: selectedProfile, - }); + if (kakaoEmail && kakaoId) { + postUser.mutate({ + nickName: nickname, + kakaoId: Number(kakaoId ?? MyInfo.kakaoId), + kakaoEmail: kakaoEmail ?? MyInfo.kakaoEmail, + googleEmail: null, + profileImage: selectedProfile, + }); + } else { + putMe.mutate({ nickname, profileImage: selectedProfile }); + } }; return ( @@ -157,7 +168,7 @@ const ProfilePage: NextPage = () => {
diff --git a/src/pages/mypage/share/index.tsx b/src/pages/mypage/share/index.tsx new file mode 100644 index 0000000..7f878a8 --- /dev/null +++ b/src/pages/mypage/share/index.tsx @@ -0,0 +1,119 @@ +import { + Modal, + ModalBody, + ModalContent, + ModalOverlay, + useDisclosure, +} from '@chakra-ui/react'; +import { RadioButtonField, SortButton, TabButton } from '@/components/atoms'; +import type { ShareSortType, ShareStatusType } from '@/types/friendship'; +import type { SortLabel, TabLabel } from '@/types/common'; +import { useRef, useState } from 'react'; + +import Header from '@/components/organisms/Header'; +import type { NextPage } from 'next'; +import { type ShareData } from '@/types/share'; +import ShareListItem from '@/components/organisms/ShareListItem'; +import { SuspenseFallback } from '@/components/templates'; +import { useObserver } from '@/hooks/useObserver'; +import { useGetMyShares } from '@/hooks/queries/mypage'; + +const TABS: TabLabel[] = [ + { label: '나눔 중', value: 'SHARE_IN_PROGRESS' }, + { label: '나눔 완료', value: 'SHARE_END' }, +]; + +const SORT_TYPES: SortLabel[] = [ + { label: '전체 나눔글', value: 'registeredDate' }, + { label: '작성한 나눔글', value: 'dueDate' }, + { label: '참여한 나눔글', value: 'dueDate' }, +]; + +const MySharePage: NextPage = () => { + const [curTab, setCurTab] = useState(TABS[0]); + const [curSortType, setCurSortType] = useState(SORT_TYPES[0]); + const { isOpen, onOpen, onClose } = useDisclosure(); + const bottom = useRef(null); + const { data, fetchNextPage, isFetchingNextPage } = useGetMyShares({ + sort: curSortType.value as ShareSortType, + status: curTab.value as ShareStatusType, + }); + + const onIntersect: IntersectionObserverCallback = ([entry]) => { + if (entry.isIntersecting) { + void fetchNextPage(); + } + }; + + useObserver({ + target: bottom, + onIntersect, + }); + + return ( + <> +
+
+
+
+ {TABS.map((ele: TabLabel) => ( + { + setCurTab(ele); + }} + active={ele.value === curTab.value} + label={ele.label} + /> + ))} +
+
+
+

총 {data?.pages[0].totalElements}건

+ +
+
+ +
+ {data?.pages.map((page) => + page.content.map((ele: ShareData) => ( + + )), + )} + {isFetchingNextPage ? :
} +
+ + + + + {SORT_TYPES.map((ele: SortLabel) => ( + { + setCurSortType(ele); + onClose(); + }} + checked={ele.value === curSortType.value} + /> + ))} + + + +
+ + ); +}; +export default MySharePage;