diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..88516fe8 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,7 @@ +{ + "search.exclude": { + "**/.node_modules": true, + "**/package-lock.json": true, + "**/yarn.lock": true, + }, +} \ No newline at end of file diff --git a/README.md b/README.md index 6ed8c4ab..c037cf5a 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Make an issue or Contact with this repository leader. - Language - [TypeScript](https://github.com/typescript-cheatsheets/react) - Server State Management Library - - [React-Query](https://react-query.tanstack.com/overview) + - [@tanstack/react-query](https://tanstack.com/query/v5) - Routing Library - [React Router v6](https://reactrouter.com/docs/en/v6/getting-started/tutorial) - Linting Library diff --git a/package.json b/package.json index 8e326ac9..15104357 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "version": "0.1.0", "private": true, "dependencies": { + "@tanstack/react-query": "^5.8.1", "@testing-library/jest-dom": "^5.14.1", "@testing-library/react": "^13.0.0", "@testing-library/user-event": "^13.2.1", @@ -21,7 +22,6 @@ "react-dom": "^18.2.0", "react-hook-form": "^7.36.1", "react-naver-maps": "^0.1.2", - "react-query": "^3.39.1", "react-router-dom": "^6.3.0", "react-scripts": "5.0.1", "react-toastify": "^9.0.8", diff --git a/src/api/follow/entity.ts b/src/api/follow/entity.ts index 3771bd14..bcf58154 100644 --- a/src/api/follow/entity.ts +++ b/src/api/follow/entity.ts @@ -1,4 +1,4 @@ -import { EmailUser } from 'api/user/entity'; +import { User } from 'api/user/entity'; import { FollowerInfo } from 'pages/Follow/static/entity'; export interface FollowListParams { @@ -27,8 +27,8 @@ export interface SentOrReceivedFollowParams { export interface SentOrReceivedFollowResponse { content: { id: number; - follower: EmailUser; - user: EmailUser; + follower: User; + user: User; }[]; empty: boolean; last: boolean; diff --git a/src/api/follow/index.ts b/src/api/follow/index.ts index f1cbaccc..bcf3490b 100644 --- a/src/api/follow/index.ts +++ b/src/api/follow/index.ts @@ -39,6 +39,6 @@ export const recentlyActiveFollow = () => followApi.get(' export const getFollowReview = (id: number, pageParam: string) => followApi.get(`/review/follower/${id}/shops?size=9&${pageParam}`); -export const getDetailReview = (followId: number, placeId: string, pageParam: number) => followApi.get(`/review/follower/${followId}/shop/${placeId}?${pageParam}`); +export const getDetailReview = (followId: number, placeId: string) => followApi.get(`/review/follower/${followId}/shop/${placeId}`); export const getFollowerReviewCount = (param: GetFollowerReviewCountParam) => followApi.get(`/review/follower/${param.followId}/count`); diff --git a/src/api/user/entity.ts b/src/api/user/entity.ts index 30e8b8f4..5f85fd35 100644 --- a/src/api/user/entity.ts +++ b/src/api/user/entity.ts @@ -25,7 +25,7 @@ export interface ModifyParams { email?: string; } -export interface EmailUser { +export interface User { account: string; id: number; nickname: string; @@ -46,12 +46,6 @@ export interface EmailUser { }; } -export interface SNSUser extends Omit { - profileImage?: EmailUser['profileImage']; -} - -export type User = EmailUser | SNSUser; - export interface SendRegisterEmailParams { email: string; } diff --git a/src/api/user/index.ts b/src/api/user/index.ts index 497a9798..5cd9d2c8 100644 --- a/src/api/user/index.ts +++ b/src/api/user/index.ts @@ -5,7 +5,6 @@ import { LoginResponse, ModifyParams, RegisterParams, - EmailUser, SendRegisterEmailParams, SendFindEmailParams, GetAccountParams, @@ -15,7 +14,7 @@ import { } from './entity'; import userApi from './userApiClient'; -export const register = (param: RegisterParams) => userApi.post('/', param); +export const register = (param: RegisterParams) => userApi.post('/', param); export const checkIdDuplicate = (param: CheckIdDuplicateParams) => userApi.get(`/exists?account=${param.account}`); diff --git a/src/components/common/LoadingSpinner/LoadingSpinner.module.scss b/src/components/common/LoadingSpinner/LoadingSpinner.module.scss new file mode 100644 index 00000000..60e976a1 --- /dev/null +++ b/src/components/common/LoadingSpinner/LoadingSpinner.module.scss @@ -0,0 +1,41 @@ +.loading-wrapper { + display: flex; + position: relative; + justify-content: center; + align-items: center; + width: 100%; + margin: 32px 0; +} + +.loading-div { + box-sizing: border-box; + display: block; + position: absolute; + margin: 8px; + border: 8px solid #175c8e; + border-radius: 50%; + animation: loading-wrapper 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite; + border-color: #175c8e transparent transparent; +} + +.loading-wrapper div:nth-child(1) { + animation-delay: -0.45s; +} + +.loading-wrapper div:nth-child(2) { + animation-delay: -0.3s; +} + +.loading-wrapper div:nth-child(3) { + animation-delay: -0.15s; +} + +@keyframes loading-wrapper { + 0% { + transform: rotate(0deg); + } + + 100% { + transform: rotate(360deg); + } +} diff --git a/src/components/common/LoadingSpinner/index.tsx b/src/components/common/LoadingSpinner/index.tsx new file mode 100644 index 00000000..5a57bdd0 --- /dev/null +++ b/src/components/common/LoadingSpinner/index.tsx @@ -0,0 +1,19 @@ +import styles from './LoadingSpinner.module.scss'; + +interface LoadingSpinnerProps { + size: number; +} + +function LoadingSpinner(props: LoadingSpinnerProps) { + const { size } = props; + return ( +
+
+
+
+
+
+ ); +} + +export default LoadingSpinner; diff --git a/src/components/common/SideNavigation/index.tsx b/src/components/common/SideNavigation/index.tsx index 6dedd175..ea9dc6b8 100644 --- a/src/components/common/SideNavigation/index.tsx +++ b/src/components/common/SideNavigation/index.tsx @@ -46,7 +46,7 @@ export default function SideNavigation(): JSX.Element { }, { name: '마이페이지', - icon: , + icon: , link: auth ? '/profile' : '/login', }, { @@ -91,7 +91,7 @@ export default function SideNavigation(): JSX.Element { {auth ? (
  • 프로필 이미지 diff --git a/src/index.tsx b/src/index.tsx index e7046286..0a9398a3 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -2,7 +2,7 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; import './index.scss'; import { BrowserRouter } from 'react-router-dom'; -import { QueryClient, QueryClientProvider } from 'react-query'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { Provider as JotaiProvider } from 'jotai'; import App from './App'; import reportWebVitals from './reportWebVitals'; diff --git a/src/pages/Auth/Signup/SignupPage/components/IdInput.tsx b/src/pages/Auth/Signup/SignupPage/components/IdInput.tsx index e20a23f1..fcc5f9e0 100644 --- a/src/pages/Auth/Signup/SignupPage/components/IdInput.tsx +++ b/src/pages/Auth/Signup/SignupPage/components/IdInput.tsx @@ -2,7 +2,7 @@ import { useEffect, useState } from 'react'; import cn from 'utils/ts/classNames'; import { ReactComponent as ErrorIcon } from 'assets/svg/auth/error.svg'; import { useFormContext } from 'react-hook-form'; -import { useQuery } from 'react-query'; +import { useQuery } from '@tanstack/react-query'; import { checkIdDuplicate } from 'api/user'; import { ERROR_MESSAGE } from 'pages/Auth/Signup/static/signUp'; import { AxiosError } from 'axios'; @@ -78,7 +78,7 @@ export default function IdInput() { // 서버에서 타입을 같이 전달하고 있어 해당 부분 임시 처리 checkServerValidation: () => (error?.response?.data.errorMessage?.slice(-23)), checkError: () => ((status !== 'error') || '이미 사용중인 아이디입니다.'), - checkLoading: () => ((status !== 'loading') || '중복 확인중입니다.'), + checkLoading: () => ((status !== 'pending') || '중복 확인중입니다.'), }, })} /> diff --git a/src/pages/Follow/components/FollowReview.tsx b/src/pages/Follow/components/FollowReview.tsx index a448db39..b046fd6c 100644 --- a/src/pages/Follow/components/FollowReview.tsx +++ b/src/pages/Follow/components/FollowReview.tsx @@ -1,5 +1,5 @@ import useMediaQuery from 'utils/hooks/useMediaQuery'; -import { useInfiniteQuery } from 'react-query'; +import { useInfiniteQuery } from '@tanstack/react-query'; import { getDetailReview } from 'api/follow'; import useBooleanState from 'utils/hooks/useBooleanState'; import { ReactComponent as Arrow } from 'assets/svg/common/arrow.svg'; @@ -17,9 +17,10 @@ interface Props { const useGetDetailReview = (placeId: string, followerId: number) => { const { data } = useInfiniteQuery( - ['detail', placeId], - ({ pageParam = '' }) => getDetailReview(followerId, placeId, pageParam), { + queryKey: ['detail', placeId], + initialPageParam: '', + queryFn: () => getDetailReview(followerId, placeId), getNextPageParam: (last) => { const len = last.data.content.length; if (last.data.empty || last.data.last) return null; diff --git a/src/pages/Follow/hooks/useAcceptFollow.ts b/src/pages/Follow/hooks/useAcceptFollow.ts index 041d3997..1db82c42 100644 --- a/src/pages/Follow/hooks/useAcceptFollow.ts +++ b/src/pages/Follow/hooks/useAcceptFollow.ts @@ -1,13 +1,16 @@ import { acceptFollow } from 'api/follow'; -import { useMutation, useQueryClient } from 'react-query'; +import { useMutation, useQueryClient } from '@tanstack/react-query'; // 팔로우 승인 후 받은 요청 목록을 다시 받아와 갱신 const useAcceptFollow = () => { const queryClient = useQueryClient(); - const { mutate: accept } = useMutation('accept', (id: number) => acceptFollow({ id }), { + const { mutate: accept } = useMutation({ + mutationKey: ['accept'], + mutationFn: (id: number) => acceptFollow({ id }), onSuccess: () => { - queryClient.invalidateQueries('received'); - queryClient.invalidateQueries('follower'); + queryClient.invalidateQueries({ + queryKey: ['received', 'follower'], + }); }, }); return accept; diff --git a/src/pages/Follow/hooks/useCancelFollow.ts b/src/pages/Follow/hooks/useCancelFollow.ts index c875e9ce..ddb408ed 100644 --- a/src/pages/Follow/hooks/useCancelFollow.ts +++ b/src/pages/Follow/hooks/useCancelFollow.ts @@ -1,12 +1,16 @@ import { cancelFollow } from 'api/follow'; -import { useMutation, useQueryClient } from 'react-query'; +import { useMutation, useQueryClient } from '@tanstack/react-query'; const useCancelFollow = () => { const queryClient = useQueryClient(); - const { mutate: cancel } = useMutation('cancel', (id: number) => cancelFollow({ id }), { + + const { mutate: cancel } = useMutation({ + mutationKey: ['cancel'], + mutationFn: (id: number) => cancelFollow({ id }), onSuccess: () => { - queryClient.invalidateQueries('sended'); - queryClient.invalidateQueries('search'); + queryClient.invalidateQueries({ + queryKey: ['sended', 'search'], + }); }, }); diff --git a/src/pages/Follow/hooks/useDeleteFollow.ts b/src/pages/Follow/hooks/useDeleteFollow.ts index 10ef0a80..82476d84 100644 --- a/src/pages/Follow/hooks/useDeleteFollow.ts +++ b/src/pages/Follow/hooks/useDeleteFollow.ts @@ -1,11 +1,14 @@ import { deleteFollow } from 'api/follow'; -import { useMutation, useQueryClient } from 'react-query'; +import { useMutation, useQueryClient } from '@tanstack/react-query'; const useDeleteFollow = () => { const queryClient = useQueryClient(); - const { mutate: del, data } = useMutation('delete', (account: string) => deleteFollow({ userAccount: account }), { + + const { mutate: del, data } = useMutation({ + mutationKey: ['delete'], + mutationFn: (account: string) => deleteFollow({ userAccount: account }), onSuccess: () => { - queryClient.invalidateQueries('follower'); + queryClient.invalidateQueries({ queryKey: ['follower'] }); }, }); diff --git a/src/pages/Follow/hooks/useGetFollowList.ts b/src/pages/Follow/hooks/useGetFollowList.ts index a47a1d6c..996e4f14 100644 --- a/src/pages/Follow/hooks/useGetFollowList.ts +++ b/src/pages/Follow/hooks/useGetFollowList.ts @@ -1,21 +1,21 @@ import { followList } from 'api/follow'; import { GetFollowListResponse } from 'api/follow/entity'; import { useEffect } from 'react'; -import { useInfiniteQuery } from 'react-query'; +import { useInfiniteQuery } from '@tanstack/react-query'; // 친구 목록 가져오기 const useGetFollowList = () => { - const { data, hasNextPage, fetchNextPage } = useInfiniteQuery( - 'follower', - ({ pageParam = '' }) => followList(pageParam), - { - getNextPageParam: (last) => { - const len = last.data.content.length; - if (last.data.empty || last.data.last) return null; - return `cursor=${last.data.content[len - 1].id}`; - }, + const { data, hasNextPage, fetchNextPage } = useInfiniteQuery({ + queryKey: ['follower'], + queryFn: ({ pageParam = '' }) => followList(pageParam), + initialPageParam: 'cursor=0', + getNextPageParam: (last) => { + const len = last.data.content.length; + if (last.data.empty || last.data.last) return null; + return `cursor=${last.data.content[len - 1].id}`; }, - ); + + }); const flatData: GetFollowListResponse = { content: data ? data.pages.flatMap((page) => page.data.content) : [], empty: !data || data.pages.every((page) => page.data.empty), diff --git a/src/pages/Follow/hooks/useGetFollowerReview.ts b/src/pages/Follow/hooks/useGetFollowerReview.ts index 4485d0d9..6961995f 100644 --- a/src/pages/Follow/hooks/useGetFollowerReview.ts +++ b/src/pages/Follow/hooks/useGetFollowerReview.ts @@ -1,20 +1,19 @@ import { getFollowReview } from 'api/follow'; import { useEffect } from 'react'; -import { useInfiniteQuery } from 'react-query'; +import { useInfiniteQuery } from '@tanstack/react-query'; const useGetFollowerReview = (id: number) => { - const { data, hasNextPage, fetchNextPage } = useInfiniteQuery( - ['review', id], - ({ pageParam = '' }) => getFollowReview(id, pageParam), - { - getNextPageParam: (last) => { - const len = last.data.content.length; - if (last.data.empty) return null; - // cursor: 마지막으로 조회한 상점 id - return `cursor=${last.data.content[len - 1].shopId}`; - }, + const { data, hasNextPage, fetchNextPage } = useInfiniteQuery({ + queryKey: ['review', id], + queryFn: ({ pageParam = '' }) => getFollowReview(id, pageParam), + initialPageParam: 'cursor=0', + getNextPageParam: (last) => { + const len = last.data.content.length; + if (last.data.empty) return null; + // cursor: 마지막으로 조회한 상점 id + return `cursor=${last.data.content[len - 1].shopId}`; }, - ); + }); const flatData = { content: data ? data.pages.flatMap((page) => page.data.content) : [], }; diff --git a/src/pages/Follow/hooks/useGetFollowerReviewCount.ts b/src/pages/Follow/hooks/useGetFollowerReviewCount.ts index f5725b73..0df9d2e7 100644 --- a/src/pages/Follow/hooks/useGetFollowerReviewCount.ts +++ b/src/pages/Follow/hooks/useGetFollowerReviewCount.ts @@ -1,8 +1,9 @@ import { getFollowerReviewCount } from 'api/follow'; -import { useQuery } from 'react-query'; +import { useQuery } from '@tanstack/react-query'; const useGetFollowerReviewCount = (followId: number) => { - const { data } = useQuery('reviewCount', () => getFollowerReviewCount({ followId })); + const { data } = useQuery({ queryKey: ['reviewCount'], queryFn: () => getFollowerReviewCount({ followId }) }); + return data; }; diff --git a/src/pages/Follow/hooks/useGetRecentlyActiveFollower.ts b/src/pages/Follow/hooks/useGetRecentlyActiveFollower.ts index fdb99898..c8ee00d0 100644 --- a/src/pages/Follow/hooks/useGetRecentlyActiveFollower.ts +++ b/src/pages/Follow/hooks/useGetRecentlyActiveFollower.ts @@ -1,9 +1,9 @@ import { recentlyActiveFollow } from 'api/follow'; -import { useQuery } from 'react-query'; +import { useQuery } from '@tanstack/react-query'; // 최대 15명만 보임, 접속한지 24시가 지나면 사라짐 const useGetRecentlyActiveFollower = () => { - const { data, isLoading } = useQuery('recent', () => recentlyActiveFollow()); + const { data, isLoading } = useQuery({ queryKey: ['recent'], queryFn: () => recentlyActiveFollow() }); return { data, isLoading }; }; diff --git a/src/pages/Follow/hooks/useRequestAndUpdate.ts b/src/pages/Follow/hooks/useRequestAndUpdate.ts index 84a9a699..2545ed10 100644 --- a/src/pages/Follow/hooks/useRequestAndUpdate.ts +++ b/src/pages/Follow/hooks/useRequestAndUpdate.ts @@ -1,14 +1,15 @@ import { requestFollow } from 'api/follow'; -import { useMutation, useQueryClient } from 'react-query'; +import { useMutation, useQueryClient } from '@tanstack/react-query'; import makeToast from 'utils/ts/makeToast'; // 팔로우 요청 후 유저 목록을 다시 받아와 요청중 상태로 변경 const useRequestAndUpdate = () => { const queryClient = useQueryClient(); - const { mutate: request } = useMutation('request', (account: string) => requestFollow({ userAccount: account }), { + const { mutate: request } = useMutation({ + mutationKey: ['request'], + mutationFn: (account: string) => requestFollow({ userAccount: account }), onSuccess: () => { - queryClient.invalidateQueries('sended'); - queryClient.invalidateQueries('search'); + queryClient.invalidateQueries({ queryKey: ['sended', 'search'] }); }, onError: () => { makeToast('error', '잘못된 친구 정보입니다.'); diff --git a/src/pages/Follow/hooks/useSearchFriend.ts b/src/pages/Follow/hooks/useSearchFriend.ts index b115587a..64f27e7c 100644 --- a/src/pages/Follow/hooks/useSearchFriend.ts +++ b/src/pages/Follow/hooks/useSearchFriend.ts @@ -1,7 +1,7 @@ import { searchUsers } from 'api/follow'; import { SearchUsersResponse } from 'api/follow/entity'; import { useState, useEffect } from 'react'; -import { useInfiniteQuery } from 'react-query'; +import { useInfiniteQuery } from '@tanstack/react-query'; // useInfinitieQuery로 무한 스크롤 구현, pageParam의 기본값은 undefined const useSearchFriend = () => { @@ -10,6 +10,7 @@ const useSearchFriend = () => { queryKey: ['search', keyword], queryFn: ({ queryKey: [, target], pageParam = '' }) => searchUsers(target, pageParam), enabled: keyword !== '', + initialPageParam: 'cursor=0', getNextPageParam: (lastPage) => { const len = lastPage.data.content.length; if (lastPage.data.empty || lastPage.data.last) return null; diff --git a/src/pages/Follow/hooks/useSentOrReceivedFollow.ts b/src/pages/Follow/hooks/useSentOrReceivedFollow.ts index a5d29cc2..1d271963 100644 --- a/src/pages/Follow/hooks/useSentOrReceivedFollow.ts +++ b/src/pages/Follow/hooks/useSentOrReceivedFollow.ts @@ -1,7 +1,7 @@ import { SentOrReceivedFollowResponse } from 'api/follow/entity'; import { AxiosResponse } from 'axios'; import { useEffect } from 'react'; -import { useInfiniteQuery } from 'react-query'; +import { useInfiniteQuery } from '@tanstack/react-query'; const useSentOrReceivedFollow = ( key: string, @@ -11,7 +11,10 @@ const useSentOrReceivedFollow = ( data, fetchNextPage, hasNextPage, - } = useInfiniteQuery(key, ({ pageParam = 0 }) => queryFn(pageParam), { + } = useInfiniteQuery({ + queryKey: [key], + queryFn: ({ pageParam = 0 }) => queryFn(pageParam), + initialPageParam: 0, // getNextPageParam은 다음 api를 요청할 때 사용될 pageParam값을 정할 수 있다. getNextPageParam: (lastPage) => { if (lastPage.data.empty || lastPage.data.last) return null; diff --git a/src/pages/Home/components/Map/hooks/useFilterShops.ts b/src/pages/Home/components/Map/hooks/useFilterShops.ts index 3cfb931b..0f7b7d82 100644 --- a/src/pages/Home/components/Map/hooks/useFilterShops.ts +++ b/src/pages/Home/components/Map/hooks/useFilterShops.ts @@ -1,5 +1,5 @@ import useGeolocation from 'utils/hooks/useGeolocation'; -import { useQuery } from 'react-query'; +import { useQuery } from '@tanstack/react-query'; import { getfilterShops } from 'api/shop'; import { FilterShopsParams } from 'api/shop/entity'; import { useAuth } from 'store/auth'; @@ -19,10 +19,12 @@ const useFilterShops = ({ }; const { isLoading, isError, data, refetch, - } = useQuery('filterShops', () => getfilterShops(params, { - lat: location?.lat, - lng: location?.lng, - }), { + } = useQuery({ + queryKey: ['filterShops'], + queryFn: () => getfilterShops(params, { + lat: location?.lat, + lng: location?.lng, + }), enabled, }); diff --git a/src/pages/Inquiry/index.tsx b/src/pages/Inquiry/index.tsx index 2ed9fbf6..fc1435cd 100644 --- a/src/pages/Inquiry/index.tsx +++ b/src/pages/Inquiry/index.tsx @@ -15,23 +15,19 @@ export default function Inquiry(): JSX.Element { return (
    - { - postData && ( -
    - - -
    - ) - } +
    + + +