diff --git a/next.config.js b/next.config.js index e5d052a9..da23095e 100644 --- a/next.config.js +++ b/next.config.js @@ -4,6 +4,9 @@ const withPlugins = require('next-compose-plugins') const withPWA = require('next-pwa')({ dest: 'public', + disable: process.env.NODE_ENV === 'development', + register: true, + skipWaiting: true, }) const withBundleAnalyzer = require('@next/bundle-analyzer')({ @@ -15,7 +18,10 @@ const nextConfig = { reactStrictMode: false, images: { minimumCacheTTL: 1 * 60 * 60 * 24 * 365, - domains: ['linkhub-s3-2025.s3.ap-northeast-2.amazonaws.com'], + domains: [ + 'linkhub-s3-2025.s3.ap-northeast-2.amazonaws.com', + 'linkhub-s3.s3.ap-northeast-2.amazonaws.com', + ], formats: ['image/avif', 'image/webp'], remotePatterns: [ { diff --git a/package-lock.json b/package-lock.json index c58c938b..1f0fae66 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3610,6 +3610,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -4260,6 +4272,19 @@ "url": "https://github.com/fb55/domutils?sponsor=1" } }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/duplexer": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", @@ -4380,6 +4405,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-iterator-helpers": { "version": "1.0.15", "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.15.tgz", @@ -4408,14 +4449,26 @@ "integrity": "sha512-l60ETUTmLqbVbVHv1J4/qj+M8nq7AwMzEcg3kmJDt9dCNrTk+yHcYFf/Kw75pMDwd9mPcIGCG5LcS20SxYRzFA==", "peer": true }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-set-tostringtag": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.2.tgz", - "integrity": "sha512-BuDyupZt65P9D2D2vA/zqcI3G5xRsklm5N3xCwuiy+/vKy8i0ifdsQP1sLgO4tZDSCaQUSnmC48khknGMV3D2Q==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", "dependencies": { - "get-intrinsic": "^1.2.2", - "has-tostringtag": "^1.0.0", - "hasown": "^2.0.0" + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" }, "engines": { "node": ">= 0.4" @@ -5178,14 +5231,23 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", - "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -5196,6 +5258,18 @@ "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==" }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/get-stream": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", @@ -5320,11 +5394,11 @@ } }, "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dependencies": { - "get-intrinsic": "^1.1.3" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -5404,9 +5478,9 @@ } }, "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "engines": { "node": ">= 0.4" }, @@ -5415,11 +5489,11 @@ } }, "node_modules/has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "dependencies": { - "has-symbols": "^1.0.2" + "has-symbols": "^1.0.3" }, "engines": { "node": ">= 0.4" @@ -5429,9 +5503,9 @@ } }, "node_modules/hasown": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", - "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "dependencies": { "function-bind": "^1.1.2" }, @@ -6431,6 +6505,14 @@ "semver": "bin/semver.js" } }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", diff --git a/src/app/(routes)/notification/invite/page.tsx b/src/app/(routes)/notification/invite/page.tsx index 2b52c85a..1d6c2522 100644 --- a/src/app/(routes)/notification/invite/page.tsx +++ b/src/app/(routes)/notification/invite/page.tsx @@ -1,7 +1,7 @@ 'use client' import NotificationList from '@/components/common/NotificationList/NotificationList' -import { fetchGetInvitations } from '@/services/notification/invitations' +import { fetchGetInvitations } from '@/services/notification/useNotification' const NotificationInvitePage = () => { return ( diff --git a/src/app/(routes)/space/[spaceId]/comment/page.tsx b/src/app/(routes)/space/[spaceId]/comment/page.tsx index a6c82f9b..060723d3 100644 --- a/src/app/(routes)/space/[spaceId]/comment/page.tsx +++ b/src/app/(routes)/space/[spaceId]/comment/page.tsx @@ -6,7 +6,6 @@ import CommentList from '@/components/CommentList/CommentList' import Button from '@/components/common/Button/Button' import DeferredComponent from '@/components/common/DeferedComponent/DeferedComponent' import Space from '@/components/common/Space/Space' -import useGetSpace from '@/components/common/Space/hooks/useGetSpace' import Tab from '@/components/common/Tab/Tab' import TabItem from '@/components/common/Tab/TabItem' import useTab from '@/components/common/Tab/hooks/useTab' @@ -14,7 +13,8 @@ import { CATEGORIES_RENDER, MIN_TAB_NUMBER } from '@/constants' import { useModal } from '@/hooks' import { useCurrentUser } from '@/hooks/useCurrentUser' import useSpaceComment from '@/hooks/useSpaceComment' -import { fetchGetComments } from '@/services/comment/comment' +import { fetchGetComments } from '@/services/comments/useComments' +import { useGetSpace } from '@/services/space/useSpace' import { XMarkIcon } from '@heroicons/react/20/solid' import dynamic from 'next/dynamic' @@ -29,7 +29,7 @@ export interface CommentFormValues { const SpaceCommentPage = ({ params }: { params: { spaceId: number } }) => { const { isLoggedIn } = useCurrentUser() - const { space, isSpaceLoading } = useGetSpace() + const { data: space, isLoading: isSpaceLoading } = useGetSpace(params.spaceId) const { register, unregister, setValue, setFocus, handleSubmit } = useForm({ defaultValues: { diff --git a/src/app/(routes)/space/[spaceId]/layout.tsx b/src/app/(routes)/space/[spaceId]/layout.tsx index 5310c16a..9c234844 100644 --- a/src/app/(routes)/space/[spaceId]/layout.tsx +++ b/src/app/(routes)/space/[spaceId]/layout.tsx @@ -1,14 +1,16 @@ -import { fetchGetSpace } from '@/services/space/space' import { Metadata } from 'next' type SpaceLayoutProps = { params: { spaceId: number } } +const baseURL = process.env.NEXT_PUBLIC_API_ADDRESS export async function generateMetadata({ params: { spaceId }, }: SpaceLayoutProps): Promise { - const space = await fetchGetSpace({ spaceId }) + const space = await fetch(`${baseURL}/spaces/${spaceId}`, { + method: 'GET', + }).then((res) => res.json()) return { title: space.spaceName, diff --git a/src/app/(routes)/space/[spaceId]/page.tsx b/src/app/(routes)/space/[spaceId]/page.tsx index 023c86aa..1dbe9c1d 100644 --- a/src/app/(routes)/space/[spaceId]/page.tsx +++ b/src/app/(routes)/space/[spaceId]/page.tsx @@ -5,8 +5,6 @@ import Button from '@/components/common/Button/Button' import DeferredComponent from '@/components/common/DeferedComponent/DeferedComponent' import useViewLink from '@/components/common/LinkList/hooks/useViewLink' import Space from '@/components/common/Space/Space' -import useGetSpace from '@/components/common/Space/hooks/useGetSpace' -import useGetTags from '@/components/common/Space/hooks/useGetTags' import Tab from '@/components/common/Tab/Tab' import TabItem from '@/components/common/Tab/TabItem' import useTab from '@/components/common/Tab/hooks/useTab' @@ -15,7 +13,8 @@ import { CATEGORIES_RENDER, MIN_TAB_NUMBER } from '@/constants' import { useSortParam } from '@/hooks' import { useCurrentUser } from '@/hooks/useCurrentUser' import useTagParam from '@/hooks/useTagParam' -import { fetchGetLinks } from '@/services/link/link' +import { fetchGetLinks } from '@/services/link/useLink' +import { useGetSpace, useGetTags } from '@/services/space/useSpace' import { cls } from '@/utils' import { PencilSquareIcon } from '@heroicons/react/24/outline' import { @@ -26,12 +25,12 @@ import { const SpacePage = ({ params }: { params: { spaceId: number } }) => { const { currentUser } = useCurrentUser() - const { space, isSpaceLoading } = useGetSpace() + const { data: space, isLoading: isSpaceLoading } = useGetSpace(params.spaceId) const [isEdit, editToggle] = useToggle(false) const [view, handleChangeList, handleChangeCard] = useViewLink() const { currentTab, tabList } = useTab({ type: 'space', space }) const { sort, sortIndex, handleSortChange } = useSortParam('link') - const { tags, refetchTags, isTagsLoading } = useGetTags({ + const { data: tags, isLoading: isTagsLoading } = useGetTags({ spaceId: space?.spaceId, }) const { tag, tagIndex, handleTagChange } = useTagParam({ tags }) @@ -138,10 +137,10 @@ const SpacePage = ({ params }: { params: { spaceId: number } }) => { isCanEdit={space.isCanEdit} isMember={ !!space?.memberDetailInfos.find( - (member) => member.memberId === currentUser?.memberId, + (member: { memberId: number }) => + member.memberId === currentUser?.memberId, ) } - refetchTags={refetchTags} /> )} { const router = useRouter() const spaceId = params.spaceId - const { space, isSpaceLoading } = useGetSpace() + const { data: space, isLoading: isSpaceLoading } = useGetSpace(spaceId) const { Modal, isOpen, modalOpen, modalClose } = useModal(false) + const { mutate: deleteSpace } = useDeleteSpace() - const handleConfirm = async () => { - await fetchDeleteSpace(spaceId) + const handleConfirm = () => { + deleteSpace({ spaceId }) notify('info', '스페이스가 삭제되었습니다.') router.replace('/') } diff --git a/src/app/(routes)/user/[userId]/favorite/page.tsx b/src/app/(routes)/user/[userId]/favorite/page.tsx index 1955c7d1..63865bac 100644 --- a/src/app/(routes)/user/[userId]/favorite/page.tsx +++ b/src/app/(routes)/user/[userId]/favorite/page.tsx @@ -3,7 +3,7 @@ import { useForm } from 'react-hook-form' import { CategoryList, Input, SpaceList } from '@/components' import { useCategoryParam, useProfileSpacesSearch } from '@/hooks' -import { fetchGetMyFavoriteSpaces } from '@/services/user/profile/favorites' +import { fetchGetMyFavoriteSpaces } from '@/services/space/useSpaces' import { usePathname, useSearchParams } from 'next/navigation' import { SearchFormValue } from '../space/page' diff --git a/src/app/(routes)/user/[userId]/layout.tsx b/src/app/(routes)/user/[userId]/layout.tsx index 08c14350..27c8f731 100644 --- a/src/app/(routes)/user/[userId]/layout.tsx +++ b/src/app/(routes)/user/[userId]/layout.tsx @@ -1,5 +1,5 @@ import { ProfileTap } from '@/components' -import { fetchGetUserProfile } from '@/services/user/profile/profile' +import { fetchGetUserProfile } from '@/services/users/useUsers' import { Metadata } from 'next' export type UserLayoutProps = { diff --git a/src/app/(routes)/user/[userId]/page.tsx b/src/app/(routes)/user/[userId]/page.tsx index 80f86169..e7c0c9f1 100644 --- a/src/app/(routes)/user/[userId]/page.tsx +++ b/src/app/(routes)/user/[userId]/page.tsx @@ -5,7 +5,7 @@ import { ProfileEditButton, } from '@/components' import { CATEGORIES_RENDER, PROFILE_MSG } from '@/constants' -import { fetchGetUserProfile } from '@/services/user/profile/profile' +import { fetchGetUserProfile } from '@/services/users/useUsers' import { UserLayoutProps } from './layout' export default async function UserPage({ diff --git a/src/app/(routes)/user/[userId]/space/page.tsx b/src/app/(routes)/user/[userId]/space/page.tsx index 22041e3a..244fca84 100644 --- a/src/app/(routes)/user/[userId]/space/page.tsx +++ b/src/app/(routes)/user/[userId]/space/page.tsx @@ -3,7 +3,7 @@ import { useForm } from 'react-hook-form' import { CategoryList, Input, SpaceList } from '@/components' import { useCategoryParam, useProfileSpacesSearch } from '@/hooks' -import { fetchSearchMySpaces } from '@/services/space/spaces' +import { fetchSearchMySpaces } from '@/services/space/useSpaces' import { usePathname, useSearchParams } from 'next/navigation' export interface SearchFormValue { diff --git a/src/app/api/auth-user/route.ts b/src/app/api/auth/route.ts similarity index 100% rename from src/app/api/auth-user/route.ts rename to src/app/api/auth/route.ts diff --git a/src/app/api/email-verify/route.ts b/src/app/api/email/verify/route.ts similarity index 100% rename from src/app/api/email-verify/route.ts rename to src/app/api/email/verify/route.ts diff --git a/src/app/api/space/[spaceId]/links/readInfo/route.ts b/src/app/api/space/[spaceId]/links/readInfo/[linkId]/route.ts similarity index 76% rename from src/app/api/space/[spaceId]/links/readInfo/route.ts rename to src/app/api/space/[spaceId]/links/readInfo/[linkId]/route.ts index a7859716..86fd42a5 100644 --- a/src/app/api/space/[spaceId]/links/readInfo/route.ts +++ b/src/app/api/space/[spaceId]/links/readInfo/[linkId]/route.ts @@ -2,11 +2,12 @@ import { useServerCookie } from '@/hooks/useServerCookie' import { apiServer } from '@/services/apiServices' import { NextRequest, NextResponse } from 'next/server' -export async function POST(req: NextRequest) { +export async function POST( + req: NextRequest, + { params }: { params: { spaceId: string; linkId: string } }, +) { const { token } = useServerCookie() - const { searchParams } = new URL(req.url) - const spaceId = searchParams.get('spaceId') - const linkId = searchParams.get('linkId') + const { spaceId, linkId } = params const path = `/spaces/${spaceId}/links/${linkId}/view` const headers = { Authorization: `Bearer ${token}`, diff --git a/src/app/api/spaces/invitation/route.ts b/src/app/api/spaces/invitations/route.ts similarity index 100% rename from src/app/api/spaces/invitation/route.ts rename to src/app/api/spaces/invitations/route.ts diff --git a/src/components/CommentList/CommentList.tsx b/src/components/CommentList/CommentList.tsx index 1ea48032..c120f219 100644 --- a/src/components/CommentList/CommentList.tsx +++ b/src/components/CommentList/CommentList.tsx @@ -1,7 +1,7 @@ import { Fragment } from 'react' import { Comment } from '@/components' import useInfiniteScroll from '@/hooks/useInfiniteScroll' -import { fetchGetReplies } from '@/services/comment/reply' +import { fetchGetReplies } from '@/services/comments/useComments' import { CommentReqBody, CommentResBody } from '@/types' import ReplyList from '../ReplyList/ReplyList' import useCommentsQuery from './hooks/useCommentsQuery' diff --git a/src/components/CommentList/hooks/useCommentsQuery.ts b/src/components/CommentList/hooks/useCommentsQuery.ts index 3214f98f..2509ef4a 100644 --- a/src/components/CommentList/hooks/useCommentsQuery.ts +++ b/src/components/CommentList/hooks/useCommentsQuery.ts @@ -1,10 +1,10 @@ -import { INITIAL_PAGE_NUMBER, PAGE_SIZE } from '@/constants' +import { INITIAL_PAGE_NUMBER, PAGE_SIZE, QUERY_KEYS } from '@/constants' import { useInfiniteQuery } from '@tanstack/react-query' import { CommentListProps } from '../CommentList' const useCommentsQuery = ({ spaceId, fetchFn }: CommentListProps) => { const { data, fetchNextPage, hasNextPage } = useInfiniteQuery({ - queryKey: ['comments', spaceId], + queryKey: [QUERY_KEYS.COMMENTS, spaceId], queryFn: ({ pageParam }) => fetchFn({ spaceId, diff --git a/src/components/FollowListButton/FollowListButton.tsx b/src/components/FollowListButton/FollowListButton.tsx index 41ead172..5bcf48ee 100644 --- a/src/components/FollowListButton/FollowListButton.tsx +++ b/src/components/FollowListButton/FollowListButton.tsx @@ -3,10 +3,7 @@ import { PROFILE_MSG } from '@/constants' import { useFollowUser, useModal } from '@/hooks' import { useCurrentUser } from '@/hooks/useCurrentUser' -import { - fetchGetFollowers, - fetchGetFollowing, -} from '@/services/user/follow/follow' +import { fetchGetFollowers, fetchGetFollowing } from '@/services/users/useUsers' import { UserProfileResBody } from '@/types' import FollowList from '../common/FollowList/FollowList' import LoginModal from '../common/Modal/LoginModal' diff --git a/src/components/PopularLink/PopularLinkList/PopularLinkList.tsx b/src/components/PopularLink/PopularLinkList/PopularLinkList.tsx index 42fb2768..6a15ec64 100644 --- a/src/components/PopularLink/PopularLinkList/PopularLinkList.tsx +++ b/src/components/PopularLink/PopularLinkList/PopularLinkList.tsx @@ -1,8 +1,8 @@ 'use client' -import useGetPopularLinks from '@/components/PopularLinkList/hooks/useGetPopularLinks' import { ChipColors } from '@/components/common/Chip/Chip' import LinkItem from '@/components/common/LinkItem/LinkItem' +import { useGetPopularLinks } from '@/services/link/useLink' import { PopularLinkResBody } from '@/types' import 'swiper/css' import 'swiper/css/free-mode' @@ -12,6 +12,7 @@ import { Swiper, SwiperSlide } from 'swiper/react' const PopularLinkList = () => { const { data } = useGetPopularLinks() + return ( { const queryClient = getQueryClient() await queryClient.prefetchQuery({ - queryKey: ['PopularLinks'], + queryKey: [QUERY_KEYS.POPULAR_LINKS], queryFn: fetchGetPopularLinks, }) diff --git a/src/components/PopularLinkList/PopularLinkList.tsx b/src/components/PopularLinkList/PopularLinkList.tsx deleted file mode 100644 index 9c0ee71f..00000000 --- a/src/components/PopularLinkList/PopularLinkList.tsx +++ /dev/null @@ -1,68 +0,0 @@ -'use client' - -import React from 'react' -import { ChipColors } from '@/components/common/Chip/Chip' -import LinkItem from '@/components/common/LinkItem/LinkItem' -import { PopularLinkResBody } from '@/types' -import 'swiper/css' -import 'swiper/css/free-mode' -import 'swiper/css/pagination' -import { FreeMode } from 'swiper/modules' -import { Swiper, SwiperSlide } from 'swiper/react' -import useGetPopularLinks from './hooks/useGetPopularLinks' - -const PopularLinkList = () => { - const { data } = useGetPopularLinks() - - return ( - - {data?.responses.map((link: PopularLinkResBody) => ( - - - - ))} - - ) -} - -export default React.memo(PopularLinkList) diff --git a/src/components/PopularLinkList/hooks/useGetPopularLinks.ts b/src/components/PopularLinkList/hooks/useGetPopularLinks.ts deleted file mode 100644 index ffaf58f5..00000000 --- a/src/components/PopularLinkList/hooks/useGetPopularLinks.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { fetchGetPopularLinks } from '@/services/link/link' -import { useQuery } from '@tanstack/react-query' - -const useGetPopularLinks = () => { - return useQuery({ - queryKey: ['PopularLinks'], - queryFn: fetchGetPopularLinks, - }) -} - -export default useGetPopularLinks diff --git a/src/components/ReplyList/hooks/useRepliesQuery.ts b/src/components/ReplyList/hooks/useRepliesQuery.ts index 6c606c84..989c209d 100644 --- a/src/components/ReplyList/hooks/useRepliesQuery.ts +++ b/src/components/ReplyList/hooks/useRepliesQuery.ts @@ -1,4 +1,4 @@ -import { INITIAL_PAGE_NUMBER, PAGE_SIZE } from '@/constants' +import { INITIAL_PAGE_NUMBER, PAGE_SIZE, QUERY_KEYS } from '@/constants' import { useInfiniteQuery } from '@tanstack/react-query' import { ReplyListProps } from '../ReplyList' @@ -8,7 +8,7 @@ const useRepliesQuery = ({ fetchFn, }: ReplyListProps) => { const { data, fetchNextPage, hasNextPage } = useInfiniteQuery({ - queryKey: ['replies', spaceId, parentCommentId], + queryKey: [QUERY_KEYS.REPLIES, spaceId, parentCommentId], queryFn: ({ pageParam }) => fetchFn({ spaceId, diff --git a/src/components/SearchController/SearchController.tsx b/src/components/SearchController/SearchController.tsx index 24de8471..1257dd33 100644 --- a/src/components/SearchController/SearchController.tsx +++ b/src/components/SearchController/SearchController.tsx @@ -3,8 +3,8 @@ import { CategoryList, Dropdown, SpaceList } from '@/components' import UserList from '@/components/UserList/UserList' import { useCategoryParam, useSortParam } from '@/hooks' -import { fetchSearchSpaces } from '@/services/space/spaces' -import { fetchSearchUsers } from '@/services/user/search/search' +import { fetchSearchSpaces } from '@/services/space/useSpaces' +import { fetchSearchUsers } from '@/services/users/useUsers' import { cls } from '@/utils' import { useSearchParams } from 'next/navigation' diff --git a/src/components/SettingController/SettingController.tsx b/src/components/SettingController/SettingController.tsx index c6f17566..f33273a7 100644 --- a/src/components/SettingController/SettingController.tsx +++ b/src/components/SettingController/SettingController.tsx @@ -2,15 +2,17 @@ import UserInfoForm from '@/components/UserInfoForm/UserInfoForm' import { useCurrentUser } from '@/hooks/useCurrentUser' +import { useGetUserProfile } from '@/services/users/useUsers' const SettingController = () => { const { currentUser } = useCurrentUser() + const { data: user } = useGetUserProfile(currentUser?.memberId || 0) return (
- {currentUser && ( + {user && ( )} diff --git a/src/components/Space/SpaceForm.tsx b/src/components/Space/SpaceForm.tsx index 9ac3204b..15b949bf 100644 --- a/src/components/Space/SpaceForm.tsx +++ b/src/components/Space/SpaceForm.tsx @@ -5,17 +5,17 @@ import { useForm } from 'react-hook-form' import { CategoryList, Input, Toggle } from '@/components' import { MIN_TAB_NUMBER } from '@/constants' import { - feachCreateSpace, - fetchScrapSpace, - fetchSettingSpace, -} from '@/services/space/space' + useGetSpace, + usePatchSpace, + usePostScrapSpace, + usePostSpace, +} from '@/services/space/useSpace' import { CreateSpaceReqBody, SpaceDetailResBody } from '@/types' import imageCompression from 'browser-image-compression' import Image from 'next/image' import { usePathname, useRouter } from 'next/navigation' import Button from '../common/Button/Button' import { CATEGORIES } from '../common/CategoryList/constants' -import useGetSpace from '../common/Space/hooks/useGetSpace' import Tab from '../common/Tab/Tab' import TabItem from '../common/Tab/TabItem' import useTab from '../common/Tab/hooks/useTab' @@ -35,7 +35,7 @@ const SpaceForm = ({ spaceType, space }: SpaceFormProps) => { const path = usePathname() const spaceId = Number(path.split('/')[2]) const router = useRouter() - const getSpace = useGetSpace() + const { data: spaceData } = useGetSpace(spaceId) const { register, getValues, @@ -54,6 +54,9 @@ const SpaceForm = ({ spaceType, space }: SpaceFormProps) => { }, }) + const { mutateAsync: postSpace } = usePostSpace() + const { mutateAsync: patchSpace } = usePatchSpace(spaceId) + const { mutateAsync: postScrapSpace } = usePostScrapSpace(spaceId) useEffect(() => { setThumnail(space?.spaceImagePath) }, [space]) @@ -81,12 +84,12 @@ const SpaceForm = ({ spaceType, space }: SpaceFormProps) => { notify('error', '카테고리를 선택해 주세요.') } else { if (spaceType === 'Create') { - const { spaceId } = await feachCreateSpace(data, imageFile) + const { spaceId } = await postSpace({ data, file: imageFile }) notify('info', '스페이스가 생성되었습니다.') - router.replace(`/space/${spaceId}`) + spaceId && router.replace(`/space/${spaceId}`) } else if (spaceType === 'Setting') { try { - const response = await fetchSettingSpace(spaceId, data, imageFile) + const response = await patchSpace({ data, file: imageFile }) if (!response.spaceId) { router.replace('/') return @@ -97,8 +100,8 @@ const SpaceForm = ({ spaceType, space }: SpaceFormProps) => { router.replace('/') } } else { - const response = await fetchScrapSpace(spaceId, data, imageFile) - notify('info', '스페이스가 생성되었습니다.') + const response = await postScrapSpace({ data, file: imageFile }) + notify('info', '스페이스를 가져왔습니다.') router.push(`/space/${response.spaceId}`) } } @@ -145,7 +148,7 @@ const SpaceForm = ({ spaceType, space }: SpaceFormProps) => {
- @{getSpace.space?.spaceName}{' '} + @{spaceData?.space?.spaceName}{' '} 에서 가져오는 중
@@ -222,18 +225,6 @@ const SpaceForm = ({ spaceType, space }: SpaceFormProps) => { onChange={() => setValue('isComment', !getValues('isComment'))} />
- {/*
-
- 링크 3줄 요약 여부 -
- {}} - /> -
*/}
읽음 처리 여부
{ const [imageFile, setImageFile] = useState() const router = useRouter() const [isVerification, setVerification] = useState(false) + const { mutateAsync: putUserProfileMutation } = usePutUserProfile( + userData?.memberId || 0, + ) + const { mutateAsync: postEmail } = usePostEmail() + const { mutateAsync: postEmailVerify } = usePostEmailVerify() useEffect(() => { setThumnail(userData?.profileImagePath) @@ -92,7 +98,7 @@ const UserInfoForm = ({ userData, formType }: UserInfoFormProps) => { const handleEmailAuth = async (email: string) => { try { - const response = await fetchPostEmail({ email }) + const response = await postEmail({ email }) if (response.errorCode) { response.errorCode === 'M001' && setVerification(true) @@ -108,7 +114,7 @@ const UserInfoForm = ({ userData, formType }: UserInfoFormProps) => { const handleCheckAuthNum = async (code: string) => { try { - const verification = await fetchPostEmailVerify({ + const verification = await postEmailVerify({ email: getValues('newsEmail'), code, }) @@ -153,7 +159,11 @@ const UserInfoForm = ({ userData, formType }: UserInfoFormProps) => { ) => { try { userData?.memberId && - (await fetchPostUserProfile(userData?.memberId, data, imageFile)) + (await putUserProfileMutation({ + memberId: userData?.memberId, + data, + file: imageFile, + })) notify('success', '수정되었습니다.') router.refresh() router.back() diff --git a/src/components/UserInfoForm/hooks/useRegister.ts b/src/components/UserInfoForm/hooks/useRegister.ts index 8ba26ccd..af1a3eb5 100644 --- a/src/components/UserInfoForm/hooks/useRegister.ts +++ b/src/components/UserInfoForm/hooks/useRegister.ts @@ -1,5 +1,5 @@ import { notify } from '@/components/common/Toast/Toast' -import { registerUser } from '@/services/auth' +import { useRegisterUser } from '@/services/auth/useAuth' import Cookies from 'js-cookie' import { useRouter } from 'next/navigation' @@ -15,11 +15,12 @@ export interface RegisterReqBody { const useRegister = () => { const router = useRouter() + const { mutateAsync: registerUser } = useRegisterUser() const registerLinkHub = async (data: RegisterReqBody, imageFile?: File) => { if (Cookies.get('Social-Id') && Cookies.get('Provider')) { data.socialId = Cookies.get('Social-Id') || '' data.provider = Cookies.get('Provider') || '' - const { jwt } = await registerUser(data, imageFile) + const { jwt } = await registerUser({ data, file: imageFile }) Cookies.remove('Social-Id') Cookies.remove('Provider') diff --git a/src/components/UserList/hooks/useUsersQuery.ts b/src/components/UserList/hooks/useUsersQuery.ts index 1d4f3cb5..051e2daa 100644 --- a/src/components/UserList/hooks/useUsersQuery.ts +++ b/src/components/UserList/hooks/useUsersQuery.ts @@ -1,10 +1,10 @@ -import { INITIAL_PAGE_NUMBER, PAGE_SIZE } from '@/constants' +import { INITIAL_PAGE_NUMBER, PAGE_SIZE, QUERY_KEYS } from '@/constants' import { useInfiniteQuery } from '@tanstack/react-query' import { UserListProps } from '../UserList' const useUsersQuery = ({ keyword, fetchFn }: UserListProps) => { const { data, fetchNextPage, hasNextPage, isLoading } = useInfiniteQuery({ - queryKey: ['users', keyword], + queryKey: [QUERY_KEYS.MEMBERS, keyword], queryFn: ({ pageParam }) => fetchFn({ pageNumber: pageParam, diff --git a/src/components/common/Comment/Comment.tsx b/src/components/common/Comment/Comment.tsx index 44cdab9f..03e068e6 100644 --- a/src/components/common/Comment/Comment.tsx +++ b/src/components/common/Comment/Comment.tsx @@ -149,7 +149,7 @@ const Comment = ({ cancelText="취소" confirmText="삭제" onClose={modalClose} - onConfirm={() => handleDeleteConfirm(!isRoot)}> + onConfirm={() => handleDeleteConfirm()}>
삭제하시겠습니까?
)} diff --git a/src/components/common/Comment/hooks/useComment.ts b/src/components/common/Comment/hooks/useComment.ts index 4bc322d4..deff8467 100644 --- a/src/components/common/Comment/hooks/useComment.ts +++ b/src/components/common/Comment/hooks/useComment.ts @@ -1,5 +1,5 @@ import { useCallback, useState } from 'react' -import { fetchDeleteComment } from '@/services/comment/comment' +import { useDeleteComment } from '@/services/comments/useComments' import { useQueryClient } from '@tanstack/react-query' export interface useCommentProps { @@ -15,8 +15,8 @@ const useComment = ({ modalOpen, handleOpenCurrentModal, }: useCommentProps) => { - const queryClient = useQueryClient() const [deleteCommentId, setDeleteCommentId] = useState(0) + const { mutate: deleteComment } = useDeleteComment(spaceId, parentCommentId) const handleDelete = useCallback( (commentId: number) => { @@ -27,18 +27,9 @@ const useComment = ({ [handleOpenCurrentModal, modalOpen], ) - const handleDeleteConfirm = useCallback( - async (isReply: boolean) => { - await fetchDeleteComment(spaceId, deleteCommentId) - await queryClient.invalidateQueries({ queryKey: ['comments', spaceId] }) - if (isReply) { - await queryClient.invalidateQueries({ - queryKey: ['replies', spaceId, parentCommentId], - }) - } - }, - [deleteCommentId, queryClient, spaceId, parentCommentId], - ) + const handleDeleteConfirm = useCallback(() => { + deleteComment({ commentId: deleteCommentId }) + }, [deleteCommentId, deleteComment]) return { handleDelete, diff --git a/src/components/common/FollowList/FollowList.tsx b/src/components/common/FollowList/FollowList.tsx index af7eb221..5fcb9cb7 100644 --- a/src/components/common/FollowList/FollowList.tsx +++ b/src/components/common/FollowList/FollowList.tsx @@ -2,13 +2,13 @@ import { Dispatch, Fragment, SetStateAction } from 'react' import { Spinner } from '@/components' import useFollowQuery from '@/components/common/FollowList/hooks/useFollowQuery' import useInfiniteScroll from '@/hooks/useInfiniteScroll' -import { FetchGetFollowProps } from '@/services/user/follow/follow' +import { IFollowList } from '@/models/member.model' import DeferredComponent from '../DeferedComponent/DeferedComponent' import User from '../User/User' export interface FollowListProps { memberId?: number - fetchFn: ({ pageNumber, pageSize }: FetchGetFollowProps) => Promise + fetchFn: ({ pageNumber, pageSize }: IFollowList) => Promise myId?: number type?: string followingCount?: number diff --git a/src/components/common/FollowList/hooks/useFollowQuery.ts b/src/components/common/FollowList/hooks/useFollowQuery.ts index d02a7341..7ca6ada3 100644 --- a/src/components/common/FollowList/hooks/useFollowQuery.ts +++ b/src/components/common/FollowList/hooks/useFollowQuery.ts @@ -1,11 +1,12 @@ import { FollowListProps } from '@/components/common/FollowList/FollowList' -import { INITIAL_PAGE_NUMBER, PAGE_SIZE } from '@/constants' +import { INITIAL_PAGE_NUMBER, PAGE_SIZE, QUERY_KEYS } from '@/constants' import { useInfiniteQuery } from '@tanstack/react-query' const useFollowQuery = ({ memberId, fetchFn, type }: FollowListProps) => { - const queryKey = type === 'following' || 'follower' + const queryKey = + type === 'following' ? QUERY_KEYS.FOLLOWING : QUERY_KEYS.FOLLOWERS const { data, fetchNextPage, hasNextPage, isLoading } = useInfiniteQuery({ - queryKey: ['follow', queryKey, memberId], + queryKey: [queryKey, memberId], queryFn: ({ pageParam }) => fetchFn({ memberId, diff --git a/src/components/common/Header/hooks/useHeader.ts b/src/components/common/Header/hooks/useHeader.ts index 9fff14ed..94b91bb5 100644 --- a/src/components/common/Header/hooks/useHeader.ts +++ b/src/components/common/Header/hooks/useHeader.ts @@ -1,6 +1,5 @@ import { useCallback, useEffect, useState } from 'react' -import { fetchGetUnCheckedNotifications } from '@/services/notification/invitations' -import { useQuery } from '@tanstack/react-query' +import { useGetUnCheckedNotifications } from '@/services/notification/useNotification' import { usePathname, useRouter, useSearchParams } from 'next/navigation' const useHeader = () => { @@ -10,10 +9,7 @@ const useHeader = () => { const [isSidebarOpen, setIsSidebarOpen] = useState(false) const isSearchModalOpen = searchParams.get('search') const currentPage = pathname.split(/\//)[1] - const { data } = useQuery({ - queryKey: ['notificationCount'], - queryFn: () => fetchGetUnCheckedNotifications(), - }) + const { data } = useGetUnCheckedNotifications() const createQueryString = useCallback( (name: string, value: string) => { diff --git a/src/components/common/LinkItem/LinkItem.tsx b/src/components/common/LinkItem/LinkItem.tsx index 237f802e..2d5d0a39 100644 --- a/src/components/common/LinkItem/LinkItem.tsx +++ b/src/components/common/LinkItem/LinkItem.tsx @@ -5,6 +5,7 @@ import { useForm } from 'react-hook-form' import TagInput from '@/components/TagInput/TagInput' import { useModal } from '@/hooks' import { useCurrentUser } from '@/hooks/useCurrentUser' +import { useDeleteLink } from '@/services/link/useLink' import { DocumentTextIcon, HeartIcon as HeartIconOutline, @@ -25,13 +26,12 @@ import { LINK_FORM_PLACEHOLDER, LINK_FORM_VALIDATION, } from '../LinkList/constants' -import useGetMeta from '../LinkList/hooks/useGetMeta' +import useGetMetaData from '../LinkList/hooks/useGetMetaData' import LoginModal from '../Modal/LoginModal' import NoneServiceModal from '../Modal/NoneServiceModal' -import { RefetchTagsType, Tag } from '../Space/hooks/useGetTags' +import { Tag } from '../Space/hooks/useGetTags' import Spinner from '../Spinner/Spinner' import { DELETE_TEXT } from './constants' -import useDeleteLink from './hooks/useDeleteLink' import useLikeLink from './hooks/useLikeLink' import useReadSaveLink from './hooks/useReadSaveLink' import useUpdateLink from './hooks/useUpdateLink' @@ -52,7 +52,6 @@ export interface LinkItemProps { isMember?: boolean type?: 'list' | 'card' tags?: Tag[] - refetchTags?: RefetchTagsType } const LinkItem = ({ @@ -71,7 +70,6 @@ const LinkItem = ({ isMember, type = 'list', tags, - refetchTags, }: LinkItemProps) => { const { isLoggedIn } = useCurrentUser() const { Modal, isOpen, modalClose, currentModal, handleOpenCurrentModal } = @@ -102,23 +100,22 @@ const LinkItem = ({ handleModalClose, handleChangeUrl, handleGetMeta, - } = useGetMeta({ getValues, setValue, modalClose }) + } = useGetMetaData({ getValues, setValue, modalClose }) const { isUpdateLinkLoading, handleUpdateLink } = useUpdateLink({ spaceId, linkId, - refetchTags, }) - const { isDeleteLinkLoading, handleDeleteLink } = useDeleteLink({ - refetchTags, + const { mutate: deleteLink, isPending: isDeleteLinkLoading } = useDeleteLink({ + spaceId, + linkId, }) - const { handleSaveReadInfo } = useReadSaveLink() + const { handleSaveReadInfo } = useReadSaveLink({ spaceId, linkId }) const { isLiked, likeCount, handleClickLike } = useLikeLink({ spaceId, linkId, isLikedValue: isInitLiked, likeCountValue: likeInitCount, }) - return ( <> {type === 'list' ? ( @@ -312,7 +309,10 @@ const LinkItem = ({ } })() })() - : spaceId && handleDeleteLink({ spaceId, linkId }) + : spaceId && + (() => { + deleteLink() + })() } type="form"> {currentModal === 'update' && ( diff --git a/src/components/common/LinkItem/hooks/useDeleteLink.ts b/src/components/common/LinkItem/hooks/useDeleteLink.ts deleted file mode 100644 index 06a8a3aa..00000000 --- a/src/components/common/LinkItem/hooks/useDeleteLink.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { useState } from 'react' -import { FetchDeleteLinkProps, fetchDeleteLink } from '@/services/link/link' -import { useQueryClient } from '@tanstack/react-query' -import { RefetchTagsType } from '../../Space/hooks/useGetTags' - -export interface UseDeleteLinkProps { - refetchTags?: RefetchTagsType -} - -const useDeleteLink = ({ refetchTags }: UseDeleteLinkProps) => { - const queryClient = useQueryClient() - const [isDeleteLinkLoading, setIsDeleteLinkLoading] = useState(false) - - const handleDeleteLink = async ({ - spaceId, - linkId, - }: FetchDeleteLinkProps) => { - if (isDeleteLinkLoading) return - - setIsDeleteLinkLoading(true) - await fetchDeleteLink({ spaceId, linkId }) - await queryClient.invalidateQueries({ queryKey: ['links', spaceId] }) - refetchTags?.() - setIsDeleteLinkLoading(false) - } - - return { isDeleteLinkLoading, handleDeleteLink } -} - -export default useDeleteLink diff --git a/src/components/common/LinkItem/hooks/useLikeLink.ts b/src/components/common/LinkItem/hooks/useLikeLink.ts index 60243cc9..7bb76faa 100644 --- a/src/components/common/LinkItem/hooks/useLikeLink.ts +++ b/src/components/common/LinkItem/hooks/useLikeLink.ts @@ -1,6 +1,5 @@ import { useCallback, useMemo, useState } from 'react' -import { fetchLikeLink, fetchUnLikeLink } from '@/services/link/link' -import { useQueryClient } from '@tanstack/react-query' +import { useDeleteLikeLink, usePostLikeLink } from '@/services/link/useLink' import { debounce } from 'lodash' import useToggle from '../../Toggle/hooks/useToggle' @@ -12,31 +11,30 @@ export interface UseLikeLinkProps { } const useLikeLink = ({ - spaceId, linkId, isLikedValue, likeCountValue, }: UseLikeLinkProps) => { - const queryClient = useQueryClient() const [isLiked, likeToggle] = useToggle(isLikedValue) const [likeCount, setLikeCount] = useState(likeCountValue) + const { mutate: deleteLikeLink } = useDeleteLikeLink() + const { mutate: postLikeLink } = usePostLikeLink() + const debounceUnLikeLink = useMemo( () => debounce(async () => { - await fetchUnLikeLink({ linkId }) - await queryClient.invalidateQueries({ queryKey: ['links', spaceId] }) + await deleteLikeLink({ linkId }) }, 300), - [spaceId, linkId, queryClient], + [deleteLikeLink, linkId], ) const debounceLikeLink = useMemo( () => debounce(async () => { - await fetchLikeLink({ linkId }) - await queryClient.invalidateQueries({ queryKey: ['links', spaceId] }) + await postLikeLink({ linkId }) }, 300), - [spaceId, linkId, queryClient], + [postLikeLink, linkId], ) const handleClickLike = useCallback( diff --git a/src/components/common/LinkItem/hooks/useReadSaveLink.ts b/src/components/common/LinkItem/hooks/useReadSaveLink.ts index 6617c728..36d642c0 100644 --- a/src/components/common/LinkItem/hooks/useReadSaveLink.ts +++ b/src/components/common/LinkItem/hooks/useReadSaveLink.ts @@ -1,18 +1,18 @@ import { useCurrentUser } from '@/hooks/useCurrentUser' -import { fetchReadSaveLink } from '@/services/link/link' +import { usePostReadSaveLink } from '@/services/link/useLink' export interface HandleSaveReadInfoProps { spaceId?: number linkId: number } -const useReadSaveLink = () => { +const useReadSaveLink = ({ spaceId }: HandleSaveReadInfoProps) => { const { isLoggedIn } = useCurrentUser() + const { mutate: postReadSaveLink } = usePostReadSaveLink(spaceId) const handleSaveReadInfo = ({ spaceId, linkId }: HandleSaveReadInfoProps) => { if (spaceId && linkId && isLoggedIn) { - fetchReadSaveLink({ spaceId, linkId }) - console.log('접속 저장됨') + postReadSaveLink({ spaceId, linkId }) } } diff --git a/src/components/common/LinkItem/hooks/useUpdateLink.ts b/src/components/common/LinkItem/hooks/useUpdateLink.ts index 399b9694..bacc14e1 100644 --- a/src/components/common/LinkItem/hooks/useUpdateLink.ts +++ b/src/components/common/LinkItem/hooks/useUpdateLink.ts @@ -1,6 +1,5 @@ -import { useState } from 'react' -import { fetchUpdateLink } from '@/services/link/link' -import { useQueryClient } from '@tanstack/react-query' +import { useEffect } from 'react' +import { usePutLink } from '@/services/link/useLink' import { RefetchTagsType } from '../../Space/hooks/useGetTags' interface HandleUpdateLinkProps { @@ -13,16 +12,12 @@ interface HandleUpdateLinkProps { interface UseUpdateLinkProps { spaceId?: number linkId: number - refetchTags?: RefetchTagsType } -const useUpdateLink = ({ - spaceId, - linkId, - refetchTags, -}: UseUpdateLinkProps) => { - const queryClient = useQueryClient() - const [isUpdateLinkLoading, setIsUpdateLinkLoading] = useState(false) +const useUpdateLink = ({ spaceId, linkId }: UseUpdateLinkProps) => { + const { mutate: updateLink, isPending: isUpdateLinkLoading } = + usePutLink(spaceId) + const handleUpdateLink = async ({ url, title, @@ -30,19 +25,7 @@ const useUpdateLink = ({ color = 'emerald', }: HandleUpdateLinkProps) => { if (isUpdateLinkLoading) return - - setIsUpdateLinkLoading(true) - await fetchUpdateLink({ - spaceId, - linkId, - url, - title, - tagName, - color, - }) - await queryClient.invalidateQueries({ queryKey: ['links', spaceId] }) - refetchTags?.() - setIsUpdateLinkLoading(false) + updateLink({ spaceId, linkId, url, title, tagName, color }) } return { isUpdateLinkLoading, handleUpdateLink } diff --git a/src/components/common/LinkList/LinkList.tsx b/src/components/common/LinkList/LinkList.tsx index 6a75711e..5a913d29 100644 --- a/src/components/common/LinkList/LinkList.tsx +++ b/src/components/common/LinkList/LinkList.tsx @@ -4,15 +4,13 @@ import { useForm } from 'react-hook-form' import { Spinner } from '@/components' import TagInput from '@/components/TagInput/TagInput' import { useModal } from '@/hooks' -import { GetLinksReqBody } from '@/types' +import { GetLinksReqBody, Tag } from '@/types' import { cls } from '@/utils' import Button from '../Button/Button' import { ChipColors } from '../Chip/Chip' import DeferredComponent from '../DeferedComponent/DeferedComponent' import Input from '../Input/Input' import LinkItem from '../LinkItem/LinkItem' -import { Tag } from '../Space/hooks/useGetTags' -import { RefetchTagsType } from '../Space/hooks/useGetTags' import { ADD_LINK_TEXT, LINK_FORM, @@ -22,7 +20,7 @@ import { NONE_LINK_RESULT, } from './constants' import useCreateLink from './hooks/useCreateLink' -import useGetMeta from './hooks/useGetMeta' +import useGetMetaData from './hooks/useGetMetaData' import useLinksQuery from './hooks/useLinksQuery' export interface linkViewHistories { @@ -44,7 +42,7 @@ export interface Link { } export interface LinkListProps { - spaceId?: number + spaceId: number read?: boolean summary?: boolean edit?: boolean @@ -55,7 +53,6 @@ export interface LinkListProps { tags: Tag[] isCanEdit: boolean isMember: boolean - refetchTags?: RefetchTagsType } export interface CreateLinkFormValue { @@ -77,12 +74,10 @@ const LinkList = ({ tags, isCanEdit, isMember, - refetchTags, }: LinkListProps) => { const { Modal, isOpen, modalOpen, modalClose } = useModal() const { isCreateLinkLoading, handleCreateLink } = useCreateLink({ spaceId, - refetchTags, }) const { register, @@ -110,7 +105,7 @@ const LinkList = ({ handleModalClose, handleChangeUrl, handleGetMeta, - } = useGetMeta({ getValues, setValue, modalClose }) + } = useGetMetaData({ getValues, setValue, modalClose }) const { links, fetchNextPage, hasNextPage, isLinksLoading } = useLinksQuery({ spaceId, fetchFn, @@ -145,7 +140,7 @@ const LinkList = ({ )} <> - {links?.pages[0].responses.length ? ( + {links?.pages[0].responses?.length ? ( links?.pages.map((group) => group.responses.map((link: Link) => ( )), diff --git a/src/components/common/LinkList/hooks/useCreateLink.ts b/src/components/common/LinkList/hooks/useCreateLink.ts index 02f761e3..3a6ddc7e 100644 --- a/src/components/common/LinkList/hooks/useCreateLink.ts +++ b/src/components/common/LinkList/hooks/useCreateLink.ts @@ -1,14 +1,10 @@ 'use client' -import { useState } from 'react' -import { FetchCreateLinkProps, fetchCreateLink } from '@/services/link/link' -import { fetchGetTags } from '@/services/space/space' -import { useQueryClient } from '@tanstack/react-query' -import { RefetchTagsType } from '../../Space/hooks/useGetTags' +import { IUpdateLink } from '@/models/link.model' +import { usePostLink } from '@/services/link/useLink' export interface UseCreateLinkProps { spaceId?: number - refetchTags?: RefetchTagsType } export interface UseCreateLinkReturnType { isCreateLinkLoading: boolean @@ -17,39 +13,23 @@ export interface UseCreateLinkReturnType { title, tagName, color, - }: FetchCreateLinkProps) => Promise + }: IUpdateLink['query']) => void } const useCreateLink = ({ spaceId, - refetchTags, }: UseCreateLinkProps): UseCreateLinkReturnType => { - const queryclient = useQueryClient() - const [isCreateLinkLoading, setIsCreateLinkLoading] = useState(false) + const { mutate: createLink, isPending: isCreateLinkLoading } = + usePostLink(spaceId) - const handleCreateLink = async ({ + const handleCreateLink = ({ url, title, tagName, color, - }: FetchCreateLinkProps) => { + }: IUpdateLink['query']) => { if (isCreateLinkLoading) return - - setIsCreateLinkLoading(true) - await fetchCreateLink({ - spaceId, - url, - title, - tagName, - color, - }) - - await fetchGetTags({ - spaceId, - }) - await queryclient.invalidateQueries({ queryKey: ['links', spaceId] }) - refetchTags?.() - setIsCreateLinkLoading(false) + createLink({ spaceId, url, title, tagName, color }) } return { diff --git a/src/components/common/LinkList/hooks/useGetMeta.ts b/src/components/common/LinkList/hooks/useGetMetaData.ts similarity index 79% rename from src/components/common/LinkList/hooks/useGetMeta.ts rename to src/components/common/LinkList/hooks/useGetMetaData.ts index d3c32df1..cc3abcd5 100644 --- a/src/components/common/LinkList/hooks/useGetMeta.ts +++ b/src/components/common/LinkList/hooks/useGetMetaData.ts @@ -1,20 +1,24 @@ import { useState } from 'react' import { UseFormGetValues, UseFormSetValue } from 'react-hook-form' -import { FetchGetMetaProps, fetchGetMeta } from '@/services/meta/meta' +import { usePostMeta } from '@/services/meta/useMeta' import { CreateLinkFormValue } from '../LinkList' import { LINK_FORM_VALIDATION } from '../constants' -export interface UseGetMetaProps { +export interface UseGetMetaDataProps { getValues: UseFormGetValues setValue: UseFormSetValue modalClose: VoidFunction } -const useGetMeta = ({ getValues, setValue, modalClose }: UseGetMetaProps) => { +const useGetMetaData = ({ + getValues, + setValue, + modalClose, +}: UseGetMetaDataProps) => { const [isUrlCheck, setIsUrlCheck] = useState(false) const [urlErrorText, setUrlErrorText] = useState('') const [isShowFormError, setIsShowFormError] = useState(false) - const [isMetaLoading, setIsMetaLoading] = useState(false) + const { mutateAsync: postMeta, isPending: isMetaLoading } = usePostMeta() const getIsValidUrl = () => { const url = getValues('url') @@ -40,16 +44,13 @@ const useGetMeta = ({ getValues, setValue, modalClose }: UseGetMetaProps) => { } } - const handleGetMeta = async ({ url }: FetchGetMetaProps) => { + const handleGetMeta = async ({ url }: { url: string }) => { if (isMetaLoading) return - if (getIsValidUrl()) { - setIsMetaLoading(true) - const { data, error } = await fetchGetMeta({ + const { data, error } = await postMeta({ url, }) handleUrlValidation({ data, error }) - setIsMetaLoading(false) } else { setUrlErrorText(LINK_FORM_VALIDATION.URL_INVALID_FORM) } @@ -88,4 +89,4 @@ const useGetMeta = ({ getValues, setValue, modalClose }: UseGetMetaProps) => { } } -export default useGetMeta +export default useGetMetaData diff --git a/src/components/common/LinkList/hooks/useLinksQuery.ts b/src/components/common/LinkList/hooks/useLinksQuery.ts index d66c0f6c..b208b89c 100644 --- a/src/components/common/LinkList/hooks/useLinksQuery.ts +++ b/src/components/common/LinkList/hooks/useLinksQuery.ts @@ -1,4 +1,4 @@ -import { INITIAL_PAGE_NUMBER, PAGE_SIZE } from '@/constants' +import { INITIAL_PAGE_NUMBER, PAGE_SIZE, QUERY_KEYS } from '@/constants' import { GetLinksReqBody } from '@/types' import { useInfiniteQuery } from '@tanstack/react-query' @@ -17,7 +17,7 @@ const useLinksQuery = ({ const sortValue = sort === 'like' ? 'popular' : 'created_at' const { data, fetchNextPage, hasNextPage, isLoading } = useInfiniteQuery({ queryKey: [ - 'links', + QUERY_KEYS.LINKS, spaceId, { ...(sortValue && { sort: sortValue }), ...(tagId && { tagId: tagId }) }, ], diff --git a/src/components/common/MainSpaceList/HydrateMainSpaceList.tsx b/src/components/common/MainSpaceList/HydrateMainSpaceList.tsx index 38b421a3..1eb9fc17 100644 --- a/src/components/common/MainSpaceList/HydrateMainSpaceList.tsx +++ b/src/components/common/MainSpaceList/HydrateMainSpaceList.tsx @@ -1,6 +1,6 @@ import { PAGE_SIZE } from '@/constants' import { getQueryClient } from '@/lib/queryClient' -import { fetchGetSpaces } from '@/services/space/spaces' +import { fetchGetSpaces } from '@/services/space/useSpaces' import { HydrationBoundary, dehydrate } from '@tanstack/react-query' import MainSpaceList from './MainSpaceList' @@ -19,9 +19,6 @@ const HydrateMainSpaceList = () => { initialPageParam: 0, }) - const dehydreatedState = dehydrate(queryClient) - console.log(dehydreatedState) - return ( diff --git a/src/components/common/MainSpaceList/MainSpaceList.tsx b/src/components/common/MainSpaceList/MainSpaceList.tsx index 722becd7..94f7e4ec 100644 --- a/src/components/common/MainSpaceList/MainSpaceList.tsx +++ b/src/components/common/MainSpaceList/MainSpaceList.tsx @@ -6,9 +6,8 @@ import useMainSpacesQuery from '@/components/SpaceList/hooks/useMainSpacesQuery' import { CATEGORIES_RENDER } from '@/constants' import { useCategoryParam, useSortParam } from '@/hooks' import useInfiniteScroll from '@/hooks/useInfiniteScroll' -import { fetchGetSpaces } from '@/services/space/spaces' +import { fetchGetSpaces } from '@/services/space/useSpaces' import { SpaceResBody } from '@/types' -import { useQueryClient } from '@tanstack/react-query' import { MORE_TEXT } from '../LinkList/constants' import Space from '../Space/Space' @@ -30,9 +29,6 @@ const MainSpaceList = ({ memberId, keyword }: SpaceListProps) => { fetchFn: fetchGetSpaces, }) - const queryClient = useQueryClient() - console.log(queryClient.getQueryCache()) - const { target } = useInfiniteScroll({ hasNextPage, fetchNextPage }) return ( diff --git a/src/components/common/Notification/Notification.tsx b/src/components/common/Notification/Notification.tsx index 96449805..611b9cee 100644 --- a/src/components/common/Notification/Notification.tsx +++ b/src/components/common/Notification/Notification.tsx @@ -4,7 +4,7 @@ import { cls } from '@/utils' import { XMarkIcon } from '@heroicons/react/24/solid' import Button from '../Button/Button' import { NOTIFICATION_MSG } from './constants' -import useNotification from './hooks/useNotification' +import useNotificationPopup from './hooks/useNotificationPopup' export interface NotificationProps { notificationId: number @@ -32,7 +32,7 @@ const Notification = ({ onClose, }: NotificationProps) => { const { handleClickUser, handleClickSpace, handleClickComment } = - useNotification() + useNotificationPopup() return (
void } -const useNotification = (): UseNotificationReturn => { +const useNotificationPopup = (): UseNotificationReturn => { const router = useRouter() const handleClickUser = ({ @@ -71,4 +71,4 @@ const useNotification = (): UseNotificationReturn => { return { handleClickUser, handleClickSpace, handleClickComment } } -export default useNotification +export default useNotificationPopup diff --git a/src/components/common/NotificationList/NotificationList.tsx b/src/components/common/NotificationList/NotificationList.tsx index 73114c1a..e510e204 100644 --- a/src/components/common/NotificationList/NotificationList.tsx +++ b/src/components/common/NotificationList/NotificationList.tsx @@ -27,8 +27,8 @@ const NotificationList = ({ fetchFn, type }: NotificationListProps) => { type, }) const { target } = useInfiniteScroll({ hasNextPage, fetchNextPage }) - const { handleAcceptInvite } = useAcceptNotification({ type }) - const { handleDeleteNotification } = useDeleteNotification({ type }) + const { handleAcceptInvite } = useAcceptNotification() + const { handleDeleteNotification } = useDeleteNotification() return isNotificationLoading ? ( diff --git a/src/components/common/NotificationList/hooks/useAcceptNotification.ts b/src/components/common/NotificationList/hooks/useAcceptNotification.ts index 3af3c9af..abc2a554 100644 --- a/src/components/common/NotificationList/hooks/useAcceptNotification.ts +++ b/src/components/common/NotificationList/hooks/useAcceptNotification.ts @@ -1,32 +1,24 @@ -import { fetchAccetpSpaceInvitation } from '@/services/space/spaces' -import { useQueryClient } from '@tanstack/react-query' +import { usePostAccetpSpaceInvitation } from '@/services/space/useSpaces' import { useRouter } from 'next/navigation' import { notify } from '../../Toast/Toast' import { NOTIFICATION_INVITE } from '../constants' -export interface UseAcceptNotificationProps { - type: 'FOLLOW' | 'COMMENT' | 'INVITATION' -} - export interface HandleAcceptInviteProps { notificationId: number } -const useAcceptNotification = ({ type }: UseAcceptNotificationProps) => { +const useAcceptNotification = () => { const router = useRouter() - const queryclient = useQueryClient() + + const { mutateAsync: acceptInvitation } = usePostAccetpSpaceInvitation() const handleAcceptInvite = async ({ notificationId, }: HandleAcceptInviteProps) => { try { - const { spaceId } = await fetchAccetpSpaceInvitation({ notificationId }) + const { spaceId } = await acceptInvitation({ notificationId }) notify('success', NOTIFICATION_INVITE.SUCCESS) router.push(`/space/${spaceId}`) - await queryclient.invalidateQueries({ queryKey: ['notification', type] }) - await queryclient.invalidateQueries({ - queryKey: ['notificationCount'], - }) } catch (e) { console.error(e) } diff --git a/src/components/common/NotificationList/hooks/useDeleteNotification.ts b/src/components/common/NotificationList/hooks/useDeleteNotification.ts index a90f1f1c..87e4c0e2 100644 --- a/src/components/common/NotificationList/hooks/useDeleteNotification.ts +++ b/src/components/common/NotificationList/hooks/useDeleteNotification.ts @@ -1,22 +1,16 @@ -import { - FetchDeleteNotificationProps, - fetchDeleteNotification, -} from '@/services/notification' -import { useQueryClient } from '@tanstack/react-query' +import { useDeleteNotifications } from '@/services/notification/useNotification' -export interface UseDeleteNotificationProps { - type: 'FOLLOW' | 'COMMENT' | 'INVITATION' +export interface FetchDeleteNotificationProps { + notificationId: number } -const useDeleteNotification = ({ type }: UseDeleteNotificationProps) => { - const queryclient = useQueryClient() +const useDeleteNotification = () => { + const { mutate: deleteNotification } = useDeleteNotifications() const handleDeleteNotification = async ({ notificationId, }: FetchDeleteNotificationProps) => { - await fetchDeleteNotification({ notificationId }) - await queryclient.invalidateQueries({ queryKey: ['notification', type] }) - await queryclient.invalidateQueries({ queryKey: ['notificationCount'] }) + deleteNotification(notificationId) } return { handleDeleteNotification } diff --git a/src/components/common/NotificationList/hooks/useNotificationQuery.ts b/src/components/common/NotificationList/hooks/useNotificationQuery.ts index 21940066..8e65c1f4 100644 --- a/src/components/common/NotificationList/hooks/useNotificationQuery.ts +++ b/src/components/common/NotificationList/hooks/useNotificationQuery.ts @@ -1,11 +1,10 @@ import { FollowListProps } from '@/components/common/FollowList/FollowList' -import { INITIAL_PAGE_NUMBER, PAGE_SIZE } from '@/constants' +import { INITIAL_PAGE_NUMBER, PAGE_SIZE, QUERY_KEYS } from '@/constants' import { useInfiniteQuery } from '@tanstack/react-query' -const useNotificationQuery = ({ fetchFn, type }: FollowListProps) => { - const queryKey = type +const useNotificationQuery = ({ fetchFn }: FollowListProps) => { const { data, fetchNextPage, hasNextPage, isLoading } = useInfiniteQuery({ - queryKey: ['notification', queryKey], + queryKey: [QUERY_KEYS.INVITATIONS], queryFn: ({ pageParam }) => fetchFn({ pageNumber: pageParam, diff --git a/src/components/common/Sidebar/Sidebar.tsx b/src/components/common/Sidebar/Sidebar.tsx index 891dcf76..d09e2d5e 100644 --- a/src/components/common/Sidebar/Sidebar.tsx +++ b/src/components/common/Sidebar/Sidebar.tsx @@ -3,6 +3,7 @@ import { useRef, useState } from 'react' import { Spinner } from '@/components' import { useCurrentUser } from '@/hooks/useCurrentUser' +import { useGetUserProfile } from '@/services/users/useUsers' import { cls } from '@/utils' import { PlusIcon } from '@heroicons/react/20/solid' import { UserIcon } from '@heroicons/react/24/outline' @@ -28,6 +29,7 @@ export interface SidebarProps { const Sidebar = ({ isSidebarOpen, onClose }: SidebarProps) => { const [isOpen, setIsOpen] = useState(isSidebarOpen) const { currentUser } = useCurrentUser() + const { data: user } = useGetUserProfile(currentUser?.memberId!) const sidebarRef = useRef(null) const { @@ -67,7 +69,7 @@ const Sidebar = ({ isSidebarOpen, onClose }: SidebarProps) => { isOpen ? 'animate-openSidebar' : 'animate-closeSidebar', )}>
- {currentUser ? ( + {user ? ( isSideBarLoading ? ( @@ -77,12 +79,12 @@ const Sidebar = ({ isSidebarOpen, onClose }: SidebarProps) => {

- {currentUser.nickname} + {user.nickname}

내 프로필 @@ -137,7 +139,7 @@ const Sidebar = ({ isSidebarOpen, onClose }: SidebarProps) => { 스페이스 전체보기 diff --git a/src/components/common/Sidebar/hooks/useMySpace.ts b/src/components/common/Sidebar/hooks/useMySpace.ts index 10f8ea92..47750d31 100644 --- a/src/components/common/Sidebar/hooks/useMySpace.ts +++ b/src/components/common/Sidebar/hooks/useMySpace.ts @@ -1,6 +1,8 @@ import { useEffect, useMemo, useState } from 'react' -import { fetchSearchMySpaces } from '@/services/space/spaces' -import { fetchGetMyFavoriteSpaces } from '@/services/user/profile/favorites' +import { + fetchGetMyFavoriteSpaces, + fetchSearchMySpaces, +} from '@/services/space/useSpaces' import { SearchMySpaceReqBody, SearchMySpaceResBody } from '@/types' const useMySpace = ( diff --git a/src/components/common/Sidebar/hooks/useSidebar.ts b/src/components/common/Sidebar/hooks/useSidebar.ts index 96f46c34..ed098a4e 100644 --- a/src/components/common/Sidebar/hooks/useSidebar.ts +++ b/src/components/common/Sidebar/hooks/useSidebar.ts @@ -1,5 +1,5 @@ import { Dispatch, SetStateAction, useState } from 'react' -import { kakaoLogout } from '@/services/auth' +import { useKakaoLogout } from '@/services/auth/useAuth' import Cookies from 'js-cookie' import { notify } from '../../Toast/Toast' @@ -13,7 +13,7 @@ export interface useSidebarProps { const useSidebar = ({ sidebarRef, setIsOpen, onClose }: useSidebarProps) => { const [spaceType, setSpaceType] = useState('내 스페이스') - + const { mutateAsync: kakaoLogout } = useKakaoLogout() const logout = async () => { try { await kakaoLogout() diff --git a/src/components/common/Space/hooks/useFavorites.ts b/src/components/common/Space/hooks/useFavorites.ts index e3e6566a..f37e38a7 100644 --- a/src/components/common/Space/hooks/useFavorites.ts +++ b/src/components/common/Space/hooks/useFavorites.ts @@ -1,8 +1,8 @@ import { useCallback, useMemo, useState } from 'react' import { - fetchFavoriteSpace, - fetchUnFavoriteSpace, -} from '@/services/space/space' + useDeleteFavoriteSpace, + usePostFavoriteSpace, +} from '@/services/space/useSpace' import { debounce } from 'lodash' import useToggle from '../../Toggle/hooks/useToggle' @@ -19,21 +19,23 @@ const useFavorites = ({ }: UseFavoritesProps) => { const [isFavorites, favoritesToggle] = useToggle(hasFavorite) const [favoritesCount, setFavoritesCount] = useState(favorite) + const { mutateAsync: postFavoriteSpace } = usePostFavoriteSpace() + const { mutateAsync: deleteFavoriteSpace } = useDeleteFavoriteSpace() const debounceUnFetchSpace = useMemo( () => debounce(async () => { - await fetchUnFavoriteSpace({ spaceId }) + await deleteFavoriteSpace({ spaceId }) }, 300), - [spaceId], + [spaceId, deleteFavoriteSpace], ) const debouncefetchSpace = useMemo( () => debounce(async () => { - await fetchFavoriteSpace({ spaceId }) + await postFavoriteSpace({ spaceId }) }, 300), - [spaceId], + [spaceId, postFavoriteSpace], ) const handleClickFavorite = useCallback( diff --git a/src/components/common/Space/hooks/useGetSpace.ts b/src/components/common/Space/hooks/useGetSpace.ts deleted file mode 100644 index e911d3af..00000000 --- a/src/components/common/Space/hooks/useGetSpace.ts +++ /dev/null @@ -1,38 +0,0 @@ -'use client' - -import { - Dispatch, - SetStateAction, - useCallback, - useEffect, - useState, -} from 'react' -import { fetchGetSpace } from '@/services/space/space' -import { SpaceDetailResBody } from '@/types' -import { usePathname } from 'next/navigation' - -const useGetSpace = (): { - space: SpaceDetailResBody | undefined - setSpace: Dispatch> - isSpaceLoading: boolean -} => { - const [space, setSpace] = useState() - const [isLoading, setIsLoading] = useState(false) - const path = usePathname() - const spaceId = Number(path.split('/')[2]) - - const handleGetSpace = useCallback(async () => { - setIsLoading(true) - const data = spaceId && (await fetchGetSpace({ spaceId })) - setSpace(data) - setIsLoading(false) - }, [spaceId, setIsLoading]) - - useEffect(() => { - handleGetSpace() - }, [handleGetSpace]) - - return { space, setSpace, isSpaceLoading: isLoading } -} - -export default useGetSpace diff --git a/src/components/common/Space/hooks/useGetTags.ts b/src/components/common/Space/hooks/useGetTags.ts index de473000..8aa4e8e2 100644 --- a/src/components/common/Space/hooks/useGetTags.ts +++ b/src/components/common/Space/hooks/useGetTags.ts @@ -1,4 +1,3 @@ -import { fetchGetTags } from '@/services/space/space' import { QueryObserverResult, RefetchOptions, @@ -26,18 +25,18 @@ export interface UseGetTagsReturnType { isTagsLoading: boolean } -const useGetTags = ({ spaceId }: UseGetTagsProps): UseGetTagsReturnType => { - const { - data, - refetch: refetchTags, - isLoading, - } = useQuery({ - queryKey: ['tagList', spaceId], - queryFn: () => fetchGetTags({ spaceId }), - enabled: !!spaceId, - }) +// const useGetTagss = ({ spaceId }: UseGetTagsProps): UseGetTagsReturnType => { +// const { +// data, +// refetch: refetchTags, +// isLoading, +// } = useQuery({ +// queryKey: ['tagList', spaceId], +// queryFn: () => fetchGetTags({ spaceId }), +// enabled: !!spaceId, +// }) - return { tags: data?.tags, refetchTags, isTagsLoading: isLoading } -} +// return { tags: data?.tags, refetchTags, isTagsLoading: isLoading } +// } -export default useGetTags +// export default useGetTagss diff --git a/src/components/common/SpaceMemberList/SpaceMemberList.tsx b/src/components/common/SpaceMemberList/SpaceMemberList.tsx index 92892466..d9c6b1ea 100644 --- a/src/components/common/SpaceMemberList/SpaceMemberList.tsx +++ b/src/components/common/SpaceMemberList/SpaceMemberList.tsx @@ -3,8 +3,8 @@ import { useForm } from 'react-hook-form' import { Input } from '@/components' import { useModal } from '@/hooks' -import { fetchInviteSpace } from '@/services/space/invitation' -import { fetchPatchRole } from '@/services/space/space' +import { usePatchRole } from '@/services/space/useSpace' +import { usePostInviteSpace } from '@/services/space/useSpaces' import { UserDetailInfo } from '@/types' import { PlusSmallIcon } from '@heroicons/react/24/solid' import { useRouter } from 'next/navigation' @@ -50,10 +50,11 @@ const SpaceMemberList = ({ }, }) const { Modal, isOpen, modalOpen, modalClose } = useModal(false) - + const { mutate: patchRole } = usePatchRole(spaceId) + const { mutateAsync: inviteSpace } = usePostInviteSpace() const handleChangeRole = async (data: ChangeRoleProps) => { try { - spaceId && (await fetchPatchRole(spaceId, data)) + spaceId && patchRole({ ...data }) alert('권한을 수정했습니다.') } catch (e) { alert('권한 수정에 실패했습니다.') @@ -84,7 +85,7 @@ const SpaceMemberList = ({ reset() }} onConfirm={handleSubmit(async ({ email, role }) => { - const res = await fetchInviteSpace({ spaceId, email, role }) + const res = await inviteSpace({ spaceId, email, role }) if (res.errorCode === 'N001') { notify('error', '이미 초대 요청을 보낸 유저입니다.') } else if (res.errorCode === 'G004') { diff --git a/src/components/common/User/User.tsx b/src/components/common/User/User.tsx index 1f12d29e..213207f9 100644 --- a/src/components/common/User/User.tsx +++ b/src/components/common/User/User.tsx @@ -38,6 +38,7 @@ const User = ({ useModal() const { isFollowing: isFollowingValue, handleClickListInFollow } = useFollowUser({ + profileId: profileId || 0, memberId: memberId || 0, isInitFollowing: !!isFollowing, followerInitCount: followingCount || 0, diff --git a/src/constants/index.ts b/src/constants/index.ts index 4dcefc74..56b03052 100644 --- a/src/constants/index.ts +++ b/src/constants/index.ts @@ -47,3 +47,18 @@ export const NOT_FOUND = { TEXT_2: '존재하지 않는 주소를 입력하셨거나,', TEXT_3: '요청하신 페이지의 주소가 변경, 삭제되어 찾을 수 없습니다.', } + +export const QUERY_KEYS = { + SPACES: 'spaces', + COMMENTS: 'comments', + REPLIES: 'replies', + TAGS: 'tags', + LINKS: 'links', + POPULAR_LINKS: 'popularLinks', + MEMBERS: 'members', + FOLLOWING: 'following', + FOLLOWERS: 'followers', + NOTIFICATION_COUNT: 'notificationCount', + INVITATIONS: 'invitations', + VALIDATE_TOKEN: 'validateToken', +} diff --git a/src/hooks/useCurrentUser.ts b/src/hooks/useCurrentUser.ts index 5cb65c9d..f29184fe 100644 --- a/src/hooks/useCurrentUser.ts +++ b/src/hooks/useCurrentUser.ts @@ -1,5 +1,6 @@ import { useEffect, useState } from 'react' -import { validateToken } from '@/services/auth' +import { QUERY_KEYS } from '@/constants' +import { validateToken } from '@/services/auth/useAuth' import { UserProfileResBody } from '@/types' import { useQuery } from '@tanstack/react-query' import Cookies from 'js-cookie' @@ -8,7 +9,7 @@ import { usePathname } from 'next/navigation' export const useValidate = () => { const token = Cookies.get('Auth-token') return useQuery({ - queryKey: ['validateToken', token], + queryKey: [QUERY_KEYS.VALIDATE_TOKEN, token], queryFn: async () => { const res = await validateToken() if (!res) { diff --git a/src/hooks/useFollowUser.ts b/src/hooks/useFollowUser.ts index 36689340..94acef6d 100644 --- a/src/hooks/useFollowUser.ts +++ b/src/hooks/useFollowUser.ts @@ -1,13 +1,11 @@ import { useCallback, useEffect, useMemo, useState } from 'react' -import { - fetchFollowUser, - fetchUnFollowUser, -} from '@/services/user/follow/follow' +import { useDeleteFollow, usePostFollow } from '@/services/users/useUsers' import { useQueryClient } from '@tanstack/react-query' import { debounce } from 'lodash' import { useCurrentUser } from './useCurrentUser' export interface UseFollowUserProps { + profileId?: number memberId: number isInitFollowing: boolean followingInitCount?: number @@ -16,6 +14,7 @@ export interface UseFollowUserProps { } const useFollowUser = ({ + profileId, memberId, isInitFollowing, followerInitCount, @@ -27,6 +26,8 @@ const useFollowUser = ({ const [isFollowing, setIsFollowing] = useState(isInitFollowing) const [followingCount, setFollowingCount] = useState(followingInitCount) const [followerCount, setFollowerCount] = useState(followerInitCount) + const { mutateAsync: postFollow } = usePostFollow(profileId) + const { mutateAsync: deleteFollow } = useDeleteFollow(profileId) useEffect(() => { setIsFollowing(isInitFollowing) @@ -44,24 +45,20 @@ const useFollowUser = ({ () => debounce(async () => { if (memberId) { - await fetchUnFollowUser({ memberId }) - await queryClient.invalidateQueries({ queryKey: ['follow'] }) - await queryClient.invalidateQueries({ queryKey: ['users'] }) + await deleteFollow({ memberId }) } }, 300), - [memberId, queryClient], + [memberId, deleteFollow], ) const debounceFollowUser = useMemo( () => debounce(async () => { if (memberId) { - await fetchFollowUser({ memberId }) - await queryClient.invalidateQueries({ queryKey: ['follow'] }) - await queryClient.invalidateQueries({ queryKey: ['users'] }) + await postFollow({ memberId }) } }, 300), - [memberId, queryClient], + [memberId, postFollow], ) const handleClickFollow = useCallback( diff --git a/src/hooks/useGetProfile.ts b/src/hooks/useGetProfile.ts index 71f6fd22..b8dd9203 100644 --- a/src/hooks/useGetProfile.ts +++ b/src/hooks/useGetProfile.ts @@ -1,29 +1,17 @@ -import { useCallback, useEffect, useState } from 'react' -import { fetchGetUserProfile } from '@/services/user/profile/profile' -import { UserProfileResBody } from '@/types' +import { useState } from 'react' +import { useGetUserProfile } from '@/services/users/useUsers' import { usePathname } from 'next/navigation' import { useCurrentUser } from './useCurrentUser' const useGetProfile = () => { - const [user, setUser] = useState() const [isLoading, setIsLoading] = useState(false) const path = usePathname() const userId = Number(path.split('/')[2]) const { currentUser } = useCurrentUser() const myId = currentUser?.memberId + const { data: userData } = useGetUserProfile(userId) - const handleGetUserProfile = useCallback(async () => { - setIsLoading(true) - const userData = await fetchGetUserProfile({ memberId: userId }) - setUser(userData) - setIsLoading(false) - }, [userId, setIsLoading]) - - useEffect(() => { - handleGetUserProfile() - }, [handleGetUserProfile]) - - return { user, myId, isProfileLoading: isLoading } + return { user: userData, myId, isProfileLoading: isLoading } } export default useGetProfile diff --git a/src/hooks/useSpaceComment.ts b/src/hooks/useSpaceComment.ts index 8b578724..b70802a1 100644 --- a/src/hooks/useSpaceComment.ts +++ b/src/hooks/useSpaceComment.ts @@ -9,11 +9,10 @@ import { CommentFormValues } from '@/app/(routes)/space/[spaceId]/comment/page' import { CommentProps } from '@/components/common/Comment/Comment' import { notify } from '@/components/common/Toast/Toast' import { - fetchCreateComment, - fetchUpdateComment, -} from '@/services/comment/comment' -import { fetchCreateReply } from '@/services/comment/reply' -import { useQueryClient } from '@tanstack/react-query' + usePostComment, + usePostReply, + usePutComment, +} from '@/services/comments/useComments' export interface SpaceComment extends CommentProps { replies?: CommentProps[] @@ -43,10 +42,18 @@ const useSpaceComment = ({ setValue, setFocus, }: useSpaceCommentProps) => { - const queryClient = useQueryClient() const [comment, setComment] = useState(defaultComment) const [openedComments, setOpenedComments] = useState([]) const commentListRef = useRef(null) + const { mutateAsync: createComment } = usePostComment(spaceId) + const { mutateAsync: updateComment } = usePutComment( + spaceId, + comment.parentCommentId, + ) + const { mutateAsync: createReply } = usePostReply( + spaceId, + comment.parentCommentId, + ) const handleOpen = useCallback( (commentId: number) => { @@ -97,27 +104,21 @@ const useSpaceComment = ({ const onSubmit: SubmitHandler = async (data) => { if (comment.type === 'create') { - await fetchCreateComment(spaceId, { content: data.content }) - await queryClient.invalidateQueries({ queryKey: ['comments', spaceId] }) + await createComment({ spaceId, content: data.content }) commentListRef.current?.scrollIntoView(false) } else if (comment.type === 'edit') { - await fetchUpdateComment(spaceId, comment.commentId, { + await updateComment({ + spaceId, + commentId: comment.commentId, content: data.content, }) - await queryClient.invalidateQueries({ queryKey: ['comments', spaceId] }) - if (comment.parentCommentId) { - await queryClient.invalidateQueries({ - queryKey: ['replies', spaceId, comment.parentCommentId], - }) - } } else if (comment.type === 'reply') { - await fetchCreateReply(spaceId, comment.commentId, { + await createReply({ + spaceId, + commentId: comment.commentId, content: data.content, }) - await queryClient.invalidateQueries({ queryKey: ['comments', spaceId] }) - await queryClient.invalidateQueries({ - queryKey: ['replies', spaceId, comment.commentId], - }) + setOpenedComments((prev) => [...prev, comment.commentId]) } setComment(defaultComment) diff --git a/src/hooks/useTagParam.ts b/src/hooks/useTagParam.ts index 2c6e14c7..52d9321a 100644 --- a/src/hooks/useTagParam.ts +++ b/src/hooks/useTagParam.ts @@ -1,4 +1,4 @@ -import { Tag } from '@/components/common/Space/hooks/useGetTags' +import { Tag } from '@/types' import { usePathname, useRouter, useSearchParams } from 'next/navigation' import useQueryString from './useQueryString' diff --git a/src/lib/fetchAPI.ts b/src/lib/fetchAPI.ts index 1555e258..e85fc07b 100644 --- a/src/lib/fetchAPI.ts +++ b/src/lib/fetchAPI.ts @@ -1,147 +1,136 @@ import { notify } from '@/components/common/Toast/Toast' +import { IFetchConfig } from '@/services/apiServices' -class FetchAPI { - private baseURL: string - private headers: { [key: string]: string } - - private static instance: FetchAPI - - private constructor() { - this.baseURL = process.env.NEXT_PUBLIC_API_INTERNAL_ADDRESS || '' - this.headers = { - 'Content-Type': 'application/json;charset=UTF-8', - } - } - - private async handleResponse(response: Response, type?: string) { - if (!response.ok) { - const data = await response.json() - switch (response.status) { - case 401: - notify('error', '인증되지 않은 사용자입니다.') - break - case 404: - notify( - 'error', - `${data.errorMessage}` || '해당하는 요청을 찾을 수 없습니다.', - ) - break - case 500: - notify( - 'error', - `${data.errorMessage}` || '서버에 오류가 발생했습니다.', - ) - break - default: - notify( - 'error', - `${data.errorMessage}` || '알 수 없는 오류가 발생했습니다.', - ) - break - } - return data - } else { - return type === 'delete' ? response : response.json() - } - } +const defaultConfig: IFetchConfig = { + baseURL: process.env.NEXT_PUBLIC_API_INTERNAL_ADDRESS || '', + headers: { + 'Content-Type': 'application/json;charset=UTF-8', + }, +} - public static getInstance(): FetchAPI { - if (!FetchAPI.instance) { - FetchAPI.instance = new FetchAPI() +const handleResponse = async (response: Response, type?: string) => { + if (!response.ok) { + const data = await response.json() + switch (response.status) { + case 401: + notify('error', '인증되지 않은 사용자입니다.') + break + case 404: + notify( + 'error', + `${data.errorMessage}` || '해당하는 요청을 찾을 수 없습니다.', + ) + break + case 500: + notify('error', `${data.errorMessage}` || '서버에 오류가 발생했습니다.') + break + default: + notify( + 'error', + `${data.errorMessage}` || '알 수 없는 오류가 발생했습니다.', + ) + break } - return FetchAPI.instance - } - - public setBaseURL(url: string): void { - this.baseURL = url + return data } + return type === 'delete' ? response : response.json() +} - public setDefaultHeader(key: string, value: string): void { - this.headers[key] = value - } +export const createFetchAPI = (customConfig: Partial = {}) => { + let config: IFetchConfig = { ...defaultConfig, ...customConfig } - public async get( + const get = async ( endpoint: string, nextInit: RequestInit = {}, customHeaders: { [key: string]: string } = {}, - ): Promise { - const response = await fetch(`${this.baseURL}${endpoint}`, { + ) => { + const response = await fetch(`${config.baseURL}${endpoint}`, { method: 'GET', - headers: { ...this.headers, ...customHeaders }, + headers: { ...config.headers, ...customHeaders }, ...nextInit, }) - return this.handleResponse(response) + return handleResponse(response) } - public async post( + const post = async ( endpoint: string, body: any, nextInit: RequestInit = {}, customHeaders: { [key: string]: string } = {}, type: string = 'default', - ): Promise { - const response = await fetch(`${this.baseURL}${endpoint}`, { + ) => { + const response = await fetch(`${config.baseURL}${endpoint}`, { method: 'POST', headers: type === 'multipart' ? { ...customHeaders } - : { ...this.headers, ...customHeaders }, + : { ...config.headers, ...customHeaders }, body: type === 'multipart' ? body : JSON.stringify(body), ...nextInit, }) - return this.handleResponse(response) + return handleResponse(response) } - public async put( + const put = async ( endpoint: string, body: any, nextInit: RequestInit = {}, customHeaders: { [key: string]: string } = {}, type: string = 'default', - ): Promise { - const response = await fetch(`${this.baseURL}${endpoint}`, { + ) => { + const response = await fetch(`${config.baseURL}${endpoint}`, { method: 'PUT', headers: type === 'multipart' ? { ...customHeaders } - : { ...this.headers, ...customHeaders }, + : { ...config.headers, ...customHeaders }, body: type === 'multipart' ? body : JSON.stringify(body), ...nextInit, }) - return this.handleResponse(response) + return handleResponse(response) } - public async delete( + const del = async ( endpoint: string, nextInit: RequestInit = {}, customHeaders: { [key: string]: string } = {}, - ): Promise { - const response = await fetch(`${this.baseURL}${endpoint}`, { + ) => { + const response = await fetch(`${config.baseURL}${endpoint}`, { method: 'DELETE', - headers: { ...this.headers, ...customHeaders }, + headers: { ...config.headers, ...customHeaders }, ...nextInit, }) - return this.handleResponse(response, 'delete') + return handleResponse(response, 'delete') } - public async patch( + const patch = async ( endpoint: string, body: any, nextInit: RequestInit = {}, customHeaders: { [key: string]: string } = {}, type: string = 'default', - ): Promise { - const response = await fetch(`${this.baseURL}${endpoint}`, { + ) => { + const response = await fetch(`${config.baseURL}${endpoint}`, { method: 'PATCH', headers: type === 'multipart' ? { ...customHeaders } - : { ...this.headers, ...customHeaders }, + : { ...config.headers, ...customHeaders }, body: type === 'multipart' ? body : JSON.stringify(body), ...nextInit, }) - return this.handleResponse(response) + return handleResponse(response) + } + + return { + get, + post, + put, + delete: del, // delete는 예약어라서 del로 정의 + patch, } } -export default FetchAPI +export const fetchAPI = createFetchAPI() + +export default fetchAPI diff --git a/src/lib/fetchServerAPI.ts b/src/lib/fetchServerAPI.ts index 7ca4ea19..a844663e 100644 --- a/src/lib/fetchServerAPI.ts +++ b/src/lib/fetchServerAPI.ts @@ -1,114 +1,110 @@ -class FetchServerAPI { - private baseURL: string - private headers: { [key: string]: string } +import { IFetchConfig } from '@/services/apiServices' - private static instance: FetchServerAPI - - private constructor() { - this.baseURL = process.env.NEXT_PUBLIC_API_ADDRESS || '' - this.headers = { - 'Content-Type': 'application/json;charset=UTF-8', - } - } - - public static getInstance(): FetchServerAPI { - if (!FetchServerAPI.instance) { - FetchServerAPI.instance = new FetchServerAPI() - } - return FetchServerAPI.instance - } - - public setBaseURL(url: string): void { - this.baseURL = url - } +const defaultConfig: IFetchConfig = { + baseURL: process.env.NEXT_PUBLIC_API_ADDRESS || '', + headers: { + 'Content-Type': 'application/json;charset=UTF-8', + }, +} - public setDefaultHeader(key: string, value: string): void { - this.headers[key] = value - } +export const createFetchServerAPI = ( + customConfig: Partial = {}, +) => { + let config: IFetchConfig = { ...defaultConfig, ...customConfig } - public async get( + const get = async ( endpoint: string, nextInit: RequestInit = {}, customHeaders: { [key: string]: string } = {}, - ): Promise { - const response = await fetch(`${this.baseURL}${endpoint}`, { + ) => { + const response = await fetch(`${config.baseURL}${endpoint}`, { method: 'GET', - headers: { ...this.headers, ...customHeaders }, + headers: { ...config.headers, ...customHeaders }, ...nextInit, }) const data = response.json() return data } - public async post( + const post = async ( endpoint: string, body: any, nextInit: RequestInit = {}, customHeaders: { [key: string]: string } = {}, type: string = 'default', - ) { - const response = await fetch(`${this.baseURL}${endpoint}`, { + ) => { + const response = await fetch(`${config.baseURL}${endpoint}`, { method: 'POST', headers: type === 'multipart' ? { ...customHeaders } - : { ...this.headers, ...customHeaders }, + : { ...config.headers, ...customHeaders }, body: type === 'multipart' ? body : JSON.stringify(body), ...nextInit, }) return response } - public async put( + const put = async ( endpoint: string, body: any, nextInit: RequestInit = {}, customHeaders: { [key: string]: string } = {}, type: string = 'default', - ) { - const response = await fetch(`${this.baseURL}${endpoint}`, { + ) => { + const response = await fetch(`${config.baseURL}${endpoint}`, { method: 'PUT', headers: type === 'multipart' ? { ...customHeaders } - : { ...this.headers, ...customHeaders }, + : { ...config.headers, ...customHeaders }, body: type === 'multipart' ? body : JSON.stringify(body), ...nextInit, }) return response } - public async delete( + const del = async ( endpoint: string, nextInit: RequestInit = {}, customHeaders: { [key: string]: string } = {}, - ) { - const response = await fetch(`${this.baseURL}${endpoint}`, { + ) => { + const response = await fetch(`${config.baseURL}${endpoint}`, { method: 'DELETE', - headers: { ...this.headers, ...customHeaders }, + headers: { ...config.headers, ...customHeaders }, ...nextInit, }) return response } - public async patch( + const patch = async ( endpoint: string, body: any, nextInit: RequestInit = {}, customHeaders: { [key: string]: string } = {}, type: string = 'default', - ) { - const response = await fetch(`${this.baseURL}${endpoint}`, { + ) => { + const response = await fetch(`${config.baseURL}${endpoint}`, { method: 'PATCH', headers: type === 'multipart' ? { ...customHeaders } - : { ...this.headers, ...customHeaders }, + : { ...config.headers, ...customHeaders }, body: type === 'multipart' ? body : JSON.stringify(body), ...nextInit, }) return response } + + return { + get, + post, + put, + delete: del, + patch, + } } -export default FetchServerAPI +export const fetchServerAPI = createFetchServerAPI() + +export default fetchServerAPI diff --git a/src/models/comments.model.ts b/src/models/comments.model.ts new file mode 100644 index 00000000..415dccd5 --- /dev/null +++ b/src/models/comments.model.ts @@ -0,0 +1,6 @@ +export interface ICommentQuery { + spaceId: number + commentId?: number + pageNumber: number + pageSize: number +} diff --git a/src/models/emails.model.ts b/src/models/emails.model.ts new file mode 100644 index 00000000..872ea178 --- /dev/null +++ b/src/models/emails.model.ts @@ -0,0 +1,7 @@ +export interface IEmail { + email: string +} + +export interface IEmailVerify { + code: string +} diff --git a/src/models/link.model.ts b/src/models/link.model.ts new file mode 100644 index 00000000..cef8d76a --- /dev/null +++ b/src/models/link.model.ts @@ -0,0 +1,23 @@ +export interface ILikeLink { + query: { + linkId: number + } +} + +export interface ISpaceLink { + query: { + spaceId: number + linkId: number + } +} + +export interface IUpdateLink { + query: { + spaceId?: number + linkId?: number + url: string + title?: string + tagName?: string + color?: string + } +} diff --git a/src/models/member.model.ts b/src/models/member.model.ts new file mode 100644 index 00000000..a87ff61e --- /dev/null +++ b/src/models/member.model.ts @@ -0,0 +1,15 @@ +export interface IFollowList { + memberId?: number + pageNumber: number + pageSize: number +} + +export interface IFollow { + memberId: number +} + +export interface IMemberSearch { + keyword: string + pageNumber: number + pageSize: number +} diff --git a/src/models/notification.model.ts b/src/models/notification.model.ts new file mode 100644 index 00000000..2bf3a8fe --- /dev/null +++ b/src/models/notification.model.ts @@ -0,0 +1,9 @@ +export interface IInvitationsQuery { + query: { + pageNumber: number + pageSize: number + } + params: { + searchParams: string + } +} diff --git a/src/models/space.model.ts b/src/models/space.model.ts new file mode 100644 index 00000000..4f0006ea --- /dev/null +++ b/src/models/space.model.ts @@ -0,0 +1,91 @@ +export interface ICreateSpace { + data: { + spaceName: string + description: string + category: string + isVisible: boolean + isComment: boolean + isLinkSummarizable: boolean + isReadMarkEnabled: boolean + } + file?: File +} + +export interface IUpdateSpace { + spaceId?: number + data: { + spaceName: string + description: string + category: string + isVisible: boolean + isComment: boolean + isLinkSummarizable: boolean + isReadMarkEnabled: boolean + } + file?: File +} + +export interface ISpaceQuery { + query: { + spaceId?: number + } +} + +export interface ISpaceLink { + query: { + spaceId: number + linkId: number + } +} + +export interface IUpdateLink { + query: { + spaceId?: number + linkId?: number + url: string + title?: string + tagName?: string + color?: string + } +} + +export interface IChangeRole { + query: { + spaceId?: number + targetMemberId: number + role: string + } +} + +export interface ISearchSpace { + query: { + memberId?: number + pageNumber?: number + lastSpaceId?: number + lastFavoriteCount?: number + pageSize: number + sort?: string + keyWord?: string + filter: string + } +} + +export interface IInviteSpace { + query: { + email: string + spaceId: number + role: string + } + response: { + errorCode: string + errorMessage: string + requestURI: string + time: string + } +} + +export interface IAcceptSpaceInvitation { + query: { + notificationId: number + } +} diff --git a/src/services/apiServices.ts b/src/services/apiServices.ts index 865d14f2..25ca9423 100644 --- a/src/services/apiServices.ts +++ b/src/services/apiServices.ts @@ -1,7 +1,12 @@ import FetchAPI from '@/lib/fetchAPI' import FetchServerAPI from '../lib/fetchServerAPI' -const apiClient = FetchAPI.getInstance() -const apiServer = FetchServerAPI.getInstance() +export interface IFetchConfig { + baseURL: string + headers: { [key: string]: string } +} + +const apiClient = FetchAPI +const apiServer = FetchServerAPI export { apiClient, apiServer } diff --git a/src/services/auth/index.ts b/src/services/auth/index.ts deleted file mode 100644 index d38cdde6..00000000 --- a/src/services/auth/index.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { RegisterReqBody } from '@/components/UserInfoForm/hooks/useRegister' -import Cookies from 'js-cookie' -import { apiClient } from '../apiServices' - -const registerUser = async (data: RegisterReqBody, file?: File) => { - const socialId = Cookies.get('Social-Id') - const provider = Cookies.get('Provider') - const path = '/api/registerUser' - const reqData = { ...data, socialId, provider } - const formData = new FormData() - formData.append('request', JSON.stringify(reqData)) - file && formData.append('file', file) - const response = await apiClient.post(path, formData, {}, {}, 'multipart') - return response -} - -const validateToken = async () => { - const response = await apiClient.get('/api/auth-user') - return response -} - -const kakaoLogout = async () => { - const path = '/api/logout' - const response = await apiClient.post(path, {}, {}) - return response -} - -export { registerUser, validateToken, kakaoLogout } diff --git a/src/services/auth/useAuth.ts b/src/services/auth/useAuth.ts new file mode 100644 index 00000000..8d90eeac --- /dev/null +++ b/src/services/auth/useAuth.ts @@ -0,0 +1,56 @@ +import { RegisterReqBody } from '@/components/UserInfoForm/hooks/useRegister' +import { useMutation } from '@tanstack/react-query' +import Cookies from 'js-cookie' +import { apiClient } from '../apiServices' + +// 회원가입 +export const useRegisterUser = () => { + return useMutation({ + mutationFn: async ({ + data, + file, + }: { + data: RegisterReqBody + file?: File + }) => { + const socialId = Cookies.get('Social-Id') + const provider = Cookies.get('Provider') + const reqData = { ...data, socialId, provider } + + const formData = new FormData() + formData.append('request', JSON.stringify(reqData)) + file && formData.append('file', file) + + const response = await apiClient.post( + '/api/registerUser', + formData, + {}, + {}, + 'multipart', + ) + return response + }, + onError: (error: Error) => { + console.error('Registration error:', error) + }, + }) +} + +// 토큰 검증 +export const validateToken = async () => { + const response = await apiClient.get('/api/auth') + return response +} + +// 카카오 로그아웃 +export const useKakaoLogout = () => { + return useMutation({ + mutationFn: async () => { + const response = await apiClient.post(`/api/logout`, {}, {}) + return response + }, + onError: (error: Error) => { + console.error('Logout error:', error) + }, + }) +} diff --git a/src/services/comment/comment.ts b/src/services/comment/comment.ts deleted file mode 100644 index 0d8c35b4..00000000 --- a/src/services/comment/comment.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { CommentReqBody, CreateCommentReqBody } from '@/types' -import { apiClient } from '../apiServices' - -const fetchGetComments = async ({ - spaceId, - pageNumber, - pageSize, -}: CommentReqBody) => { - const path = `/api/space/${spaceId}/comments` - const params = { - pageNumber: pageNumber.toString(), - pageSize: pageSize.toString(), - } - const queryString = new URLSearchParams(params).toString() - - try { - const response = await apiClient.get(`${path}?${queryString}`) - return response - } catch (e) { - if (e instanceof Error) throw new Error(e.message) - } -} - -const fetchCreateComment = async ( - spaceId: number, - { content }: CreateCommentReqBody, -) => { - const path = `/api/space/${spaceId}/comments/create` - const body = { content } - - try { - const response = await apiClient.post(path, body) - return response - } catch (e) { - if (e instanceof Error) throw new Error(e.message) - } -} - -const fetchUpdateComment = async ( - spaceId: number, - commentId: number, - { content }: CreateCommentReqBody, -) => { - const path = `/api/space/${spaceId}/comments/${commentId}` - const body = { content } - - try { - const response = await apiClient.put(path, body) - return response - } catch (e) { - if (e instanceof Error) throw new Error(e.message) - } -} - -const fetchDeleteComment = async (spaceId: number, commentId: number) => { - const path = `/api/space/${spaceId}/comments/${commentId}` - - try { - const response = await apiClient.delete(path) - return response - } catch (e) { - if (e instanceof Error) throw new Error(e.message) - } -} - -export { - fetchGetComments, - fetchCreateComment, - fetchUpdateComment, - fetchDeleteComment, -} diff --git a/src/services/comment/reply.ts b/src/services/comment/reply.ts deleted file mode 100644 index c758591d..00000000 --- a/src/services/comment/reply.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { CommentReqBody, CreateCommentReqBody } from '@/types' -import { apiClient } from '../apiServices' - -const fetchGetReplies = async ({ - spaceId, - commentId, - pageNumber, - pageSize, -}: CommentReqBody) => { - const path = `/api/space/${spaceId}/comments/${commentId}/replies` - const params = { - pageNumber: pageNumber.toString(), - pageSize: pageSize.toString(), - } - const queryString = new URLSearchParams(params).toString() - - try { - const response = await apiClient.get(`${path}?${queryString}`) - return response - } catch (e) { - if (e instanceof Error) throw new Error(e.message) - } -} - -const fetchCreateReply = async ( - spaceId: number, - commentId: number, - { content }: CreateCommentReqBody, -) => { - const path = `/api/space/${spaceId}/comments/${commentId}/replies` - const body = { content } - - try { - const response = await apiClient.post(path, body) - return response - } catch (e) { - if (e instanceof Error) throw new Error(e.message) - } -} - -export { fetchGetReplies, fetchCreateReply } diff --git a/src/services/comments/useComments.ts b/src/services/comments/useComments.ts new file mode 100644 index 00000000..0aa52dc0 --- /dev/null +++ b/src/services/comments/useComments.ts @@ -0,0 +1,170 @@ +import { QUERY_KEYS } from '@/constants' +import { ICommentQuery } from '@/models/comments.model' +import { useMutation, useQueryClient } from '@tanstack/react-query' +import { apiClient } from '../apiServices' + +// 댓글 조회 (페이지네이션 fetch 함수) +export const fetchGetComments = async ({ + spaceId, + pageNumber, + pageSize, +}: ICommentQuery) => { + const params = { + pageNumber: pageNumber.toString(), + pageSize: pageSize.toString(), + } + const queryString = new URLSearchParams(params).toString() + + try { + const response = await apiClient.get( + `/api/space/${spaceId}/comments?${queryString}`, + ) + return response + } catch (e) { + if (e instanceof Error) throw new Error(e.message) + } +} + +// 댓글 생성 +export const usePostComment = (spaceId: number) => { + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: async ({ + spaceId, + content, + }: { + spaceId: number + content: string + }) => { + const response = await apiClient.post( + `/api/space/${spaceId}/comments/create`, + { content }, + ) + return response + }, + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: [QUERY_KEYS.COMMENTS, spaceId], + }) + }, + onError: (error: Error) => { + console.log(error) + }, + }) +} + +// 댓글 수정 +export const usePutComment = (spaceId?: number, parentCommentId?: number) => { + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: async ({ + spaceId, + commentId, + content, + }: { + spaceId: number + commentId: number + content: string + }) => { + const response = await apiClient.put( + `/api/space/${spaceId}/comments/${commentId}`, + { content }, + ) + return response + }, + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: [QUERY_KEYS.COMMENTS, spaceId], + }) + queryClient.invalidateQueries({ + queryKey: [QUERY_KEYS.REPLIES, spaceId, parentCommentId], + }) + }, + onError: (error: Error) => { + console.log(error) + }, + }) +} + +// 댓글 삭제 +export const useDeleteComment = (spaceId: number, parentCommentId?: number) => { + const queryClient = useQueryClient() + return useMutation({ + mutationFn: async ({ commentId }: { commentId: number }) => { + const response = await apiClient.delete( + `/api/space/${spaceId}/comments/${commentId}`, + ) + return response + }, + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: [QUERY_KEYS.COMMENTS, spaceId], + }) + queryClient.invalidateQueries({ + queryKey: [QUERY_KEYS.REPLIES, spaceId, parentCommentId], + }) + }, + onError: (error: Error) => { + console.log(error) + }, + }) +} + +// 대댓글 조회 (무한스크롤 fetch 함수) +export const fetchGetReplies = async ({ + spaceId, + commentId, + pageNumber, + pageSize, +}: ICommentQuery) => { + const params = { + pageNumber: pageNumber.toString(), + pageSize: pageSize.toString(), + } + const queryString = new URLSearchParams(params).toString() + + try { + const response = await apiClient.get( + `/api/space/${spaceId}/comments/${commentId}/replies?${queryString}`, + ) + return response + } catch (e) { + if (e instanceof Error) throw new Error(e.message) + } +} + +// 대댓글 생성 +export const usePostReply = (spaceId: number, parentCommentId?: number) => { + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: async ({ + spaceId, + commentId, + content, + }: { + spaceId: number + commentId: number + content: string + }) => { + const response = await apiClient.post( + `/api/space/${spaceId}/comments/${commentId}/replies`, + { content }, + ) + return response + }, + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: [QUERY_KEYS.COMMENTS, spaceId], + }) + queryClient.invalidateQueries({ + queryKey: [QUERY_KEYS.REPLIES, spaceId], + }) + }, + onError: (error: Error) => { + console.log(error) + }, + }) +} diff --git a/src/services/email/index.ts b/src/services/email/index.ts deleted file mode 100644 index 755099b3..00000000 --- a/src/services/email/index.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { - EmailReqBody, - EmailVerifyReqBody, -} from '@/components/UserInfoForm/UserInfoForm' -import { apiClient } from '../apiServices' - -const fetchPostEmail = async (data: EmailReqBody) => { - const path = '/api/email' - const response = await apiClient.post(path, data) - return response -} - -const fetchPostEmailVerify = async ( - data: EmailVerifyReqBody & EmailReqBody, -) => { - const path = '/api/email-verify' - try { - const response = await apiClient.post(path, data) - return response - } catch (e) { - if (e instanceof Error) throw new Error(e.message) - } -} - -export { fetchPostEmail, fetchPostEmailVerify } diff --git a/src/services/email/useEmails.ts b/src/services/email/useEmails.ts new file mode 100644 index 00000000..ff5670fc --- /dev/null +++ b/src/services/email/useEmails.ts @@ -0,0 +1,33 @@ +import { + EmailReqBody, + EmailVerifyReqBody, +} from '@/components/UserInfoForm/UserInfoForm' +import { IEmail } from '@/models/emails.model' +import { useMutation } from '@tanstack/react-query' +import { apiClient } from '../apiServices' + +// 이메일 전송 +export const usePostEmail = () => { + return useMutation({ + mutationFn: async (email: IEmail) => { + const response = await apiClient.post(`/api/email`, email) + return response + }, + onError: (error: Error) => { + console.log(error) + }, + }) +} + +// 이메일 인증 +export const usePostEmailVerify = () => { + return useMutation({ + mutationFn: async (data: EmailVerifyReqBody & EmailReqBody) => { + const response = await apiClient.post(`/api/email/verify`, data) + return response + }, + onError: (error: Error) => { + console.log(error) + }, + }) +} diff --git a/src/services/index.ts b/src/services/index.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/src/services/link/link.ts b/src/services/link/link.ts deleted file mode 100644 index 088b87bb..00000000 --- a/src/services/link/link.ts +++ /dev/null @@ -1,175 +0,0 @@ -import { CreateLinkReqBody, GetLinksReqBody } from '@/types' -import { apiClient } from '../apiServices' - -const fetchGetLinks = async ({ - spaceId, - pageNumber, - pageSize, - sort, - tagId, -}: GetLinksReqBody) => { - const path = `/api/space/${spaceId}/links` - const params = { - pageNumber: pageNumber.toString(), - pageSize: pageSize.toString(), - ...(sort && { sort: sort }), - ...(tagId && { tagId: tagId.toString() }), - } - const queryString = new URLSearchParams(params).toString() - - try { - const response = await apiClient.get(`${path}?${queryString}`) - return response - } catch (e) { - if (e instanceof Error) throw new Error(e.message) - } -} - -export interface FetchCreateLinkProps extends CreateLinkReqBody { - spaceId?: number -} - -const fetchCreateLink = async ({ - spaceId, - url, - title, - tagName, - color, -}: FetchCreateLinkProps) => { - const path = `/api/space/${spaceId}/links` - const body = { - spaceId, - url, - title, - ...(tagName && { tagName }), - ...(color && { color }), - } - - try { - const response = await apiClient.post(path, body) - return response - } catch (e) { - if (e instanceof Error) throw new Error(e.message) - } -} - -export interface FetchUpdateLinkProps extends CreateLinkReqBody { - spaceId?: number - linkId: number -} - -const fetchUpdateLink = async ({ - spaceId, - linkId, - url, - title, - tagName, - color, -}: FetchUpdateLinkProps) => { - const path = `/api/space/${spaceId}/links` - const body = { - spaceId, - linkId, - url, - title, - ...(tagName && { tagName }), - ...(color && { color }), - } - - try { - const response = await apiClient.put(path, body) - return response - } catch (e) { - if (e instanceof Error) throw new Error(e.message) - } -} - -export interface FetchDeleteLinkProps { - spaceId: number - linkId: number -} - -const fetchDeleteLink = async ({ spaceId, linkId }: FetchDeleteLinkProps) => { - const path = `/api/space/${spaceId}/links` - const params = { - spaceId: spaceId.toString(), - linkId: linkId.toString(), - } - const queryString = new URLSearchParams(params).toString() - - try { - const response = await apiClient.delete(`${path}?${queryString}`) - return response - } catch (e) { - if (e instanceof Error) throw new Error(e.message) - } -} - -export interface FetchLikeLinkProps { - linkId: number -} - -const fetchLikeLink = async ({ linkId }: FetchLikeLinkProps) => { - const path = `/api/links/${linkId}/like` - - try { - const response = await apiClient.post(path, {}) - return response - } catch (e) { - if (e instanceof Error) throw new Error(e.message) - } -} - -const fetchUnLikeLink = async ({ linkId }: FetchLikeLinkProps) => { - const path = `/api/links/${linkId}/like` - - try { - const response = await apiClient.delete(path) - return response - } catch (e) { - if (e instanceof Error) throw new Error(e.message) - } -} - -export interface FetchReadSaveLinkProps extends FetchDeleteLinkProps {} - -const fetchReadSaveLink = async ({ - spaceId, - linkId, -}: FetchReadSaveLinkProps) => { - const path = `/api/space/${spaceId}/links/readInfo` - const params = { - spaceId: spaceId.toString(), - linkId: linkId.toString(), - } - const queryString = new URLSearchParams(params).toString() - - try { - const response = await apiClient.post(`${path}?${queryString}`, {}) - return response - } catch (e) { - if (e instanceof Error) throw new Error(e.message) - } -} - -const fetchGetPopularLinks = async () => { - const path = `/api/links` - - try { - const response = await apiClient.get(path) - return response - } catch (e) { - if (e instanceof Error) throw new Error(e.message) - } -} - -export { - fetchGetLinks, - fetchCreateLink, - fetchUpdateLink, - fetchDeleteLink, - fetchLikeLink, - fetchUnLikeLink, - fetchReadSaveLink, - fetchGetPopularLinks, -} diff --git a/src/services/link/useLink.ts b/src/services/link/useLink.ts new file mode 100644 index 00000000..6c8b564e --- /dev/null +++ b/src/services/link/useLink.ts @@ -0,0 +1,174 @@ +import { QUERY_KEYS } from '@/constants' +import { ILikeLink, ISpaceLink, IUpdateLink } from '@/models/link.model' +import { GetLinksReqBody } from '@/types' +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' +import { apiClient } from '../apiServices' + +// 링크 조회 (페이지네이션 fetch 함수) +export const fetchGetLinks = async ({ + spaceId, + pageNumber, + pageSize, + sort, + tagId, +}: GetLinksReqBody) => { + const params = { + pageNumber: pageNumber.toString(), + pageSize: pageSize.toString(), + ...(sort && { sort: sort }), + ...(tagId && { tagId: tagId.toString() }), + } + const queryString = new URLSearchParams(params).toString() + + try { + const response = await apiClient.get( + `/api/space/${spaceId}/links?${queryString}`, + ) + return response + } catch (e) { + if (e instanceof Error) throw new Error(e.message) + } +} + +// 링크 생성 +export const usePostLink = (spaceId?: number) => { + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: async (query: IUpdateLink['query']) => { + const response = await apiClient.post( + `/api/space/${spaceId}/links`, + query, + ) + return response + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.LINKS, spaceId] }) + queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.TAGS, spaceId] }) + }, + onError: (error: Error) => { + console.log(error) + }, + }) +} + +// 링크 수정 +export const usePutLink = (spaceId?: number) => { + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: async (query: IUpdateLink['query']) => { + const response = await apiClient.put(`/api/space/${spaceId}/links`, query) + return response + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.LINKS, spaceId] }) + queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.TAGS, spaceId] }) + }, + onError: (error: Error) => { + console.log(error) + }, + }) +} + +// 링크 삭제 +export const useDeleteLink = ({ + spaceId, + linkId, +}: { + spaceId?: number + linkId: number +}) => { + const queryClient = useQueryClient() + const params = { + ...(spaceId && { spaceId: spaceId.toString() }), + ...(linkId && { linkId: linkId.toString() }), + } + const queryString = new URLSearchParams(params).toString() + return useMutation({ + mutationFn: async () => { + const response = await apiClient.delete( + `/api/space/${spaceId}/links?${queryString}`, + ) + return response + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.LINKS, spaceId] }) + queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.TAGS, spaceId] }) + }, + onError: (error: Error) => { + console.log(error) + }, + }) +} + +// 인기 링크 조회 +export const useGetPopularLinks = () => { + return useQuery({ + queryKey: [QUERY_KEYS.POPULAR_LINKS], + queryFn: async () => { + const response = await apiClient.get(`/api/links`) + return response + }, + }) +} + +// 인기 링크 조회 서버 함수 +export const fetchGetPopularLinks = async () => { + try { + const response = await apiClient.get(`/api/links`) + return response + } catch (e) { + if (e instanceof Error) throw new Error(e.message) + } +} + +// 링크 좋아요 +export const usePostLikeLink = () => { + return useMutation({ + mutationFn: async (query: ILikeLink['query']) => { + const response = await apiClient.post( + `/api/links/${query.linkId}/like`, + query, + ) + return response + }, + onError: (error: Error) => { + console.log(error) + }, + }) +} + +// 링크 좋아요 취소 +export const useDeleteLikeLink = () => { + return useMutation({ + mutationFn: async (query: ILikeLink['query']) => { + const response = await apiClient.delete(`/api/links/${query.linkId}/like`) + return response + }, + onError: (error: Error) => { + console.log(error) + }, + }) +} + +// 링크 읽기 저장 +export const usePostReadSaveLink = (spaceId?: number) => { + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: async (query: ISpaceLink['query']) => { + const response = await apiClient.post( + `/api/space/${query.spaceId}/links/readInfo/${query.linkId}`, + query, + ) + return response + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.LINKS, spaceId] }) + }, + onError: (error: Error) => { + console.log(error) + }, + }) +} diff --git a/src/services/meta/meta.ts b/src/services/meta/meta.ts deleted file mode 100644 index df7d879b..00000000 --- a/src/services/meta/meta.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { apiClient } from '../apiServices' - -export interface FetchGetMetaProps { - url: string -} - -const fetchGetMeta = async ({ url }: FetchGetMetaProps) => { - const path = '/api/meta' - const body = { url } - - try { - const response = await apiClient.post(path, body) - return response - } catch (e) { - if (e instanceof Error) throw new Error(e.message) - } -} - -export { fetchGetMeta } diff --git a/src/services/meta/useMeta.ts b/src/services/meta/useMeta.ts new file mode 100644 index 00000000..1a6d1eec --- /dev/null +++ b/src/services/meta/useMeta.ts @@ -0,0 +1,14 @@ +import { useMutation } from '@tanstack/react-query' +import { apiClient } from '../apiServices' + +export const usePostMeta = () => { + return useMutation({ + mutationFn: async ({ url }: { url: string }) => { + const response = await apiClient.post(`/api/meta`, { url }) + return response + }, + onError: (error: Error) => { + console.error('Meta fetch error:', error) + }, + }) +} diff --git a/src/services/notification/index.ts b/src/services/notification/index.ts deleted file mode 100644 index 4e209d0d..00000000 --- a/src/services/notification/index.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { apiClient } from '../apiServices' - -export interface FetchDeleteNotificationProps { - notificationId: number -} - -const fetchDeleteNotification = async ({ - notificationId, -}: FetchDeleteNotificationProps) => { - const path = `/api/notification/${notificationId}` - - try { - const response = await apiClient.delete(path) - return response - } catch (e) { - if (e instanceof Error) throw new Error(e.message) - } -} - -export { fetchDeleteNotification } diff --git a/src/services/notification/invitations.ts b/src/services/notification/invitations.ts deleted file mode 100644 index 289f2cc9..00000000 --- a/src/services/notification/invitations.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { InvitationsReqBody } from '@/types' -import { apiClient } from '../apiServices' - -const fetchGetUnCheckedNotifications = async () => { - const path = '/api/notification/unchecked' - - try { - const response = await apiClient.get(path) - return response - } catch (e) { - if (e instanceof Error) throw new Error(e.message) - } -} - -const fetchGetInvitations = async ({ - pageNumber, - pageSize, -}: InvitationsReqBody) => { - const path = '/api/notification/invitations' - const params = { - pageNumber: pageNumber.toString(), - pageSize: pageSize.toString(), - } - const queryString = new URLSearchParams(params).toString() - - try { - const response = await apiClient.get(`${path}?${queryString}`) - return response - } catch (e) { - if (e instanceof Error) throw new Error(e.message) - } -} - -export { fetchGetUnCheckedNotifications, fetchGetInvitations } diff --git a/src/services/notification/useNotification.ts b/src/services/notification/useNotification.ts new file mode 100644 index 00000000..9adefea0 --- /dev/null +++ b/src/services/notification/useNotification.ts @@ -0,0 +1,59 @@ +import { QUERY_KEYS } from '@/constants' +import { IInvitationsQuery } from '@/models/notification.model' +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' +import { apiClient } from '../apiServices' + +// 미확인 알림 개수 조회 +export const useGetUnCheckedNotifications = () => { + return useQuery({ + queryKey: [QUERY_KEYS.NOTIFICATION_COUNT], + queryFn: async () => { + const response = await apiClient.get(`/api/notification/unchecked`) + return response + }, + }) +} + +// 초대 알림 조회 +export const fetchGetInvitations = async ({ + pageNumber, + pageSize, +}: IInvitationsQuery['query']) => { + const params = { + pageNumber: pageNumber.toString(), + pageSize: pageSize.toString(), + } + const queryString = new URLSearchParams(params).toString() + + try { + const response = await apiClient.get( + `/api/notification/invitations?${queryString}`, + ) + return response + } catch (e) { + if (e instanceof Error) throw new Error(e.message) + } +} + +// 알림 삭제 +export const useDeleteNotifications = () => { + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: async (notificationId: number) => { + const response = await apiClient.delete( + `/api/notification/${notificationId}`, + ) + return response + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.INVITATIONS] }) + queryClient.invalidateQueries({ + queryKey: [QUERY_KEYS.NOTIFICATION_COUNT], + }) + }, + onError: (error: Error) => { + console.log(error) + }, + }) +} diff --git a/src/services/space/invitation.ts b/src/services/space/invitation.ts deleted file mode 100644 index 514499f4..00000000 --- a/src/services/space/invitation.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { InviteSpaceReqBody } from '@/types' -import { apiClient } from '../apiServices' - -const fetchInviteSpace = async ({ - email, - spaceId, - role, -}: InviteSpaceReqBody) => { - const path = `/api/space/invitations` - const body = { email, spaceId, role } - - try { - const response = await apiClient.post(path, body) - return response - } catch (e) { - if (e instanceof Error) throw new Error(e.message) - } -} - -export { fetchInviteSpace } diff --git a/src/services/space/space.ts b/src/services/space/space.ts deleted file mode 100644 index 36e0fdca..00000000 --- a/src/services/space/space.ts +++ /dev/null @@ -1,134 +0,0 @@ -import { ChangeRoleProps } from '@/components/common/SpaceMemberList/SpaceMemberList' -import { CreateSpaceReqBody } from '@/types' -import { apiClient } from '../apiServices' - -export interface FetchGetSpaceProps { - spaceId?: number -} - -const fetchGetSpace = async ({ spaceId }: FetchGetSpaceProps) => { - const path = `/api/space/${spaceId}` - - try { - const response = await apiClient.get(path) - return response - } catch (e) { - if (e instanceof Error) throw new Error(e.message) - } -} - -const feachCreateSpace = async (data: CreateSpaceReqBody, file?: File) => { - const path = '/api/spaces/create' - const reqData = { ...data } - const formData = new FormData() - formData.append('request', JSON.stringify(reqData)) - file && formData.append('file', file) - try { - const response = await apiClient.post(path, formData, {}, {}, 'multipart') - return response - } catch (e) { - if (e instanceof Error) throw new Error(e.message) - } -} - -const fetchSettingSpace = async ( - spaceId: number, - data: CreateSpaceReqBody, - file?: File, -) => { - const path = `/api/space/${spaceId}` - const reqData = { ...data } - const formData = new FormData() - formData.append('request', JSON.stringify(reqData)) - file && formData.append('file', file) - try { - const response = await apiClient.patch(path, formData, {}, {}, 'multipart') - return response - } catch (e) { - if (e instanceof Error) throw new Error(e.message) - } -} - -const fetchDeleteSpace = async (spaceId: number) => { - const path = `/api/space/${spaceId}` - try { - const response = await apiClient.delete(path) - return response - } catch (e) { - if (e instanceof Error) throw new Error(e.message) - } -} - -const fetchFavoriteSpace = async ({ spaceId }: FetchGetSpaceProps) => { - const path = `/api/space/${spaceId}/favorites` - - try { - const response = await apiClient.post(path, {}) - return response - } catch (e) { - if (e instanceof Error) throw new Error(e.message) - } -} - -const fetchUnFavoriteSpace = async ({ spaceId }: FetchGetSpaceProps) => { - const path = `/api/space/${spaceId}/favorites` - - try { - const response = await apiClient.delete(path) - return response - } catch (e) { - if (e instanceof Error) throw new Error(e.message) - } -} - -const fetchGetTags = async ({ spaceId }: FetchGetSpaceProps) => { - const path = `/api/space/${spaceId}/tags` - - try { - const response = await apiClient.get(path) - return response - } catch (e) { - if (e instanceof Error) throw new Error(e.message) - } -} - -const fetchPatchRole = async (spaceId: number, data: ChangeRoleProps) => { - const path = `/api/space/${spaceId}/member/role` - - try { - const response = await apiClient.patch(path, data) - return response - } catch (e) { - if (e instanceof Error) throw new Error(e.message) - } -} - -const fetchScrapSpace = async ( - spaceId: number, - data: CreateSpaceReqBody, - file?: File, -) => { - const path = `/api/space/${spaceId}/scrap/new` - const reqData = { ...data } - const formData = new FormData() - formData.append('request', JSON.stringify(reqData)) - file && formData.append('file', file) - try { - const response = await apiClient.post(path, formData, {}, {}, 'multipart') - return response - } catch (e) { - if (e instanceof Error) throw new Error(e.message) - } -} - -export { - fetchGetSpace, - feachCreateSpace, - fetchFavoriteSpace, - fetchUnFavoriteSpace, - fetchSettingSpace, - fetchDeleteSpace, - fetchGetTags, - fetchPatchRole, - fetchScrapSpace, -} diff --git a/src/services/space/spaces.ts b/src/services/space/spaces.ts deleted file mode 100644 index 4d89cc6e..00000000 --- a/src/services/space/spaces.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { PAGE_SIZE } from '@/constants' -import { SearchSpaceReqBody, SpaceInviteResBody } from '@/types' -import { apiClient } from '../apiServices' - -const fetchGetSpaces = async ({ - lastSpaceId = undefined, - lastFavoriteCount = undefined, - pageSize = PAGE_SIZE, - sort = 'created_at', - filter = 'all', -}: SearchSpaceReqBody) => { - const path = '/api/spaces' - const params = { - pageSize: pageSize.toString(), - ...(lastSpaceId !== undefined && { lastSpaceId: lastSpaceId.toString() }), - ...(lastFavoriteCount !== undefined && { - lastFavoriteCount: lastFavoriteCount.toString(), - }), - ...(sort && { sort: sort }), - filter: filter, - } - const queryString = new URLSearchParams(params).toString() - - try { - const response = await apiClient.get(`${path}?${queryString}`) - return response - } catch (e) { - if (e instanceof Error) throw new Error(e.message) - } -} - -const fetchSearchSpaces = async ({ - pageNumber = 0, - pageSize, - sort, - filter, - keyWord, -}: SearchSpaceReqBody) => { - const path = '/api/spaces/search' - const params = { - pageNumber: pageNumber.toString(), - pageSize: pageSize.toString(), - ...(sort && { sort: sort }), - filter: filter, - ...(keyWord && { keyWord: keyWord }), - } - const queryString = new URLSearchParams(params).toString() - - try { - const response = await apiClient.get(`${path}?${queryString}`) - return response - } catch (e) { - if (e instanceof Error) throw new Error(e.message) - } -} - -const fetchSearchMySpaces = async ({ - memberId, - pageNumber = 0, - pageSize, - filter, - keyWord, -}: SearchSpaceReqBody) => { - const path = `/api/user/${memberId}/spaces` - const params = { - pageNumber: pageNumber.toString(), - pageSize: pageSize.toString(), - filter: filter, - ...(keyWord && { keyWord: keyWord }), - } - const queryString = new URLSearchParams(params).toString() - - try { - const response = await apiClient.get(`${path}?${queryString}`) - return response - } catch (e) { - if (e instanceof Error) throw new Error(e.message) - } -} - -const fetchAccetpSpaceInvitation = async (data: SpaceInviteResBody) => { - const path = `/api/spaces/invitation` - - try { - const response = await apiClient.post(path, data) - return response - } catch (e) { - if (e instanceof Error) throw new Error(e.message) - } -} - -export { - fetchGetSpaces, - fetchSearchSpaces, - fetchSearchMySpaces, - fetchAccetpSpaceInvitation, -} diff --git a/src/services/space/useSpace.ts b/src/services/space/useSpace.ts new file mode 100644 index 00000000..b630dd62 --- /dev/null +++ b/src/services/space/useSpace.ts @@ -0,0 +1,202 @@ +import { QUERY_KEYS } from '@/constants' +import { + IChangeRole, + ICreateSpace, + ISpaceQuery, + IUpdateSpace, +} from '@/models/space.model' +import { Tag } from '@/types' +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' +import { apiClient } from '../apiServices' + +export interface FetchGetSpaceProps { + spaceId?: number +} + +// 스페이스 상세 조회 +export const useGetSpace = (spaceId?: number) => { + return useQuery({ + queryKey: [QUERY_KEYS.SPACES, spaceId], + queryFn: async () => await apiClient.get(`/api/space/${spaceId}`), + enabled: !!spaceId, + }) +} + +// 스페이스 생성 +export const usePostSpace = () => { + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: async ({ data, file }: ICreateSpace) => { + const reqData = { ...data } + const formData = new FormData() + formData.append('request', JSON.stringify(reqData)) + file && formData.append('file', file) + + const response = await apiClient.post( + `/api/spaces/create`, + formData, + {}, + {}, + 'multipart', + ) + return response + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.SPACES] }) + }, + onError: (error: Error) => { + console.log(error) + }, + }) +} + +// 스페이스 수정 +export const usePatchSpace = (spaceId?: number) => { + const queryClient = useQueryClient() + return useMutation({ + mutationFn: async ({ data, file }: IUpdateSpace) => { + const reqData = { ...data } + const formData = new FormData() + formData.append('request', JSON.stringify(reqData)) + file && formData.append('file', file) + + const response = await apiClient.patch( + `/api/space/${spaceId}`, + formData, + {}, + {}, + 'multipart', + ) + return response + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.SPACES] }) + queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.SPACES, spaceId] }) + }, + onError: (error: Error) => { + console.log(error) + }, + }) +} + +// 스페이스 삭제 +export const useDeleteSpace = () => { + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: async (query: ISpaceQuery['query']) => { + const response = await apiClient.delete(`/api/space/${query.spaceId}`) + return response + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.SPACES] }) + }, + onError: (error: Error) => { + console.log(error) + }, + }) +} + +// 스페이스 즐겨찾기 +export const usePostFavoriteSpace = () => { + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: async (query: ISpaceQuery['query']) => { + const response = await apiClient.post( + `/api/space/${query.spaceId}/favorites`, + {}, + ) + return response + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.SPACES] }) + }, + onError: (error: Error) => { + console.log(error) + }, + }) +} + +// 스페이스 즐겨찾기 삭제 +export const useDeleteFavoriteSpace = () => { + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: async (query: ISpaceQuery['query']) => { + const response = await apiClient.delete( + `/api/space/${query.spaceId}/favorites`, + ) + return response + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.SPACES] }) + }, + onError: (error: Error) => { + console.log(error) + }, + }) +} + +// 태그 조회 +export const useGetTags = ({ spaceId }: ISpaceQuery['query']) => { + return useQuery({ + queryKey: [QUERY_KEYS.TAGS, spaceId], + queryFn: async () => { + const response = await apiClient.get(`/api/space/${spaceId}/tags`) + return response.tags + }, + enabled: !!spaceId, + }) +} + +// 스페이스 권한 변경 +export const usePatchRole = (spaceId?: number) => { + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: async (query: IChangeRole['query']) => { + const response = await apiClient.patch( + `/api/space/${spaceId}/member/role`, + query, + ) + return response + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.SPACES, spaceId] }) + }, + onError: (error: Error) => { + console.log(error) + }, + }) +} + +// 스페이스 가져오기 +export const usePostScrapSpace = (spaceId?: number) => { + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: async ({ data, file }: IUpdateSpace) => { + const reqData = { ...data } + const formData = new FormData() + formData.append('request', JSON.stringify(reqData)) + file && formData.append('file', file) + const response = await apiClient.post( + `/api/space/${spaceId}/scrap/new`, + formData, + {}, + {}, + 'multipart', + ) + return response + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.SPACES] }) + queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.SPACES, spaceId] }) + }, + onError: (error: Error) => { + console.log(error) + }, + }) +} diff --git a/src/services/space/useSpaces.ts b/src/services/space/useSpaces.ts new file mode 100644 index 00000000..415b7535 --- /dev/null +++ b/src/services/space/useSpaces.ts @@ -0,0 +1,164 @@ +import { PAGE_SIZE, QUERY_KEYS } from '@/constants' +import { + IAcceptSpaceInvitation, + IInviteSpace, + ISearchSpace, +} from '@/models/space.model' +import { + UseMutationResult, + useMutation, + useQueryClient, +} from '@tanstack/react-query' +import { apiClient } from '../apiServices' + +// 전체 스페이스 필터 조회 (무한스크롤 fetch 함수) +export const fetchGetSpaces = async ({ + lastSpaceId = undefined, + lastFavoriteCount = undefined, + pageSize = PAGE_SIZE, + sort = 'created_at', + filter = 'all', +}: ISearchSpace['query']) => { + const params = { + pageSize: pageSize.toString(), + ...(lastSpaceId !== undefined && { lastSpaceId: lastSpaceId.toString() }), + ...(lastFavoriteCount !== undefined && { + lastFavoriteCount: lastFavoriteCount.toString(), + }), + ...(sort && { sort: sort }), + filter: filter, + } + const queryString = new URLSearchParams(params).toString() + + try { + const response = await apiClient.get(`/api/spaces?${queryString}`) + return response + } catch (e) { + if (e instanceof Error) throw new Error(e.message) + } +} + +// 스페이스 검색 (무한스크롤 fetch 함수) +export const fetchSearchSpaces = async ({ + pageNumber = 0, + pageSize, + sort, + filter, + keyWord, +}: ISearchSpace['query']) => { + const params = { + pageNumber: pageNumber.toString(), + pageSize: pageSize.toString(), + ...(sort && { sort: sort }), + filter: filter, + ...(keyWord && { keyWord: keyWord }), + } + const queryString = new URLSearchParams(params).toString() + + try { + const response = await apiClient.get(`/api/spaces/search?${queryString}`) + return response + } catch (e) { + if (e instanceof Error) throw new Error(e.message) + } +} + +// 내 스페이스 검색 (무한스크롤 fetch 함수) +export const fetchSearchMySpaces = async ({ + memberId, + pageNumber = 0, + pageSize, + filter, + keyWord, +}: ISearchSpace['query']) => { + const params = { + pageNumber: pageNumber.toString(), + pageSize: pageSize.toString(), + filter: filter, + ...(keyWord && { keyWord: keyWord }), + } + const queryString = new URLSearchParams(params).toString() + + try { + const response = await apiClient.get( + `/api/user/${memberId}/spaces?${queryString}`, + ) + return response + } catch (e) { + if (e instanceof Error) throw new Error(e.message) + } +} + +// 내 즐겨찾기 스페이스 검색 (무한스크롤 fetch 함수) +export const fetchGetMyFavoriteSpaces = async ({ + pageNumber = 0, + pageSize, + filter, + keyWord, +}: ISearchSpace['query']) => { + const params = { + pageNumber: pageNumber.toString(), + pageSize: pageSize.toString(), + ...(keyWord && { keyWord: keyWord }), + filter, + } + const queryString = new URLSearchParams(params).toString() + + try { + const response = await apiClient.get(`/api/user/favorites?${queryString}`) + return response + } catch (e) { + if (e instanceof Error) throw new Error(e.message) + } +} + +// 스페이스 초대 +export const usePostInviteSpace = (): UseMutationResult< + IInviteSpace['response'], + Error, + IInviteSpace['query'] +> => { + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: async (query: IInviteSpace['query']) => { + const response = await apiClient.post(`/api/space/invitations`, query) + return response + }, + onSuccess: (data) => { + queryClient.invalidateQueries({ + queryKey: [QUERY_KEYS.SPACES, data.spaceId], + }) + }, + onError: (error: Error) => { + console.log(error) + }, + }) +} + +// 스페이스 초대 수락 +export const usePostAccetpSpaceInvitation = (): UseMutationResult< + { spaceId: number }, + Error, + IAcceptSpaceInvitation['query'] +> => { + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: async (query: IAcceptSpaceInvitation['query']) => { + const response = await apiClient.post(`/api/spaces/invitations`, query) + return response + }, + onSuccess: (data) => { + queryClient.invalidateQueries({ + queryKey: [QUERY_KEYS.NOTIFICATION_COUNT], + }) + queryClient.invalidateQueries({ + queryKey: [QUERY_KEYS.INVITATIONS], + }) + }, + onError: (error: Error) => { + console.log(error) + }, + }) +} diff --git a/src/services/user/follow/follow.ts b/src/services/user/follow/follow.ts deleted file mode 100644 index 3dcb2936..00000000 --- a/src/services/user/follow/follow.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { apiClient } from '@/services/apiServices' - -export interface FetchGetFollowProps { - memberId?: number - pageNumber: number - pageSize: number -} - -const fetchGetFollowing = async ({ - memberId, - pageNumber, - pageSize, -}: FetchGetFollowProps) => { - const path = `/api/user/${memberId}/following` - const params = { - pageNumber: pageNumber.toString(), - pageSize: pageSize.toString(), - } - const queryString = new URLSearchParams(params).toString() - - try { - const response = await apiClient.get(`${path}?${queryString}`) - return response - } catch (e) { - if (e instanceof Error) throw new Error(e.message) - } -} - -const fetchGetFollowers = async ({ - memberId, - pageNumber, - pageSize, -}: FetchGetFollowProps) => { - const path = `/api/user/${memberId}/followers` - const params = { - pageNumber: pageNumber.toString(), - pageSize: pageSize.toString(), - } - const queryString = new URLSearchParams(params).toString() - - try { - const response = await apiClient.get(`${path}?${queryString}`) - return response - } catch (e) { - if (e instanceof Error) throw new Error(e.message) - } -} - -export interface FetchFollowUserProps { - memberId: number -} - -const fetchFollowUser = async ({ memberId }: FetchFollowUserProps) => { - const path = `/api/user/${memberId}/follow` - - try { - const response = await apiClient.post(path, {}) - return response - } catch (e) { - if (e instanceof Error) throw new Error(e.message) - } -} - -const fetchUnFollowUser = async ({ memberId }: FetchFollowUserProps) => { - const path = `/api/user/${memberId}/follow` - - try { - const response = await apiClient.delete(path) - return response - } catch (e) { - if (e instanceof Error) throw new Error(e.message) - } -} - -export { - fetchGetFollowing, - fetchGetFollowers, - fetchFollowUser, - fetchUnFollowUser, -} diff --git a/src/services/user/profile/favorites.ts b/src/services/user/profile/favorites.ts deleted file mode 100644 index 444f5d20..00000000 --- a/src/services/user/profile/favorites.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { apiClient } from '@/services/apiServices' -import { SearchSpaceReqBody } from '@/types' - -const fetchGetMyFavoriteSpaces = async ({ - pageNumber = 0, - pageSize, - keyWord, - filter, -}: SearchSpaceReqBody) => { - const path = '/api/user/favorites' - const params = { - pageNumber: pageNumber.toString(), - pageSize: pageSize.toString(), - ...(keyWord && { keyWord: keyWord }), - filter, - } - const queryString = new URLSearchParams(params).toString() - - try { - const response = await apiClient.get(`${path}?${queryString}`) - return response - } catch (e) { - if (e instanceof Error) throw new Error(e.message) - } -} - -export { fetchGetMyFavoriteSpaces } diff --git a/src/services/user/profile/profile.ts b/src/services/user/profile/profile.ts deleted file mode 100644 index 48f7743b..00000000 --- a/src/services/user/profile/profile.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { apiClient } from '@/services/apiServices' -import { RegisterReqBody } from '@/types' - -export interface FetchGetUserProfileProps { - memberId: number -} - -const fetchGetUserProfile = async ({ memberId }: FetchGetUserProfileProps) => { - const path = `/api/user/${memberId}/profile` - - try { - const response = await apiClient.get(path, { cache: 'no-store' }) - return response - } catch (e) { - if (e instanceof Error) throw new Error(e.message) - } -} - -const fetchPostUserProfile = async ( - userId: number, - data: RegisterReqBody, - file?: File, -) => { - const path = `/api/user/${userId}/profile` - const reqData = { ...data } - const formData = new FormData() - formData.append('request', JSON.stringify(reqData)) - file && formData.append('file', file) - - try { - const response = await apiClient.put(path, formData, {}, {}, 'multipart') - return response - } catch (e) { - if (e instanceof Error) throw new Error(e.message) - } -} - -export { fetchGetUserProfile, fetchPostUserProfile } diff --git a/src/services/user/search/search.ts b/src/services/user/search/search.ts deleted file mode 100644 index 0bee9912..00000000 --- a/src/services/user/search/search.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { apiClient } from '@/services/apiServices' -import { SearchUserReqBody } from '@/types' - -const fetchSearchUsers = async ({ - pageNumber, - pageSize, - keyword, -}: SearchUserReqBody) => { - const path = '/api/user/search' - const params = { - pageNumber: pageNumber.toString(), - pageSize: pageSize.toString(), - keyword, - } - const queryString = new URLSearchParams(params).toString() - - try { - const response = await apiClient.get(`${path}?${queryString}`) - return response - } catch (e) { - if (e instanceof Error) throw new Error(e.message) - } -} - -export { fetchSearchUsers } diff --git a/src/services/users/useUsers.ts b/src/services/users/useUsers.ts new file mode 100644 index 00000000..ba73c8f9 --- /dev/null +++ b/src/services/users/useUsers.ts @@ -0,0 +1,208 @@ +import { QUERY_KEYS } from '@/constants' +import { IFollow, IFollowList, IMemberSearch } from '@/models/member.model' +import { RegisterReqBody } from '@/types' +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' +import { apiClient } from '../apiServices' + +// 멤버 프로필 조회 +export const useGetUserProfile = (memberId: number) => { + return useQuery({ + queryKey: [QUERY_KEYS.MEMBERS, memberId], + queryFn: async () => { + const response = await apiClient.get(`/api/user/${memberId}/profile`) + return response + }, + enabled: !!memberId, + }) +} + +// 멤버 프로필 조회 서버 함수 +export const fetchGetUserProfile = async ({ + memberId, +}: { + memberId?: number +}) => { + try { + const response = await apiClient.get(`/api/user/${memberId}/profile`, { + cache: 'no-store', + }) + return response + } catch (e) { + if (e instanceof Error) throw new Error(e.message) + } +} + +// 멤버 프로필 수정 +export const usePutUserProfile = (memberId: number) => { + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: async ({ + memberId, + data, + file, + }: { + memberId: number + data: RegisterReqBody + file?: File + }) => { + const formData = new FormData() + formData.append('request', JSON.stringify(data)) + if (file) { + formData.append('file', file) + } + + const response = await apiClient.put( + `/api/user/${memberId}/profile`, + formData, + {}, + {}, + 'multipart', + ) + return response + }, + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: [QUERY_KEYS.MEMBERS, memberId], + }) + }, + onError: (error: Error) => { + console.log(error) + }, + }) +} +export const fetchPostUserProfile = async ( + userId: number, + data: RegisterReqBody, + file?: File, +) => { + const path = `/api/user/${userId}/profile` + const reqData = { ...data } + const formData = new FormData() + formData.append('request', JSON.stringify(reqData)) + file && formData.append('file', file) + + try { + const response = await apiClient.put(path, formData, {}, {}, 'multipart') + return response + } catch (e) { + if (e instanceof Error) throw new Error(e.message) + } +} + +// 팔로잉 목록 조회 (페이지네이션 fetch 함수) +export const fetchGetFollowing = async ({ + memberId, + pageNumber, + pageSize, +}: IFollowList) => { + const params = { + pageNumber: pageNumber.toString(), + pageSize: pageSize.toString(), + } + const queryString = new URLSearchParams(params).toString() + + try { + const response = await apiClient.get( + `/api/user/${memberId}/following?${queryString}`, + ) + return response + } catch (e) { + if (e instanceof Error) throw new Error(e.message) + } +} + +// 팔로워 목록 조회 (페이지네이션 fetch 함수) +export const fetchGetFollowers = async ({ + memberId, + pageNumber, + pageSize, +}: IFollowList) => { + const params = { + pageNumber: pageNumber.toString(), + pageSize: pageSize.toString(), + } + const queryString = new URLSearchParams(params).toString() + + try { + const response = await apiClient.get( + `/api/user/${memberId}/followers?${queryString}`, + ) + return response + } catch (e) { + if (e instanceof Error) throw new Error(e.message) + } +} + +// 팔로우 추가 +export const usePostFollow = (profileId?: number) => { + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: async ({ memberId }: IFollow) => { + const response = await apiClient.post(`/api/user/${memberId}/follow`, {}) + return response + }, + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: [QUERY_KEYS.MEMBERS, profileId], + }) + queryClient.invalidateQueries({ + queryKey: [QUERY_KEYS.FOLLOWING, profileId], + }) + queryClient.invalidateQueries({ + queryKey: [QUERY_KEYS.FOLLOWERS, profileId], + }) + }, + onError: (error: Error) => { + console.log(error) + }, + }) +} + +// 팔로우 삭제 +export const useDeleteFollow = (profileId?: number) => { + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: async ({ memberId }: IFollow) => { + const response = await apiClient.delete(`/api/user/${memberId}/follow`) + return response + }, + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: [QUERY_KEYS.MEMBERS, profileId], + }) + queryClient.invalidateQueries({ + queryKey: [QUERY_KEYS.FOLLOWING, profileId], + }) + queryClient.invalidateQueries({ + queryKey: [QUERY_KEYS.FOLLOWERS, profileId], + }) + }, + onError: (error: Error) => { + console.log(error) + }, + }) +} + +// 멤버 검색 (무한스크롤 fetch 함수) +export const fetchSearchUsers = async ({ + pageNumber, + pageSize, + keyword, +}: IMemberSearch) => { + const params = { + pageNumber: pageNumber.toString(), + pageSize: pageSize.toString(), + keyword, + } + const queryString = new URLSearchParams(params).toString() + + try { + const response = await apiClient.get(`/api/user/search?${queryString}`) + return response + } catch (e) { + if (e instanceof Error) throw new Error(e.message) + } +} diff --git a/src/types/index.ts b/src/types/index.ts index a39932bc..fc498b4f 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,3 +1,5 @@ +import { ChipColors } from '@/components/common/Chip/Chip' + export interface Space { userName: string spaceId: number @@ -258,3 +260,9 @@ export interface InvitationsNotification { spaceName: string isAccepted: boolean } + +export interface Tag { + name: string + color: ChipColors + tagId: number +}