From 08f1c3ec936422cd8b927c396e0dc5f3463fbd41 Mon Sep 17 00:00:00 2001 From: Yeongjun Kim Date: Wed, 12 Mar 2025 23:06:38 +0900 Subject: [PATCH 01/30] =?UTF-8?q?chore:=20axios=20=EC=84=A4=EC=B9=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 211 +++++++++++++++++++++++++++++++++++++++------- package.json | 1 + 2 files changed, 183 insertions(+), 29 deletions(-) diff --git a/package-lock.json b/package-lock.json index c58c938b..340d0228 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@next/font": "^14.2.5", "@tanstack/react-query": "^5.8.4", "@types/js-cookie": "^3.0.6", + "axios": "^1.8.2", "browser-image-compression": "^2.0.2", "dayjs": "^1.11.10", "js-cookie": "^3.0.5", @@ -3348,6 +3349,11 @@ "has-symbols": "^1.0.3" } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, "node_modules/at-least-node": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", @@ -3413,6 +3419,16 @@ "node": ">=4" } }, + "node_modules/axios": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.2.tgz", + "integrity": "sha512-ls4GYBm5aig9vWx8AWDSGLpnpDQRtWAfrjU+EuytuODrFBkqesN2RkOQCBzrA1RQNHw1SmRMSDDDSwzNAYQ6Rg==", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/axobject-query": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.2.1.tgz", @@ -3610,6 +3626,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", @@ -3831,6 +3859,17 @@ "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", "dev": true }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/commander": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", @@ -4165,6 +4204,14 @@ "rimraf": "bin.js" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/dequal": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", @@ -4260,6 +4307,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 +4440,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 +4484,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" @@ -5083,6 +5171,25 @@ "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", "dev": true }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/for-each": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", @@ -5091,6 +5198,20 @@ "is-callable": "^1.1.3" } }, + "node_modules/form-data": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", + "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fraction.js": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", @@ -5178,14 +5299,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 +5326,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 +5462,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 +5546,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 +5557,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 +5571,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 +6573,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", @@ -6460,7 +6610,6 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "peer": true, "engines": { "node": ">= 0.6" } @@ -6469,7 +6618,6 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "peer": true, "dependencies": { "mime-db": "1.52.0" }, @@ -7384,6 +7532,11 @@ "react-is": "^16.13.1" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "node_modules/punycode": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", diff --git a/package.json b/package.json index 3f5e9a66..ff2d901b 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "@next/font": "^14.2.5", "@tanstack/react-query": "^5.8.4", "@types/js-cookie": "^3.0.6", + "axios": "^1.8.2", "browser-image-compression": "^2.0.2", "dayjs": "^1.11.10", "js-cookie": "^3.0.5", From d17c68b69bd50625e220162d477ed838ee317021 Mon Sep 17 00:00:00 2001 From: Yeongjun Kim Date: Thu, 13 Mar 2025 21:25:30 +0900 Subject: [PATCH 02/30] =?UTF-8?q?refactor:=20Query=20Key=20=EC=83=81?= =?UTF-8?q?=EC=88=98=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/constants/index.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/constants/index.ts b/src/constants/index.ts index 4dcefc74..5c45a957 100644 --- a/src/constants/index.ts +++ b/src/constants/index.ts @@ -47,3 +47,10 @@ export const NOT_FOUND = { TEXT_2: '존재하지 않는 주소를 입력하셨거나,', TEXT_3: '요청하신 페이지의 주소가 변경, 삭제되어 찾을 수 없습니다.', } + +export const QUERY_KEYS = { + SPACES: 'spaces', + TAGS: 'tags', + LINKS: 'links', + POPULAR_LINKS: 'popularLinks', +} From b7ec2f88865dec27c58bc625c46f3669ab442d96 Mon Sep 17 00:00:00 2001 From: Yeongjun Kim Date: Thu, 13 Mar 2025 21:41:08 +0900 Subject: [PATCH 03/30] =?UTF-8?q?refactor:=20=EB=A7=81=ED=81=AC=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20fetch=20=ED=95=A8=EC=88=98=EB=A5=BC=20TanS?= =?UTF-8?q?tack=20Query=EB=A1=9C=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../PopularLinkList/PopularLinkList.tsx | 3 +- .../HydratePopularLinkList.tsx | 9 +- .../PopularLinkList/PopularLinkList.tsx | 68 ------- .../hooks/useGetPopularLinks.ts | 11 -- src/components/common/LinkItem/LinkItem.tsx | 14 +- .../common/LinkItem/hooks/useDeleteLink.ts | 30 --- .../common/LinkItem/hooks/useLikeLink.ts | 18 +- .../common/LinkItem/hooks/useReadSaveLink.ts | 6 +- .../common/LinkItem/hooks/useUpdateLink.ts | 31 +--- src/components/common/LinkList/LinkList.tsx | 8 +- .../common/LinkList/hooks/useCreateLink.ts | 36 +--- .../common/LinkList/hooks/useLinksQuery.ts | 4 +- src/models/link.model.ts | 23 +++ src/services/link/link.ts | 175 ------------------ src/services/link/useLink.ts | 125 +++++++++++++ 15 files changed, 189 insertions(+), 372 deletions(-) delete mode 100644 src/components/PopularLinkList/PopularLinkList.tsx delete mode 100644 src/components/PopularLinkList/hooks/useGetPopularLinks.ts delete mode 100644 src/components/common/LinkItem/hooks/useDeleteLink.ts create mode 100644 src/models/link.model.ts delete mode 100644 src/services/link/link.ts create mode 100644 src/services/link/useLink.ts 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'], - queryFn: fetchGetPopularLinks, + queryKey: [QUERY_KEYS.POPULAR_LINKS], + queryFn: getPopularLinks, }) return ( 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/common/LinkItem/LinkItem.tsx b/src/components/common/LinkItem/LinkItem.tsx index 237f802e..9df5b976 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, @@ -28,10 +29,9 @@ import { import useGetMeta from '../LinkList/hooks/useGetMeta' 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 } = @@ -106,11 +104,9 @@ const LinkItem = ({ const { isUpdateLinkLoading, handleUpdateLink } = useUpdateLink({ spaceId, linkId, - refetchTags, - }) - const { isDeleteLinkLoading, handleDeleteLink } = useDeleteLink({ - refetchTags, }) + const { mutate: deleteLink, isPending: isDeleteLinkLoading } = + useDeleteLink(spaceId) const { handleSaveReadInfo } = useReadSaveLink() const { isLiked, likeCount, handleClickLike } = useLikeLink({ spaceId, @@ -312,7 +308,7 @@ const LinkItem = ({ } })() })() - : spaceId && handleDeleteLink({ spaceId, linkId }) + : spaceId && deleteLink({ spaceId, linkId }) } 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..1c9f7253 100644 --- a/src/components/common/LinkItem/hooks/useReadSaveLink.ts +++ b/src/components/common/LinkItem/hooks/useReadSaveLink.ts @@ -1,5 +1,5 @@ import { useCurrentUser } from '@/hooks/useCurrentUser' -import { fetchReadSaveLink } from '@/services/link/link' +import { usePostReadSaveLink } from '@/services/link/useLink' export interface HandleSaveReadInfoProps { spaceId?: number @@ -8,11 +8,11 @@ export interface HandleSaveReadInfoProps { const useReadSaveLink = () => { const { isLoggedIn } = useCurrentUser() + const { mutate: postReadSaveLink } = usePostReadSaveLink() 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..d8ffb03a 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, @@ -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, @@ -164,7 +159,6 @@ const LinkList = ({ isMember={isMember} type={type} tags={tags} - refetchTags={refetchTags} key={link.linkId} /> )), 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/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/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/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..c1e0735e --- /dev/null +++ b/src/services/link/useLink.ts @@ -0,0 +1,125 @@ +import { + createLink, + deleteLikeLink, + deleteLink, + getLinks, + getPopularLinks, + postLikeLink, + postReadSaveLink, + updateLink, +} from '@/app/apis/link.api' +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' + +// 링크 조회 (페이지네이션 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 getLinks({ spaceId, searchParams: 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: (query: IUpdateLink['query']) => createLink({ query }), + 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: (query: IUpdateLink['query']) => updateLink({ query }), + 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?: number) => { + const queryClient = useQueryClient() + return useMutation({ + mutationFn: (query: ISpaceLink['query']) => deleteLink({ query }), + 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: () => getPopularLinks(), + }) +} + +// 링크 좋아요 +export const usePostLikeLink = () => { + return useMutation({ + mutationFn: (query: ILikeLink['query']) => postLikeLink({ query }), + onError: (error: Error) => { + console.log(error) + }, + }) +} + +// 링크 좋아요 취소 +export const useDeleteLikeLink = () => { + return useMutation({ + mutationFn: (query: ILikeLink['query']) => deleteLikeLink({ query }), + onError: (error: Error) => { + console.log(error) + }, + }) +} + +// 링크 읽기 저장 +export const usePostReadSaveLink = () => { + const queryClient = useQueryClient() + return useMutation({ + mutationFn: (query: ISpaceLink['query']) => postReadSaveLink({ query }), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.LINKS] }) + }, + onError: (error: Error) => { + console.log(error) + }, + }) +} From 170176c4af30c7eed1d85a7a7877c46ee9713d05 Mon Sep 17 00:00:00 2001 From: Yeongjun Kim Date: Fri, 14 Mar 2025 12:27:25 +0900 Subject: [PATCH 04/30] =?UTF-8?q?refactor:=20=EC=8A=A4=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=8A=A4=20=EA=B4=80=EB=A0=A8=20fetch=20=ED=95=A8=EC=88=98?= =?UTF-8?q?=EB=A5=BC=20TanStack=20Query=EB=A1=9C=20=EB=A6=AC=ED=8C=A9?= =?UTF-8?q?=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../(routes)/space/[spaceId]/comment/page.tsx | 4 +- src/app/(routes)/space/[spaceId]/layout.tsx | 4 +- src/app/(routes)/space/[spaceId]/page.tsx | 13 +- .../(routes)/space/[spaceId]/setting/page.tsx | 10 +- .../(routes)/user/[userId]/favorite/page.tsx | 2 +- src/app/(routes)/user/[userId]/space/page.tsx | 2 +- .../SearchController/SearchController.tsx | 2 +- src/components/Space/SpaceForm.tsx | 40 ++--- .../MainSpaceList/HydrateMainSpaceList.tsx | 3 +- .../common/MainSpaceList/MainSpaceList.tsx | 6 +- .../hooks/useAcceptNotification.ts | 5 +- .../common/Sidebar/hooks/useMySpace.ts | 6 +- .../common/Space/hooks/useFavorites.ts | 16 +- .../common/Space/hooks/useGetSpace.ts | 38 ---- .../common/Space/hooks/useGetTags.ts | 27 ++- .../SpaceMemberList/SpaceMemberList.tsx | 11 +- src/hooks/useTagParam.ts | 2 +- src/models/space.model.ts | 91 ++++++++++ src/services/space/invitation.ts | 20 --- src/services/space/space.ts | 134 -------------- src/services/space/spaces.ts | 97 ---------- src/services/space/useSpace.ts | 168 ++++++++++++++++++ src/services/space/useSpaces.ts | 164 +++++++++++++++++ src/services/user/profile/favorites.ts | 27 --- src/types/index.ts | 8 + 25 files changed, 503 insertions(+), 397 deletions(-) delete mode 100644 src/components/common/Space/hooks/useGetSpace.ts create mode 100644 src/models/space.model.ts delete mode 100644 src/services/space/invitation.ts delete mode 100644 src/services/space/space.ts delete mode 100644 src/services/space/spaces.ts create mode 100644 src/services/space/useSpace.ts create mode 100644 src/services/space/useSpaces.ts delete mode 100644 src/services/user/profile/favorites.ts diff --git a/src/app/(routes)/space/[spaceId]/comment/page.tsx b/src/app/(routes)/space/[spaceId]/comment/page.tsx index a6c82f9b..904a9ee1 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' @@ -15,6 +14,7 @@ import { useModal } from '@/hooks' import { useCurrentUser } from '@/hooks/useCurrentUser' import useSpaceComment from '@/hooks/useSpaceComment' import { fetchGetComments } from '@/services/comment/comment' +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..a1e20e1c 100644 --- a/src/app/(routes)/space/[spaceId]/layout.tsx +++ b/src/app/(routes)/space/[spaceId]/layout.tsx @@ -1,4 +1,4 @@ -import { fetchGetSpace } from '@/services/space/space' +import { getSpace } from '@/app/apis/space.api' import { Metadata } from 'next' type SpaceLayoutProps = { @@ -8,7 +8,7 @@ type SpaceLayoutProps = { export async function generateMetadata({ params: { spaceId }, }: SpaceLayoutProps): Promise { - const space = await fetchGetSpace({ spaceId }) + const space = await getSpace({ spaceId }) 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]/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/components/SearchController/SearchController.tsx b/src/components/SearchController/SearchController.tsx index 24de8471..c85858ea 100644 --- a/src/components/SearchController/SearchController.tsx +++ b/src/components/SearchController/SearchController.tsx @@ -3,7 +3,7 @@ import { CategoryList, Dropdown, SpaceList } from '@/components' import UserList from '@/components/UserList/UserList' import { useCategoryParam, useSortParam } from '@/hooks' -import { fetchSearchSpaces } from '@/services/space/spaces' +import { fetchSearchSpaces } from '@/services/space/useSpaces' import { fetchSearchUsers } from '@/services/user/search/search' import { cls } from '@/utils' import { useSearchParams } from 'next/navigation' diff --git a/src/components/Space/SpaceForm.tsx b/src/components/Space/SpaceForm.tsx index 9ac3204b..3e823521 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,13 @@ const SpaceForm = ({ spaceType, space }: SpaceFormProps) => { notify('error', '카테고리를 선택해 주세요.') } else { if (spaceType === 'Create') { - const { spaceId } = await feachCreateSpace(data, imageFile) + const { spaceId } = await postSpace({ data, file: imageFile }) + console.log(spaceId) 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 +101,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 +149,7 @@ const SpaceForm = ({ spaceType, space }: SpaceFormProps) => {
- @{getSpace.space?.spaceName}{' '} + @{spaceData.space?.spaceName}{' '} 에서 가져오는 중
@@ -222,18 +226,6 @@ const SpaceForm = ({ spaceType, space }: SpaceFormProps) => { onChange={() => setValue('isComment', !getValues('isComment'))} />
- {/*
-
- 링크 3줄 요약 여부 -
- {}} - /> -
*/}
읽음 처리 여부
{ }) 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/NotificationList/hooks/useAcceptNotification.ts b/src/components/common/NotificationList/hooks/useAcceptNotification.ts index 3af3c9af..8c63a981 100644 --- a/src/components/common/NotificationList/hooks/useAcceptNotification.ts +++ b/src/components/common/NotificationList/hooks/useAcceptNotification.ts @@ -1,4 +1,4 @@ -import { fetchAccetpSpaceInvitation } from '@/services/space/spaces' +import { usePostAccetpSpaceInvitation } from '@/services/space/useSpaces' import { useQueryClient } from '@tanstack/react-query' import { useRouter } from 'next/navigation' import { notify } from '../../Toast/Toast' @@ -15,12 +15,13 @@ export interface HandleAcceptInviteProps { const useAcceptNotification = ({ type }: UseAcceptNotificationProps) => { 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] }) 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/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..c89d914a 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({ spaceId, ...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/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/models/space.model.ts b/src/models/space.model.ts new file mode 100644 index 00000000..93d1a9ca --- /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/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..a86be0ee --- /dev/null +++ b/src/services/space/useSpace.ts @@ -0,0 +1,168 @@ +import { + createSpace, + deleteFavoriteSpace, + deleteSpace, + getSpace, + getTags, + patchRole, + patchSpace, + postFavoriteSpace, + postScrapSpace, +} from '@/app/apis/space.api' +import { QUERY_KEYS } from '@/constants' +import { + IChangeRole, + ICreateSpace, + ISpaceQuery, + IUpdateSpace, +} from '@/models/space.model' +import { Tag } from '@/types' +import { + UseMutationResult, + useMutation, + useQuery, + useQueryClient, +} from '@tanstack/react-query' +import Cookies from 'js-cookie' + +export interface FetchGetSpaceProps { + spaceId?: number +} + +// 스페이스 상세 조회 +export const useGetSpace = (spaceId?: number) => { + const token = Cookies.get('Auth-token') + return useQuery({ + queryKey: [QUERY_KEYS.SPACES, spaceId], + queryFn: () => getSpace({ spaceId }), + enabled: !!spaceId && !!token, + }) +} + +// 스페이스 생성 +export const usePostSpace = (): UseMutationResult< + { spaceId: number }, + Error, + ICreateSpace +> => { + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: ({ data, file }: ICreateSpace) => { + const response = createSpace({ data, file }) + 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 response = patchSpace({ spaceId, data, file }) + 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: (query: ISpaceQuery['query']) => deleteSpace({ query }), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.SPACES] }) + }, + onError: (error: Error) => { + console.log(error) + }, + }) +} + +// 스페이스 즐겨찾기 +export const usePostFavoriteSpace = () => { + const queryClient = useQueryClient() + return useMutation({ + mutationFn: (query: ISpaceQuery['query']) => postFavoriteSpace({ query }), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.SPACES] }) + }, + onError: (error: Error) => { + console.log(error) + }, + }) +} + +// 스페이스 즐겨찾기 삭제 +export const useDeleteFavoriteSpace = () => { + const queryClient = useQueryClient() + return useMutation({ + mutationFn: (query: ISpaceQuery['query']) => deleteFavoriteSpace({ query }), + 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 getTags({ spaceId }) + return response.tags + }, + enabled: !!spaceId, + }) +} + +// 스페이스 권한 변경 +export const usePatchRole = (spaceId?: number) => { + const queryClient = useQueryClient() + return useMutation({ + mutationFn: (query: IChangeRole['query']) => patchRole({ query }), + 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 response = postScrapSpace({ spaceId, data, file }) + 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..283c5187 --- /dev/null +++ b/src/services/space/useSpaces.ts @@ -0,0 +1,164 @@ +import { + getAccetpSpaceInvitation, + getSearchMyFavoriteSpaces, + getSearchMySpaces, + getSearchSpaces, + getSpaces, + postInviteSpace, +} from '@/app/apis/spaces.api' +import { PAGE_SIZE, QUERY_KEYS } from '@/constants' +import { + IAcceptSpaceInvitation, + IInviteSpace, + ISearchSpace, +} from '@/models/space.model' +import { + UseMutationResult, + useMutation, + useQueryClient, +} from '@tanstack/react-query' + +// 전체 스페이스 필터 조회 (무한스크롤 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 getSpaces({ searchParams: 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 getSearchSpaces({ searchParams: 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 getSearchMySpaces({ + memberId, + searchParams: 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 getSearchMyFavoriteSpaces({ + searchParams: 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: (query: IInviteSpace['query']) => postInviteSpace(query), + 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: (query: IAcceptSpaceInvitation['query']) => + getAccetpSpaceInvitation(query), + onSuccess: (data) => { + queryClient.invalidateQueries({ + queryKey: [QUERY_KEYS.SPACES, data.spaceId], + }) + }, + onError: (error: Error) => { + console.log(error) + }, + }) +} 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/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 +} From 46542371c501d4db0244aa74994c05471eade598 Mon Sep 17 00:00:00 2001 From: Yeongjun Kim Date: Fri, 14 Mar 2025 16:46:44 +0900 Subject: [PATCH 05/30] =?UTF-8?q?refactor:=20=EB=A9=A4=EB=B2=84=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20fetch=20=ED=95=A8=EC=88=98=EB=A5=BC=20TanS?= =?UTF-8?q?tack=20Query=EB=A1=9C=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/(routes)/user/[userId]/layout.tsx | 4 +- src/app/(routes)/user/[userId]/page.tsx | 10 +- .../FollowListButton/FollowListButton.tsx | 2 +- .../SearchController/SearchController.tsx | 4 +- .../SettingController/SettingController.tsx | 6 +- src/components/UserInfoForm/UserInfoForm.tsx | 15 +- .../UserList/hooks/useUsersQuery.ts | 4 +- .../common/FollowList/FollowList.tsx | 5 +- .../common/FollowList/hooks/useFollowQuery.ts | 7 +- src/components/common/User/User.tsx | 1 + src/constants/index.ts | 4 + src/hooks/useFollowUser.ts | 21 +-- src/hooks/useGetProfile.ts | 20 +-- src/models/member.model.ts | 15 ++ src/services/members/useMember.ts | 164 ++++++++++++++++++ src/services/user/follow/follow.ts | 80 --------- src/services/user/profile/profile.ts | 38 ---- src/services/user/search/search.ts | 25 --- 18 files changed, 230 insertions(+), 195 deletions(-) create mode 100644 src/models/member.model.ts create mode 100644 src/services/members/useMember.ts delete mode 100644 src/services/user/follow/follow.ts delete mode 100644 src/services/user/profile/profile.ts delete mode 100644 src/services/user/search/search.ts diff --git a/src/app/(routes)/user/[userId]/layout.tsx b/src/app/(routes)/user/[userId]/layout.tsx index 08c14350..9fc74392 100644 --- a/src/app/(routes)/user/[userId]/layout.tsx +++ b/src/app/(routes)/user/[userId]/layout.tsx @@ -1,5 +1,5 @@ +import { getMemberProfile } from '@/app/apis/member.api' import { ProfileTap } from '@/components' -import { fetchGetUserProfile } from '@/services/user/profile/profile' import { Metadata } from 'next' export type UserLayoutProps = { @@ -9,7 +9,7 @@ export type UserLayoutProps = { export async function generateMetadata({ params: { userId }, }: UserLayoutProps): Promise { - const user = await fetchGetUserProfile({ memberId: userId }) + const user = await getMemberProfile({ memberId: userId }) return { title: user.nickname, diff --git a/src/app/(routes)/user/[userId]/page.tsx b/src/app/(routes)/user/[userId]/page.tsx index 80f86169..483a8e93 100644 --- a/src/app/(routes)/user/[userId]/page.tsx +++ b/src/app/(routes)/user/[userId]/page.tsx @@ -1,3 +1,5 @@ +'use client' + import { Avatar, CategoryListItem, @@ -5,13 +7,11 @@ import { ProfileEditButton, } from '@/components' import { CATEGORIES_RENDER, PROFILE_MSG } from '@/constants' -import { fetchGetUserProfile } from '@/services/user/profile/profile' +import { useGetMemberProfile } from '@/services/members/useMember' import { UserLayoutProps } from './layout' -export default async function UserPage({ - params: { userId }, -}: UserLayoutProps) { - const user = await fetchGetUserProfile({ memberId: userId }) +export default function UserPage({ params: { userId } }: UserLayoutProps) { + const { data: user } = useGetMemberProfile(Number(userId)) return ( <> diff --git a/src/components/FollowListButton/FollowListButton.tsx b/src/components/FollowListButton/FollowListButton.tsx index 41ead172..3eb1b475 100644 --- a/src/components/FollowListButton/FollowListButton.tsx +++ b/src/components/FollowListButton/FollowListButton.tsx @@ -6,7 +6,7 @@ import { useCurrentUser } from '@/hooks/useCurrentUser' import { fetchGetFollowers, fetchGetFollowing, -} from '@/services/user/follow/follow' +} from '@/services/members/useMember' import { UserProfileResBody } from '@/types' import FollowList from '../common/FollowList/FollowList' import LoginModal from '../common/Modal/LoginModal' diff --git a/src/components/SearchController/SearchController.tsx b/src/components/SearchController/SearchController.tsx index c85858ea..80464375 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 { fetchSearchMembers } from '@/services/members/useMember' import { fetchSearchSpaces } from '@/services/space/useSpaces' -import { fetchSearchUsers } from '@/services/user/search/search' import { cls } from '@/utils' import { useSearchParams } from 'next/navigation' @@ -61,7 +61,7 @@ const SearchController = () => { {target === 'user' && ( )} diff --git a/src/components/SettingController/SettingController.tsx b/src/components/SettingController/SettingController.tsx index c6f17566..9046df7b 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 { useGetMemberProfile } from '@/services/members/useMember' const SettingController = () => { const { currentUser } = useCurrentUser() + const { data: user } = useGetMemberProfile(currentUser?.memberId || 0) return (
- {currentUser && ( + {user && ( )} diff --git a/src/components/UserInfoForm/UserInfoForm.tsx b/src/components/UserInfoForm/UserInfoForm.tsx index 6148c098..befae41a 100644 --- a/src/components/UserInfoForm/UserInfoForm.tsx +++ b/src/components/UserInfoForm/UserInfoForm.tsx @@ -4,7 +4,7 @@ import { ChangeEvent, useEffect, useRef, useState } from 'react' import { useForm } from 'react-hook-form' import { Avatar, CategoryList, Input } from '@/components' import { fetchPostEmail, fetchPostEmailVerify } from '@/services/email' -import { fetchPostUserProfile } from '@/services/user/profile/profile' +import { usePutMemberProfile } from '@/services/members/useMember' import { UserProfileResBody } from '@/types' import { cls } from '@/utils' import { CheckIcon } from '@heroicons/react/24/solid' @@ -39,6 +39,9 @@ const UserInfoForm = ({ userData, formType }: UserInfoFormProps) => { const [imageFile, setImageFile] = useState() const router = useRouter() const [isVerification, setVerification] = useState(false) + const { mutate: putMemberProfileMutation } = usePutMemberProfile( + userData?.memberId || 0, + ) useEffect(() => { setThumnail(userData?.profileImagePath) @@ -148,12 +151,14 @@ const UserInfoForm = ({ userData, formType }: UserInfoFormProps) => { } } - const handleSettingUser = async ( - data: RegisterReqBody & EmailVerifyReqBody, - ) => { + const handleSettingUser = (data: RegisterReqBody & EmailVerifyReqBody) => { try { userData?.memberId && - (await fetchPostUserProfile(userData?.memberId, data, imageFile)) + putMemberProfileMutation({ + memberId: userData?.memberId, + data, + file: imageFile, + }) notify('success', '수정되었습니다.') router.refresh() router.back() 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/FollowList/FollowList.tsx b/src/components/common/FollowList/FollowList.tsx index af7eb221..f947386b 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 @@ -31,6 +31,7 @@ const FollowList = ({ followingCount, setFollowingCount, }: FollowListProps) => { + console.log(memberId) const { followList, fetchNextPage, hasNextPage, isFollowLoading } = useFollowQuery({ memberId, 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/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 5c45a957..f10f45e6 100644 --- a/src/constants/index.ts +++ b/src/constants/index.ts @@ -50,7 +50,11 @@ export const NOT_FOUND = { export const QUERY_KEYS = { SPACES: 'spaces', + COMMENTS: 'comments', TAGS: 'tags', LINKS: 'links', POPULAR_LINKS: 'popularLinks', + MEMBERS: 'members', + FOLLOWING: 'following', + FOLLOWERS: 'followers', } diff --git a/src/hooks/useFollowUser.ts b/src/hooks/useFollowUser.ts index 36689340..f57a770b 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/members/useMember' 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..e6d0b13e 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 { useGetMemberProfile } from '@/services/members/useMember' 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 } = useGetMemberProfile(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/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/services/members/useMember.ts b/src/services/members/useMember.ts new file mode 100644 index 00000000..1e2ceeb6 --- /dev/null +++ b/src/services/members/useMember.ts @@ -0,0 +1,164 @@ +import { + deleteFollow, + getFollowers, + getFollowing, + getMemberProfile, + getSearchMembers, + postFollow, + putMemberProfile, +} from '@/app/apis/member.api' +import { QUERY_KEYS } from '@/constants' +import { IFollow, IFollowList, IMemberSearch } from '@/models/member.model' +import { RegisterReqBody, SearchUserReqBody } from '@/types' +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' + +// 멤버 프로필 조회 +export const useGetMemberProfile = (memberId: number) => { + return useQuery({ + queryKey: [QUERY_KEYS.MEMBERS, memberId], + queryFn: () => getMemberProfile({ memberId }), + enabled: !!memberId, + }) +} + +// 멤버 프로필 수정 +export const usePutMemberProfile = (memberId: number) => { + const queryClient = useQueryClient() + return useMutation({ + mutationFn: ({ + memberId, + data, + file, + }: { + memberId: number + data: RegisterReqBody + file?: File + }) => { + const response = putMemberProfile({ memberId, data, file }) + return response + }, + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: [QUERY_KEYS.MEMBERS, memberId], + }) + }, + onError: (error: Error) => { + console.log(error) + }, + }) +} + +// 팔로잉 목록 조회 (페이지네이션 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 getFollowing({ memberId, searchParams: 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 getFollowers({ memberId, searchParams: 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: ({ memberId }: IFollow) => { + const response = postFollow({ memberId }) + 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: ({ memberId }: IFollow) => { + const response = deleteFollow({ memberId }) + 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 fetchSearchMembers = async ({ + pageNumber, + pageSize, + keyword, +}: IMemberSearch) => { + const params = { + pageNumber: pageNumber.toString(), + pageSize: pageSize.toString(), + keyword, + } + const queryString = new URLSearchParams(params).toString() + + try { + const response = await getSearchMembers({ + searchParams: queryString, + }) + return response + } catch (e) { + if (e instanceof Error) throw new Error(e.message) + } +} 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/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 } From 5db4783d9a18005ec408de85e6258f6263cc7c1d Mon Sep 17 00:00:00 2001 From: Yeongjun Kim Date: Fri, 14 Mar 2025 17:33:40 +0900 Subject: [PATCH 06/30] =?UTF-8?q?refactor:=20=EB=8C=93=EA=B8=80=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20fetch=20=ED=95=A8=EC=88=98=EB=A5=BC=20TanS?= =?UTF-8?q?tack=20Query=EB=A1=9C=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../(routes)/space/[spaceId]/comment/page.tsx | 2 +- src/app/apis/comments.api.ts | 115 ++++++++++++++ src/components/CommentList/CommentList.tsx | 2 +- .../CommentList/hooks/useCommentsQuery.ts | 4 +- .../ReplyList/hooks/useRepliesQuery.ts | 4 +- src/components/common/Comment/Comment.tsx | 2 +- .../common/Comment/hooks/useComment.ts | 19 +-- src/constants/index.ts | 1 + src/hooks/useSpaceComment.ts | 43 ++--- src/models/comments.model.ts | 6 + src/services/comment/comment.ts | 71 --------- src/services/comment/reply.ts | 41 ----- src/services/comments/useComments.ts | 148 ++++++++++++++++++ 13 files changed, 304 insertions(+), 154 deletions(-) create mode 100644 src/app/apis/comments.api.ts create mode 100644 src/models/comments.model.ts delete mode 100644 src/services/comment/comment.ts delete mode 100644 src/services/comment/reply.ts create mode 100644 src/services/comments/useComments.ts diff --git a/src/app/(routes)/space/[spaceId]/comment/page.tsx b/src/app/(routes)/space/[spaceId]/comment/page.tsx index 904a9ee1..060723d3 100644 --- a/src/app/(routes)/space/[spaceId]/comment/page.tsx +++ b/src/app/(routes)/space/[spaceId]/comment/page.tsx @@ -13,7 +13,7 @@ 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' diff --git a/src/app/apis/comments.api.ts b/src/app/apis/comments.api.ts new file mode 100644 index 00000000..b610bab3 --- /dev/null +++ b/src/app/apis/comments.api.ts @@ -0,0 +1,115 @@ +import axios from 'axios' +import Cookies from 'js-cookie' + +const baseURL = process.env.NEXT_PUBLIC_API_ADDRESS + +// 댓글 조회 +export const getComments = async ({ + spaceId, + searchParams, +}: { + spaceId: number | undefined + searchParams: string +}) => { + const token = Cookies.get('Auth-token') + const response = await axios.get( + `${baseURL}/spaces/${spaceId}/comments?${searchParams}`, + { + headers: { + Authorization: `Bearer ${token}`, + }, + }, + ) + return response.data +} + +// 댓글 생성 +export const createComment = async ({ + spaceId, + content, +}: { + spaceId?: number + content: string +}) => { + const token = Cookies.get('Auth-token') + const response = await axios.post( + `${baseURL}/spaces/${spaceId}/comments`, + { content }, + { + headers: { Authorization: `Bearer ${token}` }, + }, + ) + return response.data +} + +// 댓글 수정 +export const updateComment = async ({ + spaceId, + commentId, + content, +}: { + spaceId?: number + commentId?: number + content: string +}) => { + const token = Cookies.get('Auth-token') + const response = await axios.put( + `${baseURL}/spaces/${spaceId}/comments/${commentId}`, + { content }, + { headers: { Authorization: `Bearer ${token}` } }, + ) + return response.data +} + +// 댓글 삭제 +export const deleteComment = async ({ + spaceId, + commentId, +}: { + spaceId: number | undefined + commentId: number | undefined +}) => { + const token = Cookies.get('Auth-token') + const response = await axios.delete( + `${baseURL}/spaces/${spaceId}/comments/${commentId}`, + { headers: { Authorization: `Bearer ${token}` } }, + ) + return response.data +} + +// 대댓글 조회 +export const getReplies = async ({ + spaceId, + commentId, + searchParams, +}: { + spaceId: number | undefined + commentId: number | undefined + searchParams: string +}) => { + const token = Cookies.get('Auth-token') + const response = await axios.get( + `${baseURL}/spaces/${spaceId}/comments/${commentId}/replies?${searchParams}`, + { headers: { Authorization: `Bearer ${token}` } }, + ) + return response.data +} + +// 대댓글 생성 +export const createReply = async ({ + spaceId, + commentId, + content, +}: { + spaceId: number + commentId: number + content: string +}) => { + const token = Cookies.get('Auth-token') + const response = await axios.post( + `${baseURL}/spaces/${spaceId}/comments/${commentId}/replies`, + { content }, + { headers: { Authorization: `Bearer ${token}` } }, + ) + return response.data +} 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/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/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/constants/index.ts b/src/constants/index.ts index f10f45e6..b9f2cbb9 100644 --- a/src/constants/index.ts +++ b/src/constants/index.ts @@ -51,6 +51,7 @@ export const NOT_FOUND = { export const QUERY_KEYS = { SPACES: 'spaces', COMMENTS: 'comments', + REPLIES: 'replies', TAGS: 'tags', LINKS: 'links', POPULAR_LINKS: 'popularLinks', diff --git a/src/hooks/useSpaceComment.ts b/src/hooks/useSpaceComment.ts index 8b578724..c640a784 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,11 +42,19 @@ 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, + ) + console.log(comment.parentCommentId) const handleOpen = useCallback( (commentId: number) => { if (openedComments.includes(commentId)) { @@ -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/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/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..2d39acd1 --- /dev/null +++ b/src/services/comments/useComments.ts @@ -0,0 +1,148 @@ +import { + createComment, + createReply, + deleteComment, + getComments, + getReplies, + updateComment, +} from '@/app/apis/comments.api' +import { QUERY_KEYS } from '@/constants' +import { ICommentQuery } from '@/models/comments.model' +import { useMutation, useQueryClient } from '@tanstack/react-query' + +// 댓글 조회 (페이지네이션 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 getComments({ spaceId, searchParams: 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: ({ spaceId, content }: { spaceId: number; content: string }) => + createComment({ spaceId, content }), + 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: ({ + spaceId, + commentId, + content, + }: { + spaceId: number + commentId: number + content: string + }) => updateComment({ spaceId, commentId, content }), + 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: ({ commentId }: { commentId: number }) => + deleteComment({ spaceId, commentId }), + 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 getReplies({ + spaceId, + commentId, + searchParams: queryString, + }) + return response + } catch (e) { + if (e instanceof Error) throw new Error(e.message) + } +} + +// 대댓글 생성 +export const usePostReply = (spaceId: number, parentCommentId?: number) => { + console.log(parentCommentId) + const queryClient = useQueryClient() + return useMutation({ + mutationFn: ({ + spaceId, + commentId, + content, + }: { + spaceId: number + commentId: number + content: string + }) => createReply({ spaceId, commentId, content }), + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: [QUERY_KEYS.COMMENTS, spaceId], + }) + queryClient.invalidateQueries({ + queryKey: [QUERY_KEYS.REPLIES, spaceId], + }) + }, + onError: (error: Error) => { + console.log(error) + }, + }) +} From 1d95455a0ef7aee7792d103f0e2f4abe3a805dfc Mon Sep 17 00:00:00 2001 From: Yeongjun Kim Date: Fri, 14 Mar 2025 18:44:13 +0900 Subject: [PATCH 07/30] =?UTF-8?q?refactor:=20=EC=95=8C=EB=A6=BC=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20fetch=20=ED=95=A8=EC=88=98=EB=A5=BC=20TanS?= =?UTF-8?q?tack=20Query=EB=A1=9C=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/(routes)/notification/invite/page.tsx | 2 +- .../common/Header/hooks/useHeader.ts | 8 +-- .../common/Notification/Notification.tsx | 4 +- ...otification.ts => useNotificationPopup.ts} | 4 +- .../NotificationList/NotificationList.tsx | 4 +- .../hooks/useAcceptNotification.ts | 13 +---- .../hooks/useDeleteNotification.ts | 18 +++---- .../hooks/useNotificationQuery.ts | 7 ++- src/constants/index.ts | 2 + src/models/notification.model.ts | 9 ++++ src/services/notification/index.ts | 20 ------- src/services/notification/invitations.ts | 34 ------------ src/services/notification/useNotification.ts | 52 +++++++++++++++++++ 13 files changed, 83 insertions(+), 94 deletions(-) rename src/components/common/Notification/hooks/{useNotification.ts => useNotificationPopup.ts} (94%) create mode 100644 src/models/notification.model.ts delete mode 100644 src/services/notification/index.ts delete mode 100644 src/services/notification/invitations.ts create mode 100644 src/services/notification/useNotification.ts 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/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/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 8c63a981..abc2a554 100644 --- a/src/components/common/NotificationList/hooks/useAcceptNotification.ts +++ b/src/components/common/NotificationList/hooks/useAcceptNotification.ts @@ -1,20 +1,15 @@ import { usePostAccetpSpaceInvitation } from '@/services/space/useSpaces' -import { useQueryClient } from '@tanstack/react-query' 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 ({ @@ -24,10 +19,6 @@ const useAcceptNotification = ({ type }: UseAcceptNotificationProps) => { 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/constants/index.ts b/src/constants/index.ts index b9f2cbb9..92821061 100644 --- a/src/constants/index.ts +++ b/src/constants/index.ts @@ -58,4 +58,6 @@ export const QUERY_KEYS = { MEMBERS: 'members', FOLLOWING: 'following', FOLLOWERS: 'followers', + NOTIFICATION_COUNT: 'notificationCount', + INVITATIONS: 'invitations', } 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/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..ef916b9e --- /dev/null +++ b/src/services/notification/useNotification.ts @@ -0,0 +1,52 @@ +import { + deleteNotification, + getInvitations, + getNotificationCount, +} from '@/app/apis/notification.api' +import { QUERY_KEYS } from '@/constants' +import { IInvitationsQuery } from '@/models/notification.model' +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' + +// 미확인 알림 개수 조회 +export const useGetUnCheckedNotifications = () => { + return useQuery({ + queryKey: [QUERY_KEYS.NOTIFICATION_COUNT], + queryFn: () => getNotificationCount(), + }) +} + +// 초대 알림 조회 +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 getInvitations({ searchParams: queryString }) + return response + } catch (e) { + if (e instanceof Error) throw new Error(e.message) + } +} + +// 알림 삭제 +export const useDeleteNotifications = () => { + const queryClient = useQueryClient() + return useMutation({ + mutationFn: (notificationId: number) => deleteNotification(notificationId), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.INVITATIONS] }) + queryClient.invalidateQueries({ + queryKey: [QUERY_KEYS.NOTIFICATION_COUNT], + }) + }, + onError: (error: Error) => { + console.log(error) + }, + }) +} From f8ec756ffc7128c58883c92348ba02c99061a374 Mon Sep 17 00:00:00 2001 From: Yeongjun Kim Date: Fri, 14 Mar 2025 19:27:48 +0900 Subject: [PATCH 08/30] =?UTF-8?q?refactor:=20=EC=9D=B4=EB=A9=94=EC=9D=BC?= =?UTF-8?q?=20=EA=B4=80=EB=A0=A8=20fetch=20=ED=95=A8=EC=88=98=EB=A5=BC=20T?= =?UTF-8?q?anStack=20Query=EB=A1=9C=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/UserInfoForm/UserInfoForm.tsx | 8 +++--- src/models/emails.model.ts | 7 ++++++ src/services/email/index.ts | 25 ------------------- src/services/email/useEmails.ts | 26 ++++++++++++++++++++ 4 files changed, 38 insertions(+), 28 deletions(-) create mode 100644 src/models/emails.model.ts delete mode 100644 src/services/email/index.ts create mode 100644 src/services/email/useEmails.ts diff --git a/src/components/UserInfoForm/UserInfoForm.tsx b/src/components/UserInfoForm/UserInfoForm.tsx index befae41a..77ff3cdd 100644 --- a/src/components/UserInfoForm/UserInfoForm.tsx +++ b/src/components/UserInfoForm/UserInfoForm.tsx @@ -3,7 +3,7 @@ import { ChangeEvent, useEffect, useRef, useState } from 'react' import { useForm } from 'react-hook-form' import { Avatar, CategoryList, Input } from '@/components' -import { fetchPostEmail, fetchPostEmailVerify } from '@/services/email' +import { usePostEmail, usePostEmailVerify } from '@/services/email/useEmails' import { usePutMemberProfile } from '@/services/members/useMember' import { UserProfileResBody } from '@/types' import { cls } from '@/utils' @@ -42,6 +42,8 @@ const UserInfoForm = ({ userData, formType }: UserInfoFormProps) => { const { mutate: putMemberProfileMutation } = usePutMemberProfile( userData?.memberId || 0, ) + const { mutateAsync: postEmail } = usePostEmail() + const { mutateAsync: postEmailVerify } = usePostEmailVerify() useEffect(() => { setThumnail(userData?.profileImagePath) @@ -95,7 +97,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) @@ -111,7 +113,7 @@ const UserInfoForm = ({ userData, formType }: UserInfoFormProps) => { const handleCheckAuthNum = async (code: string) => { try { - const verification = await fetchPostEmailVerify({ + const verification = await postEmailVerify({ email: getValues('newsEmail'), code, }) 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/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..38a95644 --- /dev/null +++ b/src/services/email/useEmails.ts @@ -0,0 +1,26 @@ +import { postEmail, postEmailVerify } from '@/app/apis/email.api' +import { + EmailReqBody, + EmailVerifyReqBody, +} from '@/components/UserInfoForm/UserInfoForm' +import { IEmail } from '@/models/emails.model' +import { useMutation } from '@tanstack/react-query' + +export const usePostEmail = () => { + return useMutation({ + mutationFn: (email: IEmail) => postEmail({ email }), + onError: (error: Error) => { + console.log(error) + }, + }) +} + +export const usePostEmailVerify = () => { + return useMutation({ + mutationFn: (data: EmailVerifyReqBody & EmailReqBody) => + postEmailVerify({ data }), + onError: (error: Error) => { + console.log(error) + }, + }) +} From 6584e5cfba371c2658e3dc56007d423aba9ea3e1 Mon Sep 17 00:00:00 2001 From: Yeongjun Kim Date: Sat, 15 Mar 2025 02:41:30 +0900 Subject: [PATCH 09/30] =?UTF-8?q?chore:=20=EA=B0=9C=EB=B9=8C=20=ED=99=98?= =?UTF-8?q?=EA=B2=BD=EC=97=90=EC=84=9C=20pwa=EA=B0=80=20=EC=8B=A4=ED=96=89?= =?UTF-8?q?=EB=90=98=EC=A7=80=20=EC=95=8A=EB=8F=84=EB=A1=9D=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- next.config.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) 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: [ { From d18ed15c2d78b3645eef78dcefe82820dfd54742 Mon Sep 17 00:00:00 2001 From: Yeongjun Kim Date: Sat, 15 Mar 2025 12:17:58 +0900 Subject: [PATCH 10/30] =?UTF-8?q?feat:=20=EB=B9=84=EA=B3=B5=EA=B0=9C=20?= =?UTF-8?q?=EC=8A=A4=ED=8E=98=EC=9D=B4=EC=8A=A4=EA=B0=80=20=EC=A0=91?= =?UTF-8?q?=EC=86=8D=EB=90=98=EC=A7=80=20=EC=95=8A=EB=8A=94=20=EB=AC=B8?= =?UTF-8?q?=EC=A0=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/(routes)/space/[spaceId]/layout.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/app/(routes)/space/[spaceId]/layout.tsx b/src/app/(routes)/space/[spaceId]/layout.tsx index a1e20e1c..9c234844 100644 --- a/src/app/(routes)/space/[spaceId]/layout.tsx +++ b/src/app/(routes)/space/[spaceId]/layout.tsx @@ -1,14 +1,16 @@ -import { getSpace } from '@/app/apis/space.api' 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 getSpace({ spaceId }) + const space = await fetch(`${baseURL}/spaces/${spaceId}`, { + method: 'GET', + }).then((res) => res.json()) return { title: space.spaceName, From 10f7e23f9c0ed7470435bc713fbbbecfbfbe6e01 Mon Sep 17 00:00:00 2001 From: Yeongjun Kim Date: Sat, 15 Mar 2025 12:21:19 +0900 Subject: [PATCH 11/30] =?UTF-8?q?refactor:=20=EB=A9=94=ED=83=80=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=84=B0=20fetch=20=ED=95=A8=EC=88=98=EB=A5=BC=20TanS?= =?UTF-8?q?tack=20Query=EB=A1=9C=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{useGetMeta.ts => useGetMetaData.ts} | 21 ++++++++-------- src/services/meta/meta.ts | 19 --------------- src/services/meta/useMeta.ts | 24 +++++++++++++++++++ 3 files changed, 35 insertions(+), 29 deletions(-) rename src/components/common/LinkList/hooks/{useGetMeta.ts => useGetMetaData.ts} (79%) delete mode 100644 src/services/meta/meta.ts create mode 100644 src/services/meta/useMeta.ts 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/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..8a21c733 --- /dev/null +++ b/src/services/meta/useMeta.ts @@ -0,0 +1,24 @@ +import { useMutation } from '@tanstack/react-query' + +export const usePostMeta = () => { + return useMutation({ + mutationFn: async ({ url }: { url: string }) => { + const response = await fetch(`/api/meta`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ url }), + }) + + if (!response.ok) { + throw new Error('Failed to fetch meta data') + } + + return response.json() + }, + onError: (error: Error) => { + console.error('Meta fetch error:', error) + }, + }) +} From 6ceb235acdd451606d52e624ebe724540c70ed24 Mon Sep 17 00:00:00 2001 From: Yeongjun Kim Date: Sat, 15 Mar 2025 12:22:36 +0900 Subject: [PATCH 12/30] =?UTF-8?q?feat:=20=ED=94=84=EB=A1=9C=ED=95=84=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EC=8B=9C=20=EC=82=AC=EC=9D=B4=EB=93=9C?= =?UTF-8?q?=EB=B0=94=20=ED=94=84=EB=A1=9C=ED=95=84=20=EC=A0=95=EB=B3=B4?= =?UTF-8?q?=EB=8F=84=20=EB=B3=80=EA=B2=BD=EB=90=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/common/Sidebar/Sidebar.tsx | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) 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) => { 스페이스 전체보기 From 2e6880ba84fedc5b0781bd3d1f9733af47cdc6ae Mon Sep 17 00:00:00 2001 From: Yeongjun Kim Date: Sat, 15 Mar 2025 12:47:39 +0900 Subject: [PATCH 13/30] =?UTF-8?q?refactor:=20auth=20=EA=B4=80=EB=A0=A8=20f?= =?UTF-8?q?etch=20=ED=95=A8=EC=88=98=EB=A5=BC=20TanStack=20Query=EB=A1=9C?= =?UTF-8?q?=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../UserInfoForm/hooks/useRegister.ts | 5 +- .../common/Sidebar/hooks/useSidebar.ts | 4 +- src/constants/index.ts | 1 + src/hooks/useCurrentUser.ts | 5 +- src/services/auth/index.ts | 28 -------- src/services/auth/useAuth.ts | 67 +++++++++++++++++++ 6 files changed, 76 insertions(+), 34 deletions(-) delete mode 100644 src/services/auth/index.ts create mode 100644 src/services/auth/useAuth.ts 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/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/constants/index.ts b/src/constants/index.ts index 92821061..56b03052 100644 --- a/src/constants/index.ts +++ b/src/constants/index.ts @@ -60,4 +60,5 @@ export const QUERY_KEYS = { 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/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..b2ea43fa --- /dev/null +++ b/src/services/auth/useAuth.ts @@ -0,0 +1,67 @@ +import { RegisterReqBody } from '@/components/UserInfoForm/hooks/useRegister' +import { useMutation } from '@tanstack/react-query' +import Cookies from 'js-cookie' + +// 회원가입 +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 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 fetch(path, { + method: 'POST', + body: formData, + }) + + if (!response.ok) { + throw new Error('Failed to register user') + } + + return response.json() + }, + onError: (error: Error) => { + console.error('Registration error:', error) + }, + }) +} + +// 토큰 검증 +export const validateToken = async () => { + const response = await fetch('/api/auth', { + method: 'GET', + }) + return await response.json() +} + +// 카카오 로그아웃 +export const useKakaoLogout = () => { + return useMutation({ + mutationFn: async () => { + const response = await fetch('/api/logout', { + method: 'POST', + }) + + if (!response.ok) { + throw new Error('Logout failed') + } + + return response.json() + }, + onError: (error: Error) => { + console.error('Logout error:', error) + }, + }) +} From 03ea27ead6cc77d3fd7b007adf1ac0596d328b78 Mon Sep 17 00:00:00 2001 From: Yeongjun Kim Date: Sat, 15 Mar 2025 12:48:52 +0900 Subject: [PATCH 14/30] =?UTF-8?q?refactor:=20API=20=EB=9D=BC=EC=9A=B0?= =?UTF-8?q?=ED=84=B0=20=EA=B2=BD=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/api/{auth-user => auth}/route.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) rename src/app/api/{auth-user => auth}/route.ts (64%) diff --git a/src/app/api/auth-user/route.ts b/src/app/api/auth/route.ts similarity index 64% rename from src/app/api/auth-user/route.ts rename to src/app/api/auth/route.ts index 52608180..c80d1ec5 100644 --- a/src/app/api/auth-user/route.ts +++ b/src/app/api/auth/route.ts @@ -1,17 +1,21 @@ import { useServerCookie } from '@/hooks/useServerCookie' -import { apiServer } from '@/services/apiServices' import { NextResponse } from 'next/server' +const baseURL = process.env.NEXT_PUBLIC_API_ADDRESS + export async function GET(_request: Request) { const { token } = useServerCookie() + const path = `${baseURL}/members/profile` + try { - const response = await apiServer.get(`/members/profile`, { + const response = await fetch(path, { + method: 'GET', headers: { Authorization: 'Bearer ' + token, }, }) - - return NextResponse.json(response) + const data = await response.json() + return NextResponse.json(data) } catch (error: any) { return NextResponse.json( { error: error.response.data.message }, From 7c852c72c9d994fd9bb1a2157b10fc8e672a081c Mon Sep 17 00:00:00 2001 From: Yeongjun Kim Date: Sat, 15 Mar 2025 13:04:37 +0900 Subject: [PATCH 15/30] =?UTF-8?q?feat:=20API=20=EC=9A=94=EC=B2=AD=20?= =?UTF-8?q?=EA=B2=BD=EB=A1=9C=EB=A5=BC=20Next.js=20API=20=EB=9D=BC?= =?UTF-8?q?=EC=9A=B0=ED=8A=B8=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/services/comments/useComments.ts | 89 +++++++++++---- src/services/email/useEmails.ts | 20 +++- src/services/link/useLink.ts | 72 ++++++++---- src/services/notification/useNotification.ts | 20 ++-- src/services/space/useSpace.ts | 108 +++++++++++------- src/services/space/useSpaces.ts | 59 +++++++--- .../useMember.ts => users/useUsers.ts} | 61 +++++++--- 7 files changed, 296 insertions(+), 133 deletions(-) rename src/services/{members/useMember.ts => users/useUsers.ts} (71%) diff --git a/src/services/comments/useComments.ts b/src/services/comments/useComments.ts index 2d39acd1..108da8f3 100644 --- a/src/services/comments/useComments.ts +++ b/src/services/comments/useComments.ts @@ -1,11 +1,3 @@ -import { - createComment, - createReply, - deleteComment, - getComments, - getReplies, - updateComment, -} from '@/app/apis/comments.api' import { QUERY_KEYS } from '@/constants' import { ICommentQuery } from '@/models/comments.model' import { useMutation, useQueryClient } from '@tanstack/react-query' @@ -23,8 +15,14 @@ export const fetchGetComments = async ({ const queryString = new URLSearchParams(params).toString() try { - const response = await getComments({ spaceId, searchParams: queryString }) - return response + const response = await fetch( + `/api/space/${spaceId}/comments?${queryString}`, + { + method: 'GET', + }, + ) + const data = await response.json() + return data } catch (e) { if (e instanceof Error) throw new Error(e.message) } @@ -34,8 +32,20 @@ export const fetchGetComments = async ({ export const usePostComment = (spaceId: number) => { const queryClient = useQueryClient() return useMutation({ - mutationFn: ({ spaceId, content }: { spaceId: number; content: string }) => - createComment({ spaceId, content }), + mutationFn: async ({ + spaceId, + content, + }: { + spaceId: number + content: string + }) => { + const response = await fetch(`/api/space/${spaceId}/comments/create`, { + method: 'POST', + body: JSON.stringify({ content }), + }) + const data = await response.json() + return data + }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.COMMENTS, spaceId], @@ -51,7 +61,7 @@ export const usePostComment = (spaceId: number) => { export const usePutComment = (spaceId?: number, parentCommentId?: number) => { const queryClient = useQueryClient() return useMutation({ - mutationFn: ({ + mutationFn: async ({ spaceId, commentId, content, @@ -59,7 +69,17 @@ export const usePutComment = (spaceId?: number, parentCommentId?: number) => { spaceId: number commentId: number content: string - }) => updateComment({ spaceId, commentId, content }), + }) => { + const response = await fetch( + `/api/space/${spaceId}/comments/${commentId}`, + { + method: 'PUT', + body: JSON.stringify({ content }), + }, + ) + const data = await response.json() + return data + }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.COMMENTS, spaceId], @@ -78,8 +98,16 @@ export const usePutComment = (spaceId?: number, parentCommentId?: number) => { export const useDeleteComment = (spaceId: number, parentCommentId?: number) => { const queryClient = useQueryClient() return useMutation({ - mutationFn: ({ commentId }: { commentId: number }) => - deleteComment({ spaceId, commentId }), + mutationFn: async ({ commentId }: { commentId: number }) => { + const response = await fetch( + `/api/space/${spaceId}/comments/${commentId}`, + { + method: 'DELETE', + }, + ) + const data = await response.json() + return data + }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.COMMENTS, spaceId], @@ -108,12 +136,14 @@ export const fetchGetReplies = async ({ const queryString = new URLSearchParams(params).toString() try { - const response = await getReplies({ - spaceId, - commentId, - searchParams: queryString, - }) - return response + const response = await fetch( + `/api/space/${spaceId}/comments/${commentId}/replies?${queryString}`, + { + method: 'GET', + }, + ) + const data = await response.json() + return data } catch (e) { if (e instanceof Error) throw new Error(e.message) } @@ -121,10 +151,9 @@ export const fetchGetReplies = async ({ // 대댓글 생성 export const usePostReply = (spaceId: number, parentCommentId?: number) => { - console.log(parentCommentId) const queryClient = useQueryClient() return useMutation({ - mutationFn: ({ + mutationFn: async ({ spaceId, commentId, content, @@ -132,7 +161,17 @@ export const usePostReply = (spaceId: number, parentCommentId?: number) => { spaceId: number commentId: number content: string - }) => createReply({ spaceId, commentId, content }), + }) => { + const response = await fetch( + `/api/space/${spaceId}/comments/${commentId}/replies`, + { + method: 'POST', + body: JSON.stringify({ content }), + }, + ) + const data = await response.json() + return data + }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.COMMENTS, spaceId], diff --git a/src/services/email/useEmails.ts b/src/services/email/useEmails.ts index 38a95644..84dc127c 100644 --- a/src/services/email/useEmails.ts +++ b/src/services/email/useEmails.ts @@ -1,4 +1,3 @@ -import { postEmail, postEmailVerify } from '@/app/apis/email.api' import { EmailReqBody, EmailVerifyReqBody, @@ -6,19 +5,32 @@ import { import { IEmail } from '@/models/emails.model' import { useMutation } from '@tanstack/react-query' +// 이메일 전송 export const usePostEmail = () => { return useMutation({ - mutationFn: (email: IEmail) => postEmail({ email }), + mutationFn: async (email: IEmail) => { + const response = await fetch(`/api/email`, { + method: 'POST', + body: JSON.stringify(email), + }) + return response.json() + }, onError: (error: Error) => { console.log(error) }, }) } +// 이메일 인증 export const usePostEmailVerify = () => { return useMutation({ - mutationFn: (data: EmailVerifyReqBody & EmailReqBody) => - postEmailVerify({ data }), + mutationFn: async (data: EmailVerifyReqBody & EmailReqBody) => { + const response = await fetch(`/api/email/verify`, { + method: 'POST', + body: JSON.stringify(data), + }) + return response.json() + }, onError: (error: Error) => { console.log(error) }, diff --git a/src/services/link/useLink.ts b/src/services/link/useLink.ts index c1e0735e..4174ae23 100644 --- a/src/services/link/useLink.ts +++ b/src/services/link/useLink.ts @@ -1,13 +1,3 @@ -import { - createLink, - deleteLikeLink, - deleteLink, - getLinks, - getPopularLinks, - postLikeLink, - postReadSaveLink, - updateLink, -} from '@/app/apis/link.api' import { QUERY_KEYS } from '@/constants' import { ILikeLink, ISpaceLink, IUpdateLink } from '@/models/link.model' import { GetLinksReqBody } from '@/types' @@ -30,8 +20,13 @@ export const fetchGetLinks = async ({ const queryString = new URLSearchParams(params).toString() try { - const response = await getLinks({ spaceId, searchParams: queryString }) - return response + const response = await fetch(`/api/space/${spaceId}/links?${queryString}`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + }) + return response.json() } catch (e) { if (e instanceof Error) throw new Error(e.message) } @@ -41,7 +36,14 @@ export const fetchGetLinks = async ({ export const usePostLink = (spaceId?: number) => { const queryClient = useQueryClient() return useMutation({ - mutationFn: (query: IUpdateLink['query']) => createLink({ query }), + mutationFn: (query: IUpdateLink['query']) => + fetch(`/api/space/${spaceId}/links`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(query), + }), onSuccess: () => { queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.LINKS, spaceId] }) queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.TAGS, spaceId] }) @@ -51,12 +53,18 @@ export const usePostLink = (spaceId?: number) => { }, }) } - // 링크 수정 export const usePutLink = (spaceId?: number) => { const queryClient = useQueryClient() return useMutation({ - mutationFn: (query: IUpdateLink['query']) => updateLink({ query }), + mutationFn: (query: IUpdateLink['query']) => + fetch(`/api/space/${spaceId}/links`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(query), + }).then((res) => res.json()), onSuccess: () => { queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.LINKS, spaceId] }) queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.TAGS, spaceId] }) @@ -71,7 +79,11 @@ export const usePutLink = (spaceId?: number) => { export const useDeleteLink = (spaceId?: number) => { const queryClient = useQueryClient() return useMutation({ - mutationFn: (query: ISpaceLink['query']) => deleteLink({ query }), + mutationFn: (query: ISpaceLink['query']) => { + return fetch(`/api/space/${query.spaceId}/links/${query.linkId}`, { + method: 'DELETE', + }).then((res) => res.json()) + }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.LINKS, spaceId] }) queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.TAGS, spaceId] }) @@ -86,14 +98,24 @@ export const useDeleteLink = (spaceId?: number) => { export const useGetPopularLinks = () => { return useQuery({ queryKey: [QUERY_KEYS.POPULAR_LINKS], - queryFn: () => getPopularLinks(), + queryFn: () => + fetch('/api/links', { + method: 'GET', + }).then((res) => res.json()), }) } // 링크 좋아요 export const usePostLikeLink = () => { return useMutation({ - mutationFn: (query: ILikeLink['query']) => postLikeLink({ query }), + mutationFn: (query: ILikeLink['query']) => + fetch(`/api/links/${query.linkId}/like`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(query), + }).then((res) => res.json()), onError: (error: Error) => { console.log(error) }, @@ -103,7 +125,10 @@ export const usePostLikeLink = () => { // 링크 좋아요 취소 export const useDeleteLikeLink = () => { return useMutation({ - mutationFn: (query: ILikeLink['query']) => deleteLikeLink({ query }), + mutationFn: (query: ILikeLink['query']) => + fetch(`/api/links/${query.linkId}/like`, { + method: 'DELETE', + }).then((res) => res.json()), onError: (error: Error) => { console.log(error) }, @@ -114,7 +139,14 @@ export const useDeleteLikeLink = () => { export const usePostReadSaveLink = () => { const queryClient = useQueryClient() return useMutation({ - mutationFn: (query: ISpaceLink['query']) => postReadSaveLink({ query }), + mutationFn: (query: ISpaceLink['query']) => + fetch(`/api/space/${query.spaceId}/links/readInfo/${query.linkId}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(query), + }).then((res) => res.json()), onSuccess: () => { queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.LINKS] }) }, diff --git a/src/services/notification/useNotification.ts b/src/services/notification/useNotification.ts index ef916b9e..01d5884a 100644 --- a/src/services/notification/useNotification.ts +++ b/src/services/notification/useNotification.ts @@ -1,8 +1,3 @@ -import { - deleteNotification, - getInvitations, - getNotificationCount, -} from '@/app/apis/notification.api' import { QUERY_KEYS } from '@/constants' import { IInvitationsQuery } from '@/models/notification.model' import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' @@ -11,7 +6,10 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' export const useGetUnCheckedNotifications = () => { return useQuery({ queryKey: [QUERY_KEYS.NOTIFICATION_COUNT], - queryFn: () => getNotificationCount(), + queryFn: async () => { + const response = await fetch(`/api/notification/unchecked`) + return response.json() + }, }) } @@ -27,8 +25,8 @@ export const fetchGetInvitations = async ({ const queryString = new URLSearchParams(params).toString() try { - const response = await getInvitations({ searchParams: queryString }) - return response + const response = await fetch(`/api/notification/invitations?${queryString}`) + return response.json() } catch (e) { if (e instanceof Error) throw new Error(e.message) } @@ -38,7 +36,11 @@ export const fetchGetInvitations = async ({ export const useDeleteNotifications = () => { const queryClient = useQueryClient() return useMutation({ - mutationFn: (notificationId: number) => deleteNotification(notificationId), + mutationFn: (notificationId: number) => { + return fetch(`/api/notification/${notificationId}`, { + method: 'DELETE', + }) + }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.INVITATIONS] }) queryClient.invalidateQueries({ diff --git a/src/services/space/useSpace.ts b/src/services/space/useSpace.ts index a86be0ee..c7f1609a 100644 --- a/src/services/space/useSpace.ts +++ b/src/services/space/useSpace.ts @@ -1,14 +1,3 @@ -import { - createSpace, - deleteFavoriteSpace, - deleteSpace, - getSpace, - getTags, - patchRole, - patchSpace, - postFavoriteSpace, - postScrapSpace, -} from '@/app/apis/space.api' import { QUERY_KEYS } from '@/constants' import { IChangeRole, @@ -17,13 +6,7 @@ import { IUpdateSpace, } from '@/models/space.model' import { Tag } from '@/types' -import { - UseMutationResult, - useMutation, - useQuery, - useQueryClient, -} from '@tanstack/react-query' -import Cookies from 'js-cookie' +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' export interface FetchGetSpaceProps { spaceId?: number @@ -31,26 +14,36 @@ export interface FetchGetSpaceProps { // 스페이스 상세 조회 export const useGetSpace = (spaceId?: number) => { - const token = Cookies.get('Auth-token') return useQuery({ queryKey: [QUERY_KEYS.SPACES, spaceId], - queryFn: () => getSpace({ spaceId }), - enabled: !!spaceId && !!token, + queryFn: async () => + await fetch(`/api/space/${spaceId}`, { + method: 'GET', + }).then((res) => res.json()), + enabled: !!spaceId, }) } // 스페이스 생성 -export const usePostSpace = (): UseMutationResult< - { spaceId: number }, - Error, - ICreateSpace -> => { +export const usePostSpace = () => { const queryClient = useQueryClient() - return useMutation({ - mutationFn: ({ data, file }: ICreateSpace) => { - const response = createSpace({ data, file }) - return response + 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 fetch('/api/spaces/create', { + method: 'POST', + body: formData, + }) + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`) + } + + return response.json() }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.SPACES] }) @@ -64,11 +57,23 @@ export const usePostSpace = (): UseMutationResult< // 스페이스 수정 export const usePatchSpace = (spaceId?: number) => { const queryClient = useQueryClient() - return useMutation({ mutationFn: async ({ data, file }: IUpdateSpace) => { - const response = patchSpace({ spaceId, data, file }) - return response + const reqData = { ...data } + const formData = new FormData() + formData.append('request', JSON.stringify(reqData)) + file && formData.append('file', file) + + const response = await fetch(`/api/space/${spaceId}`, { + method: 'PATCH', + body: formData, + }) + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`) + } + + return response.json() }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.SPACES] }) @@ -84,7 +89,10 @@ export const usePatchSpace = (spaceId?: number) => { export const useDeleteSpace = () => { const queryClient = useQueryClient() return useMutation({ - mutationFn: (query: ISpaceQuery['query']) => deleteSpace({ query }), + mutationFn: async (query: ISpaceQuery['query']) => + await fetch(`/api/space/${query.spaceId}`, { + method: 'DELETE', + }).then((res) => res.json()), onSuccess: () => { queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.SPACES] }) }, @@ -98,7 +106,10 @@ export const useDeleteSpace = () => { export const usePostFavoriteSpace = () => { const queryClient = useQueryClient() return useMutation({ - mutationFn: (query: ISpaceQuery['query']) => postFavoriteSpace({ query }), + mutationFn: (query: ISpaceQuery['query']) => + fetch(`/api/space/${query.spaceId}/favorites`, { + method: 'POST', + }).then((res) => res.json()), onSuccess: () => { queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.SPACES] }) }, @@ -112,7 +123,10 @@ export const usePostFavoriteSpace = () => { export const useDeleteFavoriteSpace = () => { const queryClient = useQueryClient() return useMutation({ - mutationFn: (query: ISpaceQuery['query']) => deleteFavoriteSpace({ query }), + mutationFn: (query: ISpaceQuery['query']) => + fetch(`/api/space/${query.spaceId}/favorites`, { + method: 'DELETE', + }).then((res) => res.json()), onSuccess: () => { queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.SPACES] }) }, @@ -127,7 +141,9 @@ export const useGetTags = ({ spaceId }: ISpaceQuery['query']) => { return useQuery({ queryKey: [QUERY_KEYS.TAGS, spaceId], queryFn: async () => { - const response = await getTags({ spaceId }) + const response = await fetch(`/api/space/${spaceId}/tags`, { + method: 'GET', + }).then((res) => res.json()) return response.tags }, enabled: !!spaceId, @@ -138,7 +154,11 @@ export const useGetTags = ({ spaceId }: ISpaceQuery['query']) => { export const usePatchRole = (spaceId?: number) => { const queryClient = useQueryClient() return useMutation({ - mutationFn: (query: IChangeRole['query']) => patchRole({ query }), + mutationFn: (query: IChangeRole['query']) => + fetch(`/api/space/${spaceId}/member/role`, { + method: 'PATCH', + body: JSON.stringify(query), + }).then((res) => res.json()), onSuccess: () => { queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.SPACES, spaceId] }) }, @@ -154,8 +174,16 @@ export const usePostScrapSpace = (spaceId?: number) => { return useMutation({ mutationFn: async ({ data, file }: IUpdateSpace) => { - const response = postScrapSpace({ spaceId, data, file }) - return response + const reqData = { ...data } + const formData = new FormData() + formData.append('request', JSON.stringify(reqData)) + file && formData.append('file', file) + + const response = await fetch(`/api/space/${spaceId}/scrap/new`, { + method: 'POST', + body: formData, + }) + return await response.json() }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.SPACES] }) diff --git a/src/services/space/useSpaces.ts b/src/services/space/useSpaces.ts index 283c5187..342133f8 100644 --- a/src/services/space/useSpaces.ts +++ b/src/services/space/useSpaces.ts @@ -38,8 +38,8 @@ export const fetchGetSpaces = async ({ const queryString = new URLSearchParams(params).toString() try { - const response = await getSpaces({ searchParams: queryString }) - return response + const response = await fetch(`/api/spaces?${queryString}`) + return response.json() } catch (e) { if (e instanceof Error) throw new Error(e.message) } @@ -63,8 +63,8 @@ export const fetchSearchSpaces = async ({ const queryString = new URLSearchParams(params).toString() try { - const response = await getSearchSpaces({ searchParams: queryString }) - return response + const response = await fetch(`/api/spaces/search?${queryString}`) + return response.json() } catch (e) { if (e instanceof Error) throw new Error(e.message) } @@ -87,11 +87,8 @@ export const fetchSearchMySpaces = async ({ const queryString = new URLSearchParams(params).toString() try { - const response = await getSearchMySpaces({ - memberId, - searchParams: queryString, - }) - return response + const response = await fetch(`/api/user/${memberId}/spaces?${queryString}`) + return response.json() } catch (e) { if (e instanceof Error) throw new Error(e.message) } @@ -113,10 +110,8 @@ export const fetchGetMyFavoriteSpaces = async ({ const queryString = new URLSearchParams(params).toString() try { - const response = await getSearchMyFavoriteSpaces({ - searchParams: queryString, - }) - return response + const response = await fetch(`/api/user/favorites?${queryString}`) + return response.json() } catch (e) { if (e instanceof Error) throw new Error(e.message) } @@ -130,7 +125,21 @@ export const usePostInviteSpace = (): UseMutationResult< > => { const queryClient = useQueryClient() return useMutation({ - mutationFn: (query: IInviteSpace['query']) => postInviteSpace(query), + mutationFn: async (query: IInviteSpace['query']) => { + const response = await fetch(`/api/space/invitations`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(query), + }) + + if (!response.ok) { + throw new Error('Failed to invite to space') + } + + return response.json() + }, onSuccess: (data) => { queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.SPACES, data.spaceId], @@ -150,11 +159,27 @@ export const usePostAccetpSpaceInvitation = (): UseMutationResult< > => { const queryClient = useQueryClient() return useMutation({ - mutationFn: (query: IAcceptSpaceInvitation['query']) => - getAccetpSpaceInvitation(query), + mutationFn: async (query: IAcceptSpaceInvitation['query']) => { + const response = await fetch(`/api/spaces/invitations`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(query), + }) + console.log(response) + if (!response.ok) { + throw new Error('Failed to accept space invitation') + } + + return response.json() + }, onSuccess: (data) => { queryClient.invalidateQueries({ - queryKey: [QUERY_KEYS.SPACES, data.spaceId], + queryKey: [QUERY_KEYS.NOTIFICATION_COUNT], + }) + queryClient.invalidateQueries({ + queryKey: [QUERY_KEYS.INVITATIONS], }) }, onError: (error: Error) => { diff --git a/src/services/members/useMember.ts b/src/services/users/useUsers.ts similarity index 71% rename from src/services/members/useMember.ts rename to src/services/users/useUsers.ts index 1e2ceeb6..c9644810 100644 --- a/src/services/members/useMember.ts +++ b/src/services/users/useUsers.ts @@ -9,23 +9,27 @@ import { } from '@/app/apis/member.api' import { QUERY_KEYS } from '@/constants' import { IFollow, IFollowList, IMemberSearch } from '@/models/member.model' -import { RegisterReqBody, SearchUserReqBody } from '@/types' +import { RegisterReqBody } from '@/types' import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' // 멤버 프로필 조회 -export const useGetMemberProfile = (memberId: number) => { +export const useGetUserProfile = (memberId: number) => { return useQuery({ queryKey: [QUERY_KEYS.MEMBERS, memberId], - queryFn: () => getMemberProfile({ memberId }), + queryFn: async () => { + const response = await fetch(`/api/user/${memberId}/profile`) + const data = await response.json() + return data + }, enabled: !!memberId, }) } // 멤버 프로필 수정 -export const usePutMemberProfile = (memberId: number) => { +export const usePutUserProfile = (memberId: number) => { const queryClient = useQueryClient() return useMutation({ - mutationFn: ({ + mutationFn: async ({ memberId, data, file, @@ -34,8 +38,22 @@ export const usePutMemberProfile = (memberId: number) => { data: RegisterReqBody file?: File }) => { - const response = putMemberProfile({ memberId, data, file }) - return response + const formData = new FormData() + formData.append('request', JSON.stringify(data)) + if (file) { + formData.append('file', file) + } + + const response = await fetch(`/api/user/${memberId}/profile`, { + method: 'PUT', + body: formData, + }) + + if (!response.ok) { + throw new Error('Failed to update profile') + } + + return await response.json() }, onSuccess: () => { queryClient.invalidateQueries({ @@ -61,8 +79,10 @@ export const fetchGetFollowing = async ({ const queryString = new URLSearchParams(params).toString() try { - const response = await getFollowing({ memberId, searchParams: queryString }) - return response + const response = await fetch( + `/api/user/${memberId}/following?${queryString}`, + ) + return response.json() } catch (e) { if (e instanceof Error) throw new Error(e.message) } @@ -81,8 +101,10 @@ export const fetchGetFollowers = async ({ const queryString = new URLSearchParams(params).toString() try { - const response = await getFollowers({ memberId, searchParams: queryString }) - return response + const response = await fetch( + `/api/user/${memberId}/followers?${queryString}`, + ) + return response.json() } catch (e) { if (e instanceof Error) throw new Error(e.message) } @@ -94,7 +116,9 @@ export const usePostFollow = (profileId?: number) => { return useMutation({ mutationFn: ({ memberId }: IFollow) => { - const response = postFollow({ memberId }) + const response = fetch(`/api/user/${memberId}/follow`, { + method: 'POST', + }) return response }, onSuccess: () => { @@ -120,7 +144,9 @@ export const useDeleteFollow = (profileId?: number) => { return useMutation({ mutationFn: ({ memberId }: IFollow) => { - const response = deleteFollow({ memberId }) + const response = fetch(`/api/user/${memberId}/follow`, { + method: 'DELETE', + }) return response }, onSuccess: () => { @@ -141,7 +167,7 @@ export const useDeleteFollow = (profileId?: number) => { } // 멤버 검색 (무한스크롤 fetch 함수) -export const fetchSearchMembers = async ({ +export const fetchSearchUsers = async ({ pageNumber, pageSize, keyword, @@ -154,10 +180,9 @@ export const fetchSearchMembers = async ({ const queryString = new URLSearchParams(params).toString() try { - const response = await getSearchMembers({ - searchParams: queryString, - }) - return response + const response = await fetch(`/api/user/search?${queryString}`) + const data = await response.json() + return data } catch (e) { if (e instanceof Error) throw new Error(e.message) } From 74d0b495191ced5d01f2718c02265ecc4f4175e6 Mon Sep 17 00:00:00 2001 From: Yeongjun Kim Date: Sat, 15 Mar 2025 13:05:12 +0900 Subject: [PATCH 16/30] =?UTF-8?q?feat:=20=EC=98=B5=EC=85=94=EB=84=90=20?= =?UTF-8?q?=EC=B2=B4=EC=9D=B4=EB=8B=9D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/models/space.model.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/models/space.model.ts b/src/models/space.model.ts index 93d1a9ca..4f0006ea 100644 --- a/src/models/space.model.ts +++ b/src/models/space.model.ts @@ -51,7 +51,7 @@ export interface IUpdateLink { export interface IChangeRole { query: { - spaceId: number + spaceId?: number targetMemberId: number role: string } From e67c78f2489457a7bf6850d51f884056649aba73 Mon Sep 17 00:00:00 2001 From: Yeongjun Kim Date: Sat, 15 Mar 2025 13:07:36 +0900 Subject: [PATCH 17/30] =?UTF-8?q?refactor:=20members=20=EB=84=A4=EC=9D=B4?= =?UTF-8?q?=EB=B0=8D=EC=9D=84=20users=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/(routes)/user/[userId]/page.tsx | 4 ++-- src/components/FollowListButton/FollowListButton.tsx | 5 +---- src/components/SearchController/SearchController.tsx | 4 ++-- src/components/SettingController/SettingController.tsx | 4 ++-- src/components/UserInfoForm/UserInfoForm.tsx | 6 +++--- src/hooks/useFollowUser.ts | 2 +- src/hooks/useGetProfile.ts | 4 ++-- 7 files changed, 13 insertions(+), 16 deletions(-) diff --git a/src/app/(routes)/user/[userId]/page.tsx b/src/app/(routes)/user/[userId]/page.tsx index 483a8e93..5759c09a 100644 --- a/src/app/(routes)/user/[userId]/page.tsx +++ b/src/app/(routes)/user/[userId]/page.tsx @@ -7,11 +7,11 @@ import { ProfileEditButton, } from '@/components' import { CATEGORIES_RENDER, PROFILE_MSG } from '@/constants' -import { useGetMemberProfile } from '@/services/members/useMember' +import { useGetUserProfile } from '@/services/users/useUsers' import { UserLayoutProps } from './layout' export default function UserPage({ params: { userId } }: UserLayoutProps) { - const { data: user } = useGetMemberProfile(Number(userId)) + const { data: user } = useGetUserProfile(Number(userId)) return ( <> diff --git a/src/components/FollowListButton/FollowListButton.tsx b/src/components/FollowListButton/FollowListButton.tsx index 3eb1b475..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/members/useMember' +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/SearchController/SearchController.tsx b/src/components/SearchController/SearchController.tsx index 80464375..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 { fetchSearchMembers } from '@/services/members/useMember' import { fetchSearchSpaces } from '@/services/space/useSpaces' +import { fetchSearchUsers } from '@/services/users/useUsers' import { cls } from '@/utils' import { useSearchParams } from 'next/navigation' @@ -61,7 +61,7 @@ const SearchController = () => { {target === 'user' && ( )} diff --git a/src/components/SettingController/SettingController.tsx b/src/components/SettingController/SettingController.tsx index 9046df7b..f33273a7 100644 --- a/src/components/SettingController/SettingController.tsx +++ b/src/components/SettingController/SettingController.tsx @@ -2,11 +2,11 @@ import UserInfoForm from '@/components/UserInfoForm/UserInfoForm' import { useCurrentUser } from '@/hooks/useCurrentUser' -import { useGetMemberProfile } from '@/services/members/useMember' +import { useGetUserProfile } from '@/services/users/useUsers' const SettingController = () => { const { currentUser } = useCurrentUser() - const { data: user } = useGetMemberProfile(currentUser?.memberId || 0) + const { data: user } = useGetUserProfile(currentUser?.memberId || 0) return (
diff --git a/src/components/UserInfoForm/UserInfoForm.tsx b/src/components/UserInfoForm/UserInfoForm.tsx index 77ff3cdd..606eb96d 100644 --- a/src/components/UserInfoForm/UserInfoForm.tsx +++ b/src/components/UserInfoForm/UserInfoForm.tsx @@ -4,7 +4,7 @@ import { ChangeEvent, useEffect, useRef, useState } from 'react' import { useForm } from 'react-hook-form' import { Avatar, CategoryList, Input } from '@/components' import { usePostEmail, usePostEmailVerify } from '@/services/email/useEmails' -import { usePutMemberProfile } from '@/services/members/useMember' +import { usePutUserProfile } from '@/services/users/useUsers' import { UserProfileResBody } from '@/types' import { cls } from '@/utils' import { CheckIcon } from '@heroicons/react/24/solid' @@ -39,7 +39,7 @@ const UserInfoForm = ({ userData, formType }: UserInfoFormProps) => { const [imageFile, setImageFile] = useState() const router = useRouter() const [isVerification, setVerification] = useState(false) - const { mutate: putMemberProfileMutation } = usePutMemberProfile( + const { mutate: putUserProfileMutation } = usePutUserProfile( userData?.memberId || 0, ) const { mutateAsync: postEmail } = usePostEmail() @@ -156,7 +156,7 @@ const UserInfoForm = ({ userData, formType }: UserInfoFormProps) => { const handleSettingUser = (data: RegisterReqBody & EmailVerifyReqBody) => { try { userData?.memberId && - putMemberProfileMutation({ + putUserProfileMutation({ memberId: userData?.memberId, data, file: imageFile, diff --git a/src/hooks/useFollowUser.ts b/src/hooks/useFollowUser.ts index f57a770b..94acef6d 100644 --- a/src/hooks/useFollowUser.ts +++ b/src/hooks/useFollowUser.ts @@ -1,5 +1,5 @@ import { useCallback, useEffect, useMemo, useState } from 'react' -import { useDeleteFollow, usePostFollow } from '@/services/members/useMember' +import { useDeleteFollow, usePostFollow } from '@/services/users/useUsers' import { useQueryClient } from '@tanstack/react-query' import { debounce } from 'lodash' import { useCurrentUser } from './useCurrentUser' diff --git a/src/hooks/useGetProfile.ts b/src/hooks/useGetProfile.ts index e6d0b13e..b8dd9203 100644 --- a/src/hooks/useGetProfile.ts +++ b/src/hooks/useGetProfile.ts @@ -1,5 +1,5 @@ import { useState } from 'react' -import { useGetMemberProfile } from '@/services/members/useMember' +import { useGetUserProfile } from '@/services/users/useUsers' import { usePathname } from 'next/navigation' import { useCurrentUser } from './useCurrentUser' @@ -9,7 +9,7 @@ const useGetProfile = () => { const userId = Number(path.split('/')[2]) const { currentUser } = useCurrentUser() const myId = currentUser?.memberId - const { data: userData } = useGetMemberProfile(userId) + const { data: userData } = useGetUserProfile(userId) return { user: userData, myId, isProfileLoading: isLoading } } From 1cce1c6809e3ad59b5188469b3bf92afe4bc1db5 Mon Sep 17 00:00:00 2001 From: Yeongjun Kim Date: Sat, 15 Mar 2025 13:09:20 +0900 Subject: [PATCH 18/30] =?UTF-8?q?chore:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=EC=BD=94=EB=93=9C=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/SpaceList/hooks/useMainSpacesQuery.ts | 5 +---- src/components/common/FollowList/FollowList.tsx | 1 - src/components/common/MainSpaceList/HydrateMainSpaceList.tsx | 2 -- src/components/common/SpaceMemberList/SpaceMemberList.tsx | 2 +- src/hooks/useSpaceComment.ts | 2 +- src/services/index.ts | 0 6 files changed, 3 insertions(+), 9 deletions(-) delete mode 100644 src/services/index.ts diff --git a/src/components/SpaceList/hooks/useMainSpacesQuery.ts b/src/components/SpaceList/hooks/useMainSpacesQuery.ts index c1649cab..5d413bda 100644 --- a/src/components/SpaceList/hooks/useMainSpacesQuery.ts +++ b/src/components/SpaceList/hooks/useMainSpacesQuery.ts @@ -1,9 +1,6 @@ import { PAGE_SIZE } from '@/constants' import { SpaceResBody } from '@/types' -import { - useInfiniteQuery, - useSuspenseInfiniteQuery, -} from '@tanstack/react-query' +import { useInfiniteQuery } from '@tanstack/react-query' import { SpaceListProps } from '../SpaceList' interface MainSpacePageType { diff --git a/src/components/common/FollowList/FollowList.tsx b/src/components/common/FollowList/FollowList.tsx index f947386b..5fcb9cb7 100644 --- a/src/components/common/FollowList/FollowList.tsx +++ b/src/components/common/FollowList/FollowList.tsx @@ -31,7 +31,6 @@ const FollowList = ({ followingCount, setFollowingCount, }: FollowListProps) => { - console.log(memberId) const { followList, fetchNextPage, hasNextPage, isFollowLoading } = useFollowQuery({ memberId, diff --git a/src/components/common/MainSpaceList/HydrateMainSpaceList.tsx b/src/components/common/MainSpaceList/HydrateMainSpaceList.tsx index 8e76fc6d..1eb9fc17 100644 --- a/src/components/common/MainSpaceList/HydrateMainSpaceList.tsx +++ b/src/components/common/MainSpaceList/HydrateMainSpaceList.tsx @@ -19,8 +19,6 @@ const HydrateMainSpaceList = () => { initialPageParam: 0, }) - const dehydreatedState = dehydrate(queryClient) - return ( diff --git a/src/components/common/SpaceMemberList/SpaceMemberList.tsx b/src/components/common/SpaceMemberList/SpaceMemberList.tsx index c89d914a..d9c6b1ea 100644 --- a/src/components/common/SpaceMemberList/SpaceMemberList.tsx +++ b/src/components/common/SpaceMemberList/SpaceMemberList.tsx @@ -54,7 +54,7 @@ const SpaceMemberList = ({ const { mutateAsync: inviteSpace } = usePostInviteSpace() const handleChangeRole = async (data: ChangeRoleProps) => { try { - spaceId && patchRole({ spaceId, ...data }) + spaceId && patchRole({ ...data }) alert('권한을 수정했습니다.') } catch (e) { alert('권한 수정에 실패했습니다.') diff --git a/src/hooks/useSpaceComment.ts b/src/hooks/useSpaceComment.ts index c640a784..b70802a1 100644 --- a/src/hooks/useSpaceComment.ts +++ b/src/hooks/useSpaceComment.ts @@ -54,7 +54,7 @@ const useSpaceComment = ({ spaceId, comment.parentCommentId, ) - console.log(comment.parentCommentId) + const handleOpen = useCallback( (commentId: number) => { if (openedComments.includes(commentId)) { diff --git a/src/services/index.ts b/src/services/index.ts deleted file mode 100644 index e69de29b..00000000 From 3f10b35a7bd58beaaf88b018153541990f1e0b79 Mon Sep 17 00:00:00 2001 From: Yeongjun Kim Date: Sat, 15 Mar 2025 13:10:14 +0900 Subject: [PATCH 19/30] =?UTF-8?q?refactor:=20meta=20=EB=84=A4=EC=9D=B4?= =?UTF-8?q?=EB=B0=8D=EC=9D=84=20metaData=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/common/LinkItem/LinkItem.tsx | 10 ++++++---- src/components/common/LinkList/LinkList.tsx | 6 +++--- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/components/common/LinkItem/LinkItem.tsx b/src/components/common/LinkItem/LinkItem.tsx index 9df5b976..0662fb2f 100644 --- a/src/components/common/LinkItem/LinkItem.tsx +++ b/src/components/common/LinkItem/LinkItem.tsx @@ -26,7 +26,7 @@ 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 { Tag } from '../Space/hooks/useGetTags' @@ -100,7 +100,7 @@ const LinkItem = ({ handleModalClose, handleChangeUrl, handleGetMeta, - } = useGetMeta({ getValues, setValue, modalClose }) + } = useGetMetaData({ getValues, setValue, modalClose }) const { isUpdateLinkLoading, handleUpdateLink } = useUpdateLink({ spaceId, linkId, @@ -114,7 +114,6 @@ const LinkItem = ({ isLikedValue: isInitLiked, likeCountValue: likeInitCount, }) - return ( <> {type === 'list' ? ( @@ -308,7 +307,10 @@ const LinkItem = ({ } })() })() - : spaceId && deleteLink({ spaceId, linkId }) + : spaceId && + (() => { + deleteLink({ spaceId, linkId }) + })() } type="form"> {currentModal === 'update' && ( diff --git a/src/components/common/LinkList/LinkList.tsx b/src/components/common/LinkList/LinkList.tsx index d8ffb03a..2375fa2d 100644 --- a/src/components/common/LinkList/LinkList.tsx +++ b/src/components/common/LinkList/LinkList.tsx @@ -20,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 { @@ -105,7 +105,7 @@ const LinkList = ({ handleModalClose, handleChangeUrl, handleGetMeta, - } = useGetMeta({ getValues, setValue, modalClose }) + } = useGetMetaData({ getValues, setValue, modalClose }) const { links, fetchNextPage, hasNextPage, isLinksLoading } = useLinksQuery({ spaceId, fetchFn, @@ -140,7 +140,7 @@ const LinkList = ({ )} <> - {links?.pages[0].responses.length ? ( + {links?.pages[0].responses?.length ? ( links?.pages.map((group) => group.responses.map((link: Link) => ( Date: Sat, 15 Mar 2025 13:11:41 +0900 Subject: [PATCH 20/30] =?UTF-8?q?feat:=20=EC=98=B5=EC=85=94=EB=84=90=20?= =?UTF-8?q?=EC=B2=B4=EC=9D=B4=EB=8B=9D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Space/SpaceForm.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/Space/SpaceForm.tsx b/src/components/Space/SpaceForm.tsx index 3e823521..15b949bf 100644 --- a/src/components/Space/SpaceForm.tsx +++ b/src/components/Space/SpaceForm.tsx @@ -85,7 +85,6 @@ const SpaceForm = ({ spaceType, space }: SpaceFormProps) => { } else { if (spaceType === 'Create') { const { spaceId } = await postSpace({ data, file: imageFile }) - console.log(spaceId) notify('info', '스페이스가 생성되었습니다.') spaceId && router.replace(`/space/${spaceId}`) } else if (spaceType === 'Setting') { @@ -149,7 +148,7 @@ const SpaceForm = ({ spaceType, space }: SpaceFormProps) => {
- @{spaceData.space?.spaceName}{' '} + @{spaceData?.space?.spaceName}{' '} 에서 가져오는 중
From 40472a7573f593fd461445e2e8b0e166d479bfe4 Mon Sep 17 00:00:00 2001 From: Yeongjun Kim Date: Sat, 15 Mar 2025 13:42:05 +0900 Subject: [PATCH 21/30] =?UTF-8?q?feat:=20=ED=94=84=EB=A1=9C=ED=95=84=20?= =?UTF-8?q?=EC=A0=95=EB=B3=B4=EB=A5=BC=20=EC=84=9C=EB=B2=84=20=ED=8C=A8?= =?UTF-8?q?=EC=B9=98=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/(routes)/user/[userId]/page.tsx | 10 ++++----- src/services/users/useUsers.ts | 27 ++++++++++++++++--------- 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/src/app/(routes)/user/[userId]/page.tsx b/src/app/(routes)/user/[userId]/page.tsx index 5759c09a..e7c0c9f1 100644 --- a/src/app/(routes)/user/[userId]/page.tsx +++ b/src/app/(routes)/user/[userId]/page.tsx @@ -1,5 +1,3 @@ -'use client' - import { Avatar, CategoryListItem, @@ -7,11 +5,13 @@ import { ProfileEditButton, } from '@/components' import { CATEGORIES_RENDER, PROFILE_MSG } from '@/constants' -import { useGetUserProfile } from '@/services/users/useUsers' +import { fetchGetUserProfile } from '@/services/users/useUsers' import { UserLayoutProps } from './layout' -export default function UserPage({ params: { userId } }: UserLayoutProps) { - const { data: user } = useGetUserProfile(Number(userId)) +export default async function UserPage({ + params: { userId }, +}: UserLayoutProps) { + const user = await fetchGetUserProfile({ memberId: userId }) return ( <> diff --git a/src/services/users/useUsers.ts b/src/services/users/useUsers.ts index c9644810..2a913504 100644 --- a/src/services/users/useUsers.ts +++ b/src/services/users/useUsers.ts @@ -1,17 +1,10 @@ -import { - deleteFollow, - getFollowers, - getFollowing, - getMemberProfile, - getSearchMembers, - postFollow, - putMemberProfile, -} from '@/app/apis/member.api' 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' +const baseURL = process.env.NEXT_PUBLIC_API_INTERNAL_ADDRESS + // 멤버 프로필 조회 export const useGetUserProfile = (memberId: number) => { return useQuery({ @@ -25,6 +18,22 @@ export const useGetUserProfile = (memberId: number) => { }) } +// 멤버 프로필 조회 서버 함수 +export const fetchGetUserProfile = async ({ + memberId, +}: { + memberId?: number +}) => { + const path = `${baseURL}/api/user/${memberId}/profile` + + try { + const response = await fetch(path, { cache: 'no-store' }) + return response.json() + } catch (e) { + if (e instanceof Error) throw new Error(e.message) + } +} + // 멤버 프로필 수정 export const usePutUserProfile = (memberId: number) => { const queryClient = useQueryClient() From e1992c7de9b982a2aa880864cd2bac021485711d Mon Sep 17 00:00:00 2001 From: Yeongjun Kim Date: Sat, 15 Mar 2025 13:43:09 +0900 Subject: [PATCH 22/30] =?UTF-8?q?chore:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=EC=BD=94=EB=93=9C=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/services/space/useSpaces.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/services/space/useSpaces.ts b/src/services/space/useSpaces.ts index 342133f8..729468d4 100644 --- a/src/services/space/useSpaces.ts +++ b/src/services/space/useSpaces.ts @@ -1,11 +1,3 @@ -import { - getAccetpSpaceInvitation, - getSearchMyFavoriteSpaces, - getSearchMySpaces, - getSearchSpaces, - getSpaces, - postInviteSpace, -} from '@/app/apis/spaces.api' import { PAGE_SIZE, QUERY_KEYS } from '@/constants' import { IAcceptSpaceInvitation, From 18cf43d488cc182ab09c264d218e2860e0db180d Mon Sep 17 00:00:00 2001 From: Yeongjun Kim Date: Sat, 15 Mar 2025 14:03:16 +0900 Subject: [PATCH 23/30] =?UTF-8?q?feat:=20=EC=84=9C=EB=B2=84=20=ED=8C=A8?= =?UTF-8?q?=EC=B9=98=20=ED=95=A8=EC=88=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../PopularLinkList/HydratePopularLinkList.tsx | 4 ++-- src/services/link/useLink.ts | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/components/PopularLinkList/HydratePopularLinkList.tsx b/src/components/PopularLinkList/HydratePopularLinkList.tsx index 5688be3c..9e7e1c26 100644 --- a/src/components/PopularLinkList/HydratePopularLinkList.tsx +++ b/src/components/PopularLinkList/HydratePopularLinkList.tsx @@ -1,7 +1,7 @@ import React from 'react' -import { getPopularLinks } from '@/app/apis/link.api' import { QUERY_KEYS } from '@/constants' import { getQueryClient } from '@/lib/queryClient' +import { fetchGetPopularLinks } from '@/services/link/useLink' import { HydrationBoundary, dehydrate } from '@tanstack/react-query' import PopularLinkList from '../PopularLink/PopularLinkList/PopularLinkList' @@ -9,7 +9,7 @@ const HydratePopularLinkList = async () => { const queryClient = getQueryClient() await queryClient.prefetchQuery({ queryKey: [QUERY_KEYS.POPULAR_LINKS], - queryFn: getPopularLinks, + queryFn: fetchGetPopularLinks, }) return ( diff --git a/src/services/link/useLink.ts b/src/services/link/useLink.ts index 4174ae23..50c18039 100644 --- a/src/services/link/useLink.ts +++ b/src/services/link/useLink.ts @@ -105,6 +105,20 @@ export const useGetPopularLinks = () => { }) } +// 인기 링크 조회 서버 함수 +export const fetchGetPopularLinks = async () => { + const path = `/api/links` + + try { + const response = await fetch(path, { + method: 'GET', + }) + return response.json() + } catch (e) { + if (e instanceof Error) throw new Error(e.message) + } +} + // 링크 좋아요 export const usePostLikeLink = () => { return useMutation({ From b95239b3a10af354cc5085e2a44f189188bc5c13 Mon Sep 17 00:00:00 2001 From: Yeongjun Kim Date: Sun, 16 Mar 2025 01:58:14 +0900 Subject: [PATCH 24/30] =?UTF-8?q?chore:=20API=20=EB=9D=BC=EC=9A=B0?= =?UTF-8?q?=ED=84=B0=20=EA=B2=BD=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{email-verify => email/verify}/route.ts | 0 .../space/[spaceId]/links/readInfo/route.ts | 24 ------------------- .../{invitation => invitations}/route.ts | 0 3 files changed, 24 deletions(-) rename src/app/api/{email-verify => email/verify}/route.ts (100%) delete mode 100644 src/app/api/space/[spaceId]/links/readInfo/route.ts rename src/app/api/spaces/{invitation => invitations}/route.ts (100%) 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/route.ts deleted file mode 100644 index a7859716..00000000 --- a/src/app/api/space/[spaceId]/links/readInfo/route.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { useServerCookie } from '@/hooks/useServerCookie' -import { apiServer } from '@/services/apiServices' -import { NextRequest, NextResponse } from 'next/server' - -export async function POST(req: NextRequest) { - const { token } = useServerCookie() - const { searchParams } = new URL(req.url) - const spaceId = searchParams.get('spaceId') - const linkId = searchParams.get('linkId') - const path = `/spaces/${spaceId}/links/${linkId}/view` - const headers = { - Authorization: `Bearer ${token}`, - } - - try { - const data = await apiServer.post(path, {}, {}, headers) - return NextResponse.json(data) - } catch (error: any) { - return NextResponse.json( - { error: error.response.data.message }, - { status: error.response.status }, - ) - } -} 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 From 3b278d4958cf32218e2e9a140e3ce754c4f8ae5b Mon Sep 17 00:00:00 2001 From: Yeongjun Kim Date: Sun, 16 Mar 2025 04:09:11 +0900 Subject: [PATCH 25/30] =?UTF-8?q?refactor:=20FetchAPI=EC=99=80=20FetchServ?= =?UTF-8?q?erAPI=20=ED=81=B4=EB=9E=98=EC=8A=A4=EB=A5=BC=20=ED=95=A8?= =?UTF-8?q?=EC=88=98=ED=98=95=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/fetchAPI.ts | 151 +++++++++++++++++------------------- src/lib/fetchServerAPI.ts | 88 ++++++++++----------- src/services/apiServices.ts | 9 ++- 3 files changed, 119 insertions(+), 129 deletions(-) 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/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 } From ae803e92751fd6c67325628e008cc2e8d4053361 Mon Sep 17 00:00:00 2001 From: Yeongjun Kim Date: Mon, 17 Mar 2025 12:53:56 +0900 Subject: [PATCH 26/30] =?UTF-8?q?refactor:=20API=20=ED=98=B8=EC=B6=9C=20?= =?UTF-8?q?=EB=B0=A9=EC=8B=9D=EC=9D=84=20fetchAPI=20=EC=9C=A0=ED=8B=B8?= =?UTF-8?q?=EB=A6=AC=ED=8B=B0=20=ED=95=A8=EC=88=98=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/api/auth/route.ts | 12 +- .../links/readInfo/[linkId]/route.ts | 25 ++++ src/components/common/LinkItem/LinkItem.tsx | 10 +- .../common/LinkItem/hooks/useReadSaveLink.ts | 4 +- src/services/auth/useAuth.ts | 37 ++---- src/services/comments/useComments.ts | 59 +++------ src/services/email/useEmails.ts | 15 +-- src/services/link/useLink.ts | 123 +++++++++--------- src/services/meta/useMeta.ts | 16 +-- src/services/notification/useNotification.ts | 21 +-- src/services/space/useSpace.ts | 106 ++++++++------- src/services/space/useSpaces.ts | 51 +++----- src/services/users/useUsers.ts | 78 ++++++----- 13 files changed, 272 insertions(+), 285 deletions(-) create mode 100644 src/app/api/space/[spaceId]/links/readInfo/[linkId]/route.ts diff --git a/src/app/api/auth/route.ts b/src/app/api/auth/route.ts index c80d1ec5..52608180 100644 --- a/src/app/api/auth/route.ts +++ b/src/app/api/auth/route.ts @@ -1,21 +1,17 @@ import { useServerCookie } from '@/hooks/useServerCookie' +import { apiServer } from '@/services/apiServices' import { NextResponse } from 'next/server' -const baseURL = process.env.NEXT_PUBLIC_API_ADDRESS - export async function GET(_request: Request) { const { token } = useServerCookie() - const path = `${baseURL}/members/profile` - try { - const response = await fetch(path, { - method: 'GET', + const response = await apiServer.get(`/members/profile`, { headers: { Authorization: 'Bearer ' + token, }, }) - const data = await response.json() - return NextResponse.json(data) + + return NextResponse.json(response) } catch (error: any) { return NextResponse.json( { error: error.response.data.message }, diff --git a/src/app/api/space/[spaceId]/links/readInfo/[linkId]/route.ts b/src/app/api/space/[spaceId]/links/readInfo/[linkId]/route.ts new file mode 100644 index 00000000..86fd42a5 --- /dev/null +++ b/src/app/api/space/[spaceId]/links/readInfo/[linkId]/route.ts @@ -0,0 +1,25 @@ +import { useServerCookie } from '@/hooks/useServerCookie' +import { apiServer } from '@/services/apiServices' +import { NextRequest, NextResponse } from 'next/server' + +export async function POST( + req: NextRequest, + { params }: { params: { spaceId: string; linkId: string } }, +) { + const { token } = useServerCookie() + const { spaceId, linkId } = params + const path = `/spaces/${spaceId}/links/${linkId}/view` + const headers = { + Authorization: `Bearer ${token}`, + } + + try { + const data = await apiServer.post(path, {}, {}, headers) + return NextResponse.json(data) + } catch (error: any) { + return NextResponse.json( + { error: error.response.data.message }, + { status: error.response.status }, + ) + } +} diff --git a/src/components/common/LinkItem/LinkItem.tsx b/src/components/common/LinkItem/LinkItem.tsx index 0662fb2f..2d5d0a39 100644 --- a/src/components/common/LinkItem/LinkItem.tsx +++ b/src/components/common/LinkItem/LinkItem.tsx @@ -105,9 +105,11 @@ const LinkItem = ({ spaceId, linkId, }) - const { mutate: deleteLink, isPending: isDeleteLinkLoading } = - useDeleteLink(spaceId) - const { handleSaveReadInfo } = useReadSaveLink() + const { mutate: deleteLink, isPending: isDeleteLinkLoading } = useDeleteLink({ + spaceId, + linkId, + }) + const { handleSaveReadInfo } = useReadSaveLink({ spaceId, linkId }) const { isLiked, likeCount, handleClickLike } = useLikeLink({ spaceId, linkId, @@ -309,7 +311,7 @@ const LinkItem = ({ })() : spaceId && (() => { - deleteLink({ spaceId, linkId }) + deleteLink() })() } type="form"> diff --git a/src/components/common/LinkItem/hooks/useReadSaveLink.ts b/src/components/common/LinkItem/hooks/useReadSaveLink.ts index 1c9f7253..36d642c0 100644 --- a/src/components/common/LinkItem/hooks/useReadSaveLink.ts +++ b/src/components/common/LinkItem/hooks/useReadSaveLink.ts @@ -6,9 +6,9 @@ export interface HandleSaveReadInfoProps { linkId: number } -const useReadSaveLink = () => { +const useReadSaveLink = ({ spaceId }: HandleSaveReadInfoProps) => { const { isLoggedIn } = useCurrentUser() - const { mutate: postReadSaveLink } = usePostReadSaveLink() + const { mutate: postReadSaveLink } = usePostReadSaveLink(spaceId) const handleSaveReadInfo = ({ spaceId, linkId }: HandleSaveReadInfoProps) => { if (spaceId && linkId && isLoggedIn) { diff --git a/src/services/auth/useAuth.ts b/src/services/auth/useAuth.ts index b2ea43fa..8d90eeac 100644 --- a/src/services/auth/useAuth.ts +++ b/src/services/auth/useAuth.ts @@ -1,6 +1,7 @@ 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 = () => { @@ -14,23 +15,20 @@ export const useRegisterUser = () => { }) => { 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 fetch(path, { - method: 'POST', - body: formData, - }) - - if (!response.ok) { - throw new Error('Failed to register user') - } - - return response.json() + const response = await apiClient.post( + '/api/registerUser', + formData, + {}, + {}, + 'multipart', + ) + return response }, onError: (error: Error) => { console.error('Registration error:', error) @@ -40,25 +38,16 @@ export const useRegisterUser = () => { // 토큰 검증 export const validateToken = async () => { - const response = await fetch('/api/auth', { - method: 'GET', - }) - return await response.json() + const response = await apiClient.get('/api/auth') + return response } // 카카오 로그아웃 export const useKakaoLogout = () => { return useMutation({ mutationFn: async () => { - const response = await fetch('/api/logout', { - method: 'POST', - }) - - if (!response.ok) { - throw new Error('Logout failed') - } - - return response.json() + const response = await apiClient.post(`/api/logout`, {}, {}) + return response }, onError: (error: Error) => { console.error('Logout error:', error) diff --git a/src/services/comments/useComments.ts b/src/services/comments/useComments.ts index 108da8f3..0aa52dc0 100644 --- a/src/services/comments/useComments.ts +++ b/src/services/comments/useComments.ts @@ -1,6 +1,7 @@ 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 ({ @@ -15,14 +16,10 @@ export const fetchGetComments = async ({ const queryString = new URLSearchParams(params).toString() try { - const response = await fetch( + const response = await apiClient.get( `/api/space/${spaceId}/comments?${queryString}`, - { - method: 'GET', - }, ) - const data = await response.json() - return data + return response } catch (e) { if (e instanceof Error) throw new Error(e.message) } @@ -31,6 +28,7 @@ export const fetchGetComments = async ({ // 댓글 생성 export const usePostComment = (spaceId: number) => { const queryClient = useQueryClient() + return useMutation({ mutationFn: async ({ spaceId, @@ -39,12 +37,11 @@ export const usePostComment = (spaceId: number) => { spaceId: number content: string }) => { - const response = await fetch(`/api/space/${spaceId}/comments/create`, { - method: 'POST', - body: JSON.stringify({ content }), - }) - const data = await response.json() - return data + const response = await apiClient.post( + `/api/space/${spaceId}/comments/create`, + { content }, + ) + return response }, onSuccess: () => { queryClient.invalidateQueries({ @@ -60,6 +57,7 @@ export const usePostComment = (spaceId: number) => { // 댓글 수정 export const usePutComment = (spaceId?: number, parentCommentId?: number) => { const queryClient = useQueryClient() + return useMutation({ mutationFn: async ({ spaceId, @@ -70,15 +68,11 @@ export const usePutComment = (spaceId?: number, parentCommentId?: number) => { commentId: number content: string }) => { - const response = await fetch( + const response = await apiClient.put( `/api/space/${spaceId}/comments/${commentId}`, - { - method: 'PUT', - body: JSON.stringify({ content }), - }, + { content }, ) - const data = await response.json() - return data + return response }, onSuccess: () => { queryClient.invalidateQueries({ @@ -99,14 +93,10 @@ export const useDeleteComment = (spaceId: number, parentCommentId?: number) => { const queryClient = useQueryClient() return useMutation({ mutationFn: async ({ commentId }: { commentId: number }) => { - const response = await fetch( + const response = await apiClient.delete( `/api/space/${spaceId}/comments/${commentId}`, - { - method: 'DELETE', - }, ) - const data = await response.json() - return data + return response }, onSuccess: () => { queryClient.invalidateQueries({ @@ -136,14 +126,10 @@ export const fetchGetReplies = async ({ const queryString = new URLSearchParams(params).toString() try { - const response = await fetch( + const response = await apiClient.get( `/api/space/${spaceId}/comments/${commentId}/replies?${queryString}`, - { - method: 'GET', - }, ) - const data = await response.json() - return data + return response } catch (e) { if (e instanceof Error) throw new Error(e.message) } @@ -152,6 +138,7 @@ export const fetchGetReplies = async ({ // 대댓글 생성 export const usePostReply = (spaceId: number, parentCommentId?: number) => { const queryClient = useQueryClient() + return useMutation({ mutationFn: async ({ spaceId, @@ -162,15 +149,11 @@ export const usePostReply = (spaceId: number, parentCommentId?: number) => { commentId: number content: string }) => { - const response = await fetch( + const response = await apiClient.post( `/api/space/${spaceId}/comments/${commentId}/replies`, - { - method: 'POST', - body: JSON.stringify({ content }), - }, + { content }, ) - const data = await response.json() - return data + return response }, onSuccess: () => { queryClient.invalidateQueries({ diff --git a/src/services/email/useEmails.ts b/src/services/email/useEmails.ts index 84dc127c..ff5670fc 100644 --- a/src/services/email/useEmails.ts +++ b/src/services/email/useEmails.ts @@ -4,16 +4,14 @@ import { } 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 fetch(`/api/email`, { - method: 'POST', - body: JSON.stringify(email), - }) - return response.json() + const response = await apiClient.post(`/api/email`, email) + return response }, onError: (error: Error) => { console.log(error) @@ -25,11 +23,8 @@ export const usePostEmail = () => { export const usePostEmailVerify = () => { return useMutation({ mutationFn: async (data: EmailVerifyReqBody & EmailReqBody) => { - const response = await fetch(`/api/email/verify`, { - method: 'POST', - body: JSON.stringify(data), - }) - return response.json() + const response = await apiClient.post(`/api/email/verify`, data) + return response }, onError: (error: Error) => { console.log(error) diff --git a/src/services/link/useLink.ts b/src/services/link/useLink.ts index 50c18039..6c8b564e 100644 --- a/src/services/link/useLink.ts +++ b/src/services/link/useLink.ts @@ -2,6 +2,7 @@ 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 ({ @@ -20,13 +21,10 @@ export const fetchGetLinks = async ({ const queryString = new URLSearchParams(params).toString() try { - const response = await fetch(`/api/space/${spaceId}/links?${queryString}`, { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - }, - }) - return response.json() + const response = await apiClient.get( + `/api/space/${spaceId}/links?${queryString}`, + ) + return response } catch (e) { if (e instanceof Error) throw new Error(e.message) } @@ -35,15 +33,15 @@ export const fetchGetLinks = async ({ // 링크 생성 export const usePostLink = (spaceId?: number) => { const queryClient = useQueryClient() + return useMutation({ - mutationFn: (query: IUpdateLink['query']) => - fetch(`/api/space/${spaceId}/links`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(query), - }), + 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] }) @@ -53,18 +51,16 @@ export const usePostLink = (spaceId?: number) => { }, }) } + // 링크 수정 export const usePutLink = (spaceId?: number) => { const queryClient = useQueryClient() + return useMutation({ - mutationFn: (query: IUpdateLink['query']) => - fetch(`/api/space/${spaceId}/links`, { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(query), - }).then((res) => res.json()), + 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] }) @@ -76,13 +72,25 @@ export const usePutLink = (spaceId?: number) => { } // 링크 삭제 -export const useDeleteLink = (spaceId?: number) => { +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: (query: ISpaceLink['query']) => { - return fetch(`/api/space/${query.spaceId}/links/${query.linkId}`, { - method: 'DELETE', - }).then((res) => res.json()) + mutationFn: async () => { + const response = await apiClient.delete( + `/api/space/${spaceId}/links?${queryString}`, + ) + return response }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.LINKS, spaceId] }) @@ -98,22 +106,18 @@ export const useDeleteLink = (spaceId?: number) => { export const useGetPopularLinks = () => { return useQuery({ queryKey: [QUERY_KEYS.POPULAR_LINKS], - queryFn: () => - fetch('/api/links', { - method: 'GET', - }).then((res) => res.json()), + queryFn: async () => { + const response = await apiClient.get(`/api/links`) + return response + }, }) } // 인기 링크 조회 서버 함수 export const fetchGetPopularLinks = async () => { - const path = `/api/links` - try { - const response = await fetch(path, { - method: 'GET', - }) - return response.json() + const response = await apiClient.get(`/api/links`) + return response } catch (e) { if (e instanceof Error) throw new Error(e.message) } @@ -122,14 +126,13 @@ export const fetchGetPopularLinks = async () => { // 링크 좋아요 export const usePostLikeLink = () => { return useMutation({ - mutationFn: (query: ILikeLink['query']) => - fetch(`/api/links/${query.linkId}/like`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(query), - }).then((res) => res.json()), + mutationFn: async (query: ILikeLink['query']) => { + const response = await apiClient.post( + `/api/links/${query.linkId}/like`, + query, + ) + return response + }, onError: (error: Error) => { console.log(error) }, @@ -139,10 +142,10 @@ export const usePostLikeLink = () => { // 링크 좋아요 취소 export const useDeleteLikeLink = () => { return useMutation({ - mutationFn: (query: ILikeLink['query']) => - fetch(`/api/links/${query.linkId}/like`, { - method: 'DELETE', - }).then((res) => res.json()), + mutationFn: async (query: ILikeLink['query']) => { + const response = await apiClient.delete(`/api/links/${query.linkId}/like`) + return response + }, onError: (error: Error) => { console.log(error) }, @@ -150,19 +153,19 @@ export const useDeleteLikeLink = () => { } // 링크 읽기 저장 -export const usePostReadSaveLink = () => { +export const usePostReadSaveLink = (spaceId?: number) => { const queryClient = useQueryClient() + return useMutation({ - mutationFn: (query: ISpaceLink['query']) => - fetch(`/api/space/${query.spaceId}/links/readInfo/${query.linkId}`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(query), - }).then((res) => res.json()), + 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] }) + queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.LINKS, spaceId] }) }, onError: (error: Error) => { console.log(error) diff --git a/src/services/meta/useMeta.ts b/src/services/meta/useMeta.ts index 8a21c733..1a6d1eec 100644 --- a/src/services/meta/useMeta.ts +++ b/src/services/meta/useMeta.ts @@ -1,21 +1,11 @@ import { useMutation } from '@tanstack/react-query' +import { apiClient } from '../apiServices' export const usePostMeta = () => { return useMutation({ mutationFn: async ({ url }: { url: string }) => { - const response = await fetch(`/api/meta`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ url }), - }) - - if (!response.ok) { - throw new Error('Failed to fetch meta data') - } - - return response.json() + 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/useNotification.ts b/src/services/notification/useNotification.ts index 01d5884a..9adefea0 100644 --- a/src/services/notification/useNotification.ts +++ b/src/services/notification/useNotification.ts @@ -1,14 +1,15 @@ 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 fetch(`/api/notification/unchecked`) - return response.json() + const response = await apiClient.get(`/api/notification/unchecked`) + return response }, }) } @@ -25,8 +26,10 @@ export const fetchGetInvitations = async ({ const queryString = new URLSearchParams(params).toString() try { - const response = await fetch(`/api/notification/invitations?${queryString}`) - return response.json() + const response = await apiClient.get( + `/api/notification/invitations?${queryString}`, + ) + return response } catch (e) { if (e instanceof Error) throw new Error(e.message) } @@ -35,11 +38,13 @@ export const fetchGetInvitations = async ({ // 알림 삭제 export const useDeleteNotifications = () => { const queryClient = useQueryClient() + return useMutation({ - mutationFn: (notificationId: number) => { - return fetch(`/api/notification/${notificationId}`, { - method: 'DELETE', - }) + mutationFn: async (notificationId: number) => { + const response = await apiClient.delete( + `/api/notification/${notificationId}`, + ) + return response }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.INVITATIONS] }) diff --git a/src/services/space/useSpace.ts b/src/services/space/useSpace.ts index c7f1609a..b630dd62 100644 --- a/src/services/space/useSpace.ts +++ b/src/services/space/useSpace.ts @@ -7,6 +7,7 @@ import { } 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 @@ -16,10 +17,7 @@ export interface FetchGetSpaceProps { export const useGetSpace = (spaceId?: number) => { return useQuery({ queryKey: [QUERY_KEYS.SPACES, spaceId], - queryFn: async () => - await fetch(`/api/space/${spaceId}`, { - method: 'GET', - }).then((res) => res.json()), + queryFn: async () => await apiClient.get(`/api/space/${spaceId}`), enabled: !!spaceId, }) } @@ -27,6 +25,7 @@ export const useGetSpace = (spaceId?: number) => { // 스페이스 생성 export const usePostSpace = () => { const queryClient = useQueryClient() + return useMutation({ mutationFn: async ({ data, file }: ICreateSpace) => { const reqData = { ...data } @@ -34,16 +33,14 @@ export const usePostSpace = () => { formData.append('request', JSON.stringify(reqData)) file && formData.append('file', file) - const response = await fetch('/api/spaces/create', { - method: 'POST', - body: formData, - }) - - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`) - } - - return response.json() + const response = await apiClient.post( + `/api/spaces/create`, + formData, + {}, + {}, + 'multipart', + ) + return response }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.SPACES] }) @@ -64,16 +61,14 @@ export const usePatchSpace = (spaceId?: number) => { formData.append('request', JSON.stringify(reqData)) file && formData.append('file', file) - const response = await fetch(`/api/space/${spaceId}`, { - method: 'PATCH', - body: formData, - }) - - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`) - } - - return response.json() + const response = await apiClient.patch( + `/api/space/${spaceId}`, + formData, + {}, + {}, + 'multipart', + ) + return response }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.SPACES] }) @@ -88,11 +83,12 @@ export const usePatchSpace = (spaceId?: number) => { // 스페이스 삭제 export const useDeleteSpace = () => { const queryClient = useQueryClient() + return useMutation({ - mutationFn: async (query: ISpaceQuery['query']) => - await fetch(`/api/space/${query.spaceId}`, { - method: 'DELETE', - }).then((res) => res.json()), + mutationFn: async (query: ISpaceQuery['query']) => { + const response = await apiClient.delete(`/api/space/${query.spaceId}`) + return response + }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.SPACES] }) }, @@ -105,11 +101,15 @@ export const useDeleteSpace = () => { // 스페이스 즐겨찾기 export const usePostFavoriteSpace = () => { const queryClient = useQueryClient() + return useMutation({ - mutationFn: (query: ISpaceQuery['query']) => - fetch(`/api/space/${query.spaceId}/favorites`, { - method: 'POST', - }).then((res) => res.json()), + mutationFn: async (query: ISpaceQuery['query']) => { + const response = await apiClient.post( + `/api/space/${query.spaceId}/favorites`, + {}, + ) + return response + }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.SPACES] }) }, @@ -122,11 +122,14 @@ export const usePostFavoriteSpace = () => { // 스페이스 즐겨찾기 삭제 export const useDeleteFavoriteSpace = () => { const queryClient = useQueryClient() + return useMutation({ - mutationFn: (query: ISpaceQuery['query']) => - fetch(`/api/space/${query.spaceId}/favorites`, { - method: 'DELETE', - }).then((res) => res.json()), + mutationFn: async (query: ISpaceQuery['query']) => { + const response = await apiClient.delete( + `/api/space/${query.spaceId}/favorites`, + ) + return response + }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.SPACES] }) }, @@ -141,9 +144,7 @@ export const useGetTags = ({ spaceId }: ISpaceQuery['query']) => { return useQuery({ queryKey: [QUERY_KEYS.TAGS, spaceId], queryFn: async () => { - const response = await fetch(`/api/space/${spaceId}/tags`, { - method: 'GET', - }).then((res) => res.json()) + const response = await apiClient.get(`/api/space/${spaceId}/tags`) return response.tags }, enabled: !!spaceId, @@ -153,12 +154,15 @@ export const useGetTags = ({ spaceId }: ISpaceQuery['query']) => { // 스페이스 권한 변경 export const usePatchRole = (spaceId?: number) => { const queryClient = useQueryClient() + return useMutation({ - mutationFn: (query: IChangeRole['query']) => - fetch(`/api/space/${spaceId}/member/role`, { - method: 'PATCH', - body: JSON.stringify(query), - }).then((res) => res.json()), + 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] }) }, @@ -178,12 +182,14 @@ export const usePostScrapSpace = (spaceId?: number) => { const formData = new FormData() formData.append('request', JSON.stringify(reqData)) file && formData.append('file', file) - - const response = await fetch(`/api/space/${spaceId}/scrap/new`, { - method: 'POST', - body: formData, - }) - return await response.json() + const response = await apiClient.post( + `/api/space/${spaceId}/scrap/new`, + formData, + {}, + {}, + 'multipart', + ) + return response }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.SPACES] }) diff --git a/src/services/space/useSpaces.ts b/src/services/space/useSpaces.ts index 729468d4..415b7535 100644 --- a/src/services/space/useSpaces.ts +++ b/src/services/space/useSpaces.ts @@ -9,6 +9,7 @@ import { useMutation, useQueryClient, } from '@tanstack/react-query' +import { apiClient } from '../apiServices' // 전체 스페이스 필터 조회 (무한스크롤 fetch 함수) export const fetchGetSpaces = async ({ @@ -30,8 +31,8 @@ export const fetchGetSpaces = async ({ const queryString = new URLSearchParams(params).toString() try { - const response = await fetch(`/api/spaces?${queryString}`) - return response.json() + const response = await apiClient.get(`/api/spaces?${queryString}`) + return response } catch (e) { if (e instanceof Error) throw new Error(e.message) } @@ -55,8 +56,8 @@ export const fetchSearchSpaces = async ({ const queryString = new URLSearchParams(params).toString() try { - const response = await fetch(`/api/spaces/search?${queryString}`) - return response.json() + const response = await apiClient.get(`/api/spaces/search?${queryString}`) + return response } catch (e) { if (e instanceof Error) throw new Error(e.message) } @@ -79,8 +80,10 @@ export const fetchSearchMySpaces = async ({ const queryString = new URLSearchParams(params).toString() try { - const response = await fetch(`/api/user/${memberId}/spaces?${queryString}`) - return response.json() + const response = await apiClient.get( + `/api/user/${memberId}/spaces?${queryString}`, + ) + return response } catch (e) { if (e instanceof Error) throw new Error(e.message) } @@ -102,8 +105,8 @@ export const fetchGetMyFavoriteSpaces = async ({ const queryString = new URLSearchParams(params).toString() try { - const response = await fetch(`/api/user/favorites?${queryString}`) - return response.json() + const response = await apiClient.get(`/api/user/favorites?${queryString}`) + return response } catch (e) { if (e instanceof Error) throw new Error(e.message) } @@ -116,21 +119,11 @@ export const usePostInviteSpace = (): UseMutationResult< IInviteSpace['query'] > => { const queryClient = useQueryClient() + return useMutation({ mutationFn: async (query: IInviteSpace['query']) => { - const response = await fetch(`/api/space/invitations`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(query), - }) - - if (!response.ok) { - throw new Error('Failed to invite to space') - } - - return response.json() + const response = await apiClient.post(`/api/space/invitations`, query) + return response }, onSuccess: (data) => { queryClient.invalidateQueries({ @@ -150,21 +143,11 @@ export const usePostAccetpSpaceInvitation = (): UseMutationResult< IAcceptSpaceInvitation['query'] > => { const queryClient = useQueryClient() + return useMutation({ mutationFn: async (query: IAcceptSpaceInvitation['query']) => { - const response = await fetch(`/api/spaces/invitations`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(query), - }) - console.log(response) - if (!response.ok) { - throw new Error('Failed to accept space invitation') - } - - return response.json() + const response = await apiClient.post(`/api/spaces/invitations`, query) + return response }, onSuccess: (data) => { queryClient.invalidateQueries({ diff --git a/src/services/users/useUsers.ts b/src/services/users/useUsers.ts index 2a913504..ba73c8f9 100644 --- a/src/services/users/useUsers.ts +++ b/src/services/users/useUsers.ts @@ -2,17 +2,15 @@ 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' - -const baseURL = process.env.NEXT_PUBLIC_API_INTERNAL_ADDRESS +import { apiClient } from '../apiServices' // 멤버 프로필 조회 export const useGetUserProfile = (memberId: number) => { return useQuery({ queryKey: [QUERY_KEYS.MEMBERS, memberId], queryFn: async () => { - const response = await fetch(`/api/user/${memberId}/profile`) - const data = await response.json() - return data + const response = await apiClient.get(`/api/user/${memberId}/profile`) + return response }, enabled: !!memberId, }) @@ -24,11 +22,11 @@ export const fetchGetUserProfile = async ({ }: { memberId?: number }) => { - const path = `${baseURL}/api/user/${memberId}/profile` - try { - const response = await fetch(path, { cache: 'no-store' }) - return response.json() + 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) } @@ -37,6 +35,7 @@ export const fetchGetUserProfile = async ({ // 멤버 프로필 수정 export const usePutUserProfile = (memberId: number) => { const queryClient = useQueryClient() + return useMutation({ mutationFn: async ({ memberId, @@ -53,16 +52,14 @@ export const usePutUserProfile = (memberId: number) => { formData.append('file', file) } - const response = await fetch(`/api/user/${memberId}/profile`, { - method: 'PUT', - body: formData, - }) - - if (!response.ok) { - throw new Error('Failed to update profile') - } - - return await response.json() + const response = await apiClient.put( + `/api/user/${memberId}/profile`, + formData, + {}, + {}, + 'multipart', + ) + return response }, onSuccess: () => { queryClient.invalidateQueries({ @@ -74,6 +71,24 @@ export const usePutUserProfile = (memberId: number) => { }, }) } +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 ({ @@ -88,10 +103,10 @@ export const fetchGetFollowing = async ({ const queryString = new URLSearchParams(params).toString() try { - const response = await fetch( + const response = await apiClient.get( `/api/user/${memberId}/following?${queryString}`, ) - return response.json() + return response } catch (e) { if (e instanceof Error) throw new Error(e.message) } @@ -110,10 +125,10 @@ export const fetchGetFollowers = async ({ const queryString = new URLSearchParams(params).toString() try { - const response = await fetch( + const response = await apiClient.get( `/api/user/${memberId}/followers?${queryString}`, ) - return response.json() + return response } catch (e) { if (e instanceof Error) throw new Error(e.message) } @@ -124,10 +139,8 @@ export const usePostFollow = (profileId?: number) => { const queryClient = useQueryClient() return useMutation({ - mutationFn: ({ memberId }: IFollow) => { - const response = fetch(`/api/user/${memberId}/follow`, { - method: 'POST', - }) + mutationFn: async ({ memberId }: IFollow) => { + const response = await apiClient.post(`/api/user/${memberId}/follow`, {}) return response }, onSuccess: () => { @@ -152,10 +165,8 @@ export const useDeleteFollow = (profileId?: number) => { const queryClient = useQueryClient() return useMutation({ - mutationFn: ({ memberId }: IFollow) => { - const response = fetch(`/api/user/${memberId}/follow`, { - method: 'DELETE', - }) + mutationFn: async ({ memberId }: IFollow) => { + const response = await apiClient.delete(`/api/user/${memberId}/follow`) return response }, onSuccess: () => { @@ -189,9 +200,8 @@ export const fetchSearchUsers = async ({ const queryString = new URLSearchParams(params).toString() try { - const response = await fetch(`/api/user/search?${queryString}`) - const data = await response.json() - return data + const response = await apiClient.get(`/api/user/search?${queryString}`) + return response } catch (e) { if (e instanceof Error) throw new Error(e.message) } From 45c2d00f91210a527a120325f2d846870280e290 Mon Sep 17 00:00:00 2001 From: Yeongjun Kim Date: Mon, 17 Mar 2025 12:55:10 +0900 Subject: [PATCH 27/30] =?UTF-8?q?feat:=20LinkListProps=20=ED=83=80?= =?UTF-8?q?=EC=9E=85=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/common/LinkList/LinkList.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/common/LinkList/LinkList.tsx b/src/components/common/LinkList/LinkList.tsx index 2375fa2d..5a913d29 100644 --- a/src/components/common/LinkList/LinkList.tsx +++ b/src/components/common/LinkList/LinkList.tsx @@ -42,7 +42,7 @@ export interface Link { } export interface LinkListProps { - spaceId?: number + spaceId: number read?: boolean summary?: boolean edit?: boolean From ecccfbc53b5425145b185c611015b78f76528d88 Mon Sep 17 00:00:00 2001 From: Yeongjun Kim Date: Mon, 17 Mar 2025 12:56:39 +0900 Subject: [PATCH 28/30] =?UTF-8?q?refactor:=20=ED=94=84=EB=A1=9C=ED=95=84?= =?UTF-8?q?=20=EC=84=9C=EB=B2=84=20=ED=8C=A8=EC=B9=98=20=ED=95=A8=EC=88=98?= =?UTF-8?q?=20=EB=84=A4=EC=9D=B4=EB=B0=8D=20=EB=B0=8F=20=EA=B2=BD=EB=A1=9C?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/(routes)/user/[userId]/layout.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/(routes)/user/[userId]/layout.tsx b/src/app/(routes)/user/[userId]/layout.tsx index 9fc74392..27c8f731 100644 --- a/src/app/(routes)/user/[userId]/layout.tsx +++ b/src/app/(routes)/user/[userId]/layout.tsx @@ -1,5 +1,5 @@ -import { getMemberProfile } from '@/app/apis/member.api' import { ProfileTap } from '@/components' +import { fetchGetUserProfile } from '@/services/users/useUsers' import { Metadata } from 'next' export type UserLayoutProps = { @@ -9,7 +9,7 @@ export type UserLayoutProps = { export async function generateMetadata({ params: { userId }, }: UserLayoutProps): Promise { - const user = await getMemberProfile({ memberId: userId }) + const user = await fetchGetUserProfile({ memberId: userId }) return { title: user.nickname, From f3e338f29434eb30a6f45bb254f9fcd83b9af548 Mon Sep 17 00:00:00 2001 From: Yeongjun Kim Date: Mon, 17 Mar 2025 12:58:49 +0900 Subject: [PATCH 29/30] =?UTF-8?q?feat:=20=ED=94=84=EB=A1=9C=ED=95=84=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20mutation=EC=9D=84=20=EB=8F=99=EA=B8=B0?= =?UTF-8?q?=EC=A0=81=EC=9C=BC=EB=A1=9C=20=EC=B2=98=EB=A6=AC=ED=95=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/UserInfoForm/UserInfoForm.tsx | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/components/UserInfoForm/UserInfoForm.tsx b/src/components/UserInfoForm/UserInfoForm.tsx index 606eb96d..ac054e38 100644 --- a/src/components/UserInfoForm/UserInfoForm.tsx +++ b/src/components/UserInfoForm/UserInfoForm.tsx @@ -4,6 +4,7 @@ import { ChangeEvent, useEffect, useRef, useState } from 'react' import { useForm } from 'react-hook-form' import { Avatar, CategoryList, Input } from '@/components' import { usePostEmail, usePostEmailVerify } from '@/services/email/useEmails' +import { fetchPostUserProfile } from '@/services/users/useUsers' import { usePutUserProfile } from '@/services/users/useUsers' import { UserProfileResBody } from '@/types' import { cls } from '@/utils' @@ -39,7 +40,7 @@ const UserInfoForm = ({ userData, formType }: UserInfoFormProps) => { const [imageFile, setImageFile] = useState() const router = useRouter() const [isVerification, setVerification] = useState(false) - const { mutate: putUserProfileMutation } = usePutUserProfile( + const { mutateAsync: putUserProfileMutation } = usePutUserProfile( userData?.memberId || 0, ) const { mutateAsync: postEmail } = usePostEmail() @@ -153,14 +154,16 @@ const UserInfoForm = ({ userData, formType }: UserInfoFormProps) => { } } - const handleSettingUser = (data: RegisterReqBody & EmailVerifyReqBody) => { + const handleSettingUser = async ( + data: RegisterReqBody & EmailVerifyReqBody, + ) => { try { userData?.memberId && - putUserProfileMutation({ + (await putUserProfileMutation({ memberId: userData?.memberId, data, file: imageFile, - }) + })) notify('success', '수정되었습니다.') router.refresh() router.back() From 48255f116125d0fdaac7d8e13f9684fca025a87c Mon Sep 17 00:00:00 2001 From: Yeongjun Kim Date: Mon, 17 Mar 2025 13:15:44 +0900 Subject: [PATCH 30/30] =?UTF-8?q?chore:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=ED=8C=8C=EC=9D=BC=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/apis/comments.api.ts | 115 ----------------------------------- 1 file changed, 115 deletions(-) delete mode 100644 src/app/apis/comments.api.ts diff --git a/src/app/apis/comments.api.ts b/src/app/apis/comments.api.ts deleted file mode 100644 index b610bab3..00000000 --- a/src/app/apis/comments.api.ts +++ /dev/null @@ -1,115 +0,0 @@ -import axios from 'axios' -import Cookies from 'js-cookie' - -const baseURL = process.env.NEXT_PUBLIC_API_ADDRESS - -// 댓글 조회 -export const getComments = async ({ - spaceId, - searchParams, -}: { - spaceId: number | undefined - searchParams: string -}) => { - const token = Cookies.get('Auth-token') - const response = await axios.get( - `${baseURL}/spaces/${spaceId}/comments?${searchParams}`, - { - headers: { - Authorization: `Bearer ${token}`, - }, - }, - ) - return response.data -} - -// 댓글 생성 -export const createComment = async ({ - spaceId, - content, -}: { - spaceId?: number - content: string -}) => { - const token = Cookies.get('Auth-token') - const response = await axios.post( - `${baseURL}/spaces/${spaceId}/comments`, - { content }, - { - headers: { Authorization: `Bearer ${token}` }, - }, - ) - return response.data -} - -// 댓글 수정 -export const updateComment = async ({ - spaceId, - commentId, - content, -}: { - spaceId?: number - commentId?: number - content: string -}) => { - const token = Cookies.get('Auth-token') - const response = await axios.put( - `${baseURL}/spaces/${spaceId}/comments/${commentId}`, - { content }, - { headers: { Authorization: `Bearer ${token}` } }, - ) - return response.data -} - -// 댓글 삭제 -export const deleteComment = async ({ - spaceId, - commentId, -}: { - spaceId: number | undefined - commentId: number | undefined -}) => { - const token = Cookies.get('Auth-token') - const response = await axios.delete( - `${baseURL}/spaces/${spaceId}/comments/${commentId}`, - { headers: { Authorization: `Bearer ${token}` } }, - ) - return response.data -} - -// 대댓글 조회 -export const getReplies = async ({ - spaceId, - commentId, - searchParams, -}: { - spaceId: number | undefined - commentId: number | undefined - searchParams: string -}) => { - const token = Cookies.get('Auth-token') - const response = await axios.get( - `${baseURL}/spaces/${spaceId}/comments/${commentId}/replies?${searchParams}`, - { headers: { Authorization: `Bearer ${token}` } }, - ) - return response.data -} - -// 대댓글 생성 -export const createReply = async ({ - spaceId, - commentId, - content, -}: { - spaceId: number - commentId: number - content: string -}) => { - const token = Cookies.get('Auth-token') - const response = await axios.post( - `${baseURL}/spaces/${spaceId}/comments/${commentId}/replies`, - { content }, - { headers: { Authorization: `Bearer ${token}` } }, - ) - return response.data -}