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 0000000..1ed9c4d Binary files /dev/null and b/src/assets/lottie.webp differ diff --git a/src/components/atoms/IngredientDateTag.tsx b/src/components/atoms/IngredientDateTag.tsx index 4813060..dc11385 100644 --- a/src/components/atoms/IngredientDateTag.tsx +++ b/src/components/atoms/IngredientDateTag.tsx @@ -11,7 +11,7 @@ const IngredientDateTag: React.FC = ({ 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;