diff --git a/package.json b/package.json index d8a35b1..35357c7 100644 --- a/package.json +++ b/package.json @@ -10,8 +10,11 @@ "lint": "next lint" }, "dependencies": { + "@tanstack/query-core": "4.29.14", "@tanstack/react-query": "4.29.14", "axios": "1.4.0", + "ky": "0.33.3", + "ky-universal": "0.11.0", "next": "13.4.6", "postcss": "8.4.24", "react": "18.2.0", @@ -21,11 +24,12 @@ }, "devDependencies": { "@svgr/webpack": "8.0.1", + "@tanstack/react-query-devtools": "4.29.14", "@types/node": "20.3.1", "@types/react": "18.2.12", "@types/react-dom": "18.2.5", "autoprefixer": "10.4.14", - "classnames": "^2.3.2", + "classnames": "2.3.2", "eslint": "8.42.0", "eslint-config-next": "13.4.6", "eslint-config-prettier": "8.8.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3670207..991f4bd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5,12 +5,21 @@ settings: excludeLinksFromLockfile: false dependencies: + '@tanstack/query-core': + specifier: 4.29.14 + version: 4.29.14 '@tanstack/react-query': specifier: 4.29.14 version: 4.29.14(react-dom@18.2.0)(react@18.2.0) axios: specifier: 1.4.0 version: 1.4.0 + ky: + specifier: 0.33.3 + version: 0.33.3 + ky-universal: + specifier: 0.11.0 + version: 0.11.0(ky@0.33.3) next: specifier: 13.4.6 version: 13.4.6(@babel/core@7.22.5)(react-dom@18.2.0)(react@18.2.0)(sass@1.63.4) @@ -34,6 +43,9 @@ devDependencies: '@svgr/webpack': specifier: 8.0.1 version: 8.0.1 + '@tanstack/react-query-devtools': + specifier: 4.29.14 + version: 4.29.14(@tanstack/react-query@4.29.14)(react-dom@18.2.0)(react@18.2.0) '@types/node': specifier: 20.3.1 version: 20.3.1 @@ -47,7 +59,7 @@ devDependencies: specifier: 10.4.14 version: 10.4.14(postcss@8.4.24) classnames: - specifier: ^2.3.2 + specifier: 2.3.2 version: 2.3.2 eslint: specifier: 8.42.0 @@ -1757,9 +1769,30 @@ packages: tslib: 2.5.3 dev: false + /@tanstack/match-sorter-utils@8.8.4: + resolution: {integrity: sha512-rKH8LjZiszWEvmi01NR72QWZ8m4xmXre0OOwlRGnjU01Eqz/QnN+cqpty2PJ0efHblq09+KilvyR7lsbzmXVEw==} + engines: {node: '>=12'} + dependencies: + remove-accents: 0.4.2 + dev: true + /@tanstack/query-core@4.29.14: resolution: {integrity: sha512-ElEAahtLWA7Y7c2Haw10KdEf2tx+XZl/Z8dmyWxZehxWK3TPL5qtXtb7kUEhvt/8u2cSP62fLxgh2qqzMMGnDQ==} - dev: false + + /@tanstack/react-query-devtools@4.29.14(@tanstack/react-query@4.29.14)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-2H4otgQiXJSU7z8HIPw6whm7xfaEA3ouoM2PrWTHs+DMFX0BbodhOfQeJxsjw5uq2oV1yln/DABLJjZoQmQbpQ==} + peerDependencies: + '@tanstack/react-query': 4.29.14 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + dependencies: + '@tanstack/match-sorter-utils': 8.8.4 + '@tanstack/react-query': 4.29.14(react-dom@18.2.0)(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + superjson: 1.12.3 + use-sync-external-store: 1.2.0(react@18.2.0) + dev: true /@tanstack/react-query@4.29.14(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-wh4bd/QIy85YgTDBtj/7/9ZkpRX41QdZuUL8xKoSzuMCukXvAE1/oJ4p0F15lqQq9W3g2pgcbr2Aa+CNvqABhg==} @@ -1777,7 +1810,6 @@ packages: react: 18.2.0 react-dom: 18.2.0(react@18.2.0) use-sync-external-store: 1.2.0(react@18.2.0) - dev: false /@trysound/sax@0.2.0: resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==} @@ -1876,6 +1908,13 @@ packages: eslint-visitor-keys: 3.4.1 dev: true + /abort-controller@3.0.0: + resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} + engines: {node: '>=6.5'} + dependencies: + event-target-shim: 5.0.1 + dev: false + /acorn-jsx@5.3.2(acorn@8.9.0): resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -2248,6 +2287,13 @@ packages: /convert-source-map@1.9.0: resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} + /copy-anything@3.0.5: + resolution: {integrity: sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==} + engines: {node: '>=12.13'} + dependencies: + is-what: 4.1.15 + dev: true + /core-js-compat@3.31.0: resolution: {integrity: sha512-hM7YCu1cU6Opx7MXNu0NuumM0ezNeAeRKadixyiQELWY3vT3De9S4J5ZBMraWV2vZnrE1Cirl0GtFtDtMUXzPw==} dependencies: @@ -2325,6 +2371,11 @@ packages: resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} dev: true + /data-uri-to-buffer@4.0.1: + resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} + engines: {node: '>= 12'} + dev: false + /debug@3.2.7: resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} peerDependencies: @@ -2846,6 +2897,11 @@ packages: engines: {node: '>=0.10.0'} dev: true + /event-target-shim@5.0.1: + resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} + engines: {node: '>=6'} + dev: false + /execa@5.1.1: resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} engines: {node: '>=10'} @@ -2903,6 +2959,14 @@ packages: dependencies: reusify: 1.0.4 + /fetch-blob@3.2.0: + resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} + engines: {node: ^12.20 || >= 14.13} + dependencies: + node-domexception: 1.0.0 + web-streams-polyfill: 3.2.1 + dev: false + /file-entry-cache@6.0.1: resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} engines: {node: ^10.12.0 || >=12.0.0} @@ -2961,6 +3025,13 @@ packages: mime-types: 2.1.35 dev: false + /formdata-polyfill@4.0.10: + resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} + engines: {node: '>=12.20.0'} + dependencies: + fetch-blob: 3.2.0 + dev: false + /fraction.js@4.2.0: resolution: {integrity: sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==} dev: true @@ -3382,6 +3453,11 @@ packages: call-bind: 1.0.2 dev: true + /is-what@4.1.15: + resolution: {integrity: sha512-uKua1wfy3Yt+YqsD6mTUEa2zSi3G1oPlqTflgaPJ7z63vUGN5pxFpnQfeSLMFnJDEsdvOtkp1rUWkYjB4YfhgA==} + engines: {node: '>=12.13'} + dev: true + /is-wsl@2.2.0: resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} engines: {node: '>=8'} @@ -3450,6 +3526,26 @@ packages: object.assign: 4.1.4 dev: true + /ky-universal@0.11.0(ky@0.33.3): + resolution: {integrity: sha512-65KyweaWvk+uKKkCrfAf+xqN2/epw1IJDtlyCPxYffFCMR8u1sp2U65NtWpnozYfZxQ6IUzIlvUcw+hQ82U2Xw==} + engines: {node: '>=14.16'} + peerDependencies: + ky: '>=0.31.4' + web-streams-polyfill: '>=3.2.1' + peerDependenciesMeta: + web-streams-polyfill: + optional: true + dependencies: + abort-controller: 3.0.0 + ky: 0.33.3 + node-fetch: 3.3.1 + dev: false + + /ky@0.33.3: + resolution: {integrity: sha512-CasD9OCEQSFIam2U8efFK81Yeg8vNMTBUqtMOHlrcWQHqUX3HeCl9Dr31u4toV7emlH8Mymk5+9p0lL6mKb/Xw==} + engines: {node: '>=14.16'} + dev: false + /language-subtag-registry@0.3.22: resolution: {integrity: sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w==} dev: true @@ -3644,6 +3740,20 @@ packages: tslib: 2.5.3 dev: true + /node-domexception@1.0.0: + resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} + engines: {node: '>=10.5.0'} + dev: false + + /node-fetch@3.3.1: + resolution: {integrity: sha512-cRVc/kyto/7E5shrWca1Wsea4y6tL9iYJE5FBCius3JQfb/4P4I295PfhgbJQBLTx6lATE4z+wK0rPM4VS2uow==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + data-uri-to-buffer: 4.0.1 + fetch-blob: 3.2.0 + formdata-polyfill: 4.0.10 + dev: false + /node-releases@2.0.12: resolution: {integrity: sha512-QzsYKWhXTWx8h1kIvqfnC++o0pEmpRQA/aenALsL2F4pqNVr7YzcdMlDij5WBnwftRbJCNJL/O7zdKaxKPHqgQ==} @@ -3971,7 +4081,6 @@ packages: loose-envify: 1.4.0 react: 18.2.0 scheduler: 0.23.0 - dev: false /react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} @@ -3982,7 +4091,6 @@ packages: engines: {node: '>=0.10.0'} dependencies: loose-envify: 1.4.0 - dev: false /read-cache@1.0.0: resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} @@ -4046,6 +4154,10 @@ packages: jsesc: 0.5.0 dev: true + /remove-accents@0.4.2: + resolution: {integrity: sha512-7pXIJqJOq5tFgG1A2Zxti3Ht8jJF337m4sowbuHsW30ZnkQFnDzy9qBNhgzX8ZLW4+UBcXiiR7SwR6pokHsxiA==} + dev: true + /resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -4117,7 +4229,6 @@ packages: resolution: {integrity: sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==} dependencies: loose-envify: 1.4.0 - dev: false /semver@6.3.0: resolution: {integrity: sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==} @@ -4278,6 +4389,13 @@ packages: ts-interface-checker: 0.1.13 dev: false + /superjson@1.12.3: + resolution: {integrity: sha512-0j+U70KUtP8+roVPbwfqkyQI7lBt7ETnuA7KXbTDX3mCKiD/4fXs2ldKSMdt0MCfpTwiMxo20yFU3vu6ewETpQ==} + engines: {node: '>=10'} + dependencies: + copy-anything: 3.0.5 + dev: true + /supports-color@5.5.0: resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} engines: {node: '>=4'} @@ -4504,7 +4622,6 @@ packages: react: ^16.8.0 || ^17.0.0 || ^18.0.0 dependencies: react: 18.2.0 - dev: false /util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} @@ -4518,6 +4635,11 @@ packages: graceful-fs: 4.2.11 dev: false + /web-streams-polyfill@3.2.1: + resolution: {integrity: sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==} + engines: {node: '>= 8'} + dev: false + /which-boxed-primitive@1.0.2: resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} dependencies: diff --git a/src/apis/API.ts b/src/apis/API.ts new file mode 100644 index 0000000..5420129 --- /dev/null +++ b/src/apis/API.ts @@ -0,0 +1,97 @@ +import ky, { Options } from 'ky-universal'; + +const API = ky.create({ prefixUrl: process.env.SERVER_URL }); + +/** + * GET 요청을 처리하는 API 유틸 함수 getAsync + * @param T 요청 결과로 받을 데이터의 타입 + * + * @param url 요청을 전송할 URL + * @param config Ky 요청 관련 config (Options) + * @returns 요청 성공 시 T 객체, 요청 실패 시 에러 throw + */ +export async function getAsync( + url: string, + config?: Options, +): Promise { + const response = await API.get(url, { ...config }); + return response.json(); +} + +/** + * POST HTTP 요청을 처리하는 API 유틸 함수 postAsync + * @param T 요청 결과로 받을 데이터의 타입 + * + * @param url 요청을 전송할 URL + * @param data body 에 넣어 보낼 데이터 + * @param config Ky 요청 관련 config (Options) + * @returns 요청 성공 시 T 객체, 요청 실패 시 에러 throw + */ +export async function postAsync( + url: string, + data: unknown, + config?: Options, +): Promise { + const response = await API.post(url, { json: data, ...config }); + return response.json(); +} + +/** + * DELETE HTTP 요청을 처리하는 API 유틸 함수 deleteAsync + * @param T 요청 결과로 받을 데이터의 타입 + * + * @param url 요청을 전송할 URL + * @param data body 에 넣어 보낼 데이터 + * @param config Ky 요청 관련 config (Options) + * @returns 요청 성공 시 T 객체, 요청 실패 시 에러 throw + */ +export async function deleteAsync( + url: string, + data: unknown, + config?: Options, +): Promise { + const response = await API.delete(url, { json: data, ...config }); + return response.json(); +} + +/** + * PATCH HTTP 요청을 처리하는 API 유틸 함수 postAsync + * @param T 요청 결과로 받을 데이터의 타입 + * + * @param url 요청을 전송할 URL + * @param data body 에 넣어 보낼 데이터 + * @param config Ky 요청 관련 config (Options) + * @returns 요청 성공 시 T 객체, 요청 실패 시 에러 throw + */ +export async function patchAsync( + url: string, + data: unknown, + config?: Options, +): Promise { + const response = await API.patch(url, { + json: data, + ...config, + }); + return response.json(); +} + +/** + * PUT HTTP 요청을 처리하는 API 유틸 함수 putAsync + * @param T 요청 결과로 받을 데이터의 타입 + * + * @param url 요청을 전송할 URL + * @param data body 에 넣어 보낼 데이터 + * @param config Ky 요청 관련 config (Options) + * @returns 요청 성공 시 T 객체, 요청 실패 시 에러 throw + */ +export async function putAsync( + url: string, + data: unknown, + config?: Options, +): Promise { + const response = await API.put(url, { + json: data, + ...config, + }); + return response.json(); +} diff --git a/src/app/ReactQueryProvider.tsx b/src/app/ReactQueryProvider.tsx new file mode 100644 index 0000000..113a7eb --- /dev/null +++ b/src/app/ReactQueryProvider.tsx @@ -0,0 +1,26 @@ +'use client'; + +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; +import { PropsWithChildren, useState } from 'react'; + +export default function ReactQueryProvider({ children }: PropsWithChildren) { + const [client] = useState( + () => + new QueryClient({ + defaultOptions: { + queries: { + refetchOnWindowFocus: false, + retry: 0, + }, + }, + }), + ); + + return ( + + {children} + + + ); +} diff --git a/src/utils/getQueryClient.tsx b/src/utils/getQueryClient.tsx new file mode 100644 index 0000000..b78ce01 --- /dev/null +++ b/src/utils/getQueryClient.tsx @@ -0,0 +1,5 @@ +import { QueryClient } from '@tanstack/query-core'; +import { cache } from 'react'; + +const getQueryClient = cache(() => new QueryClient()); +export default getQueryClient; diff --git a/src/utils/hydrateOnClient.tsx b/src/utils/hydrateOnClient.tsx new file mode 100644 index 0000000..944c013 --- /dev/null +++ b/src/utils/hydrateOnClient.tsx @@ -0,0 +1,12 @@ +'use client'; + +import { + Hydrate as ReactQueryHydrate, + HydrateProps, +} from '@tanstack/react-query'; + +function HydrateOnClient(props: HydrateProps) { + return ; +} + +export default HydrateOnClient;