diff --git a/package.json b/package.json index 62c8352c..e833f309 100644 --- a/package.json +++ b/package.json @@ -3,12 +3,17 @@ "version": "0.1.0", "private": true, "dependencies": { + "@jiphyeonjeon-42/contracts": "https://github.com/jiphyeonjeon-42/backend/releases/download/v0.0.11-alpha/contracts.tgz", "@ladle/react": "^2.13.0", "@sentry/react": "^7.53.0", "@sentry/tracing": "^7.53.0", + "@tanstack/react-query": "^4.29.19", + "@tanstack/react-query-devtools": "^4.29.19", "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^14.0.0", "@testing-library/user-event": "^14.4.3", + "@ts-rest/core": "^3.28.0", + "@ts-rest/react-query": "^3.28.0", "@vitejs/plugin-react-swc": "^3.3.1", "@zxing/library": "^0.20.0", "axios": "^1.4.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0c55ed7e..b5a2cbe6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5,6 +5,9 @@ settings: excludeLinksFromLockfile: false dependencies: + '@jiphyeonjeon-42/contracts': + specifier: https://github.com/jiphyeonjeon-42/backend/releases/download/v0.0.11-alpha/contracts.tgz + version: '@github.com/jiphyeonjeon-42/backend/releases/download/v0.0.11-alpha/contracts.tgz(zod@3.22.2)' '@ladle/react': specifier: ^2.13.0 version: 2.13.0(react-dom@18.2.0)(react@18.2.0) @@ -14,6 +17,12 @@ dependencies: '@sentry/tracing': specifier: ^7.53.0 version: 7.53.0 + '@tanstack/react-query': + specifier: ^4.29.19 + version: 4.29.19(react-dom@18.2.0)(react@18.2.0) + '@tanstack/react-query-devtools': + specifier: ^4.29.19 + version: 4.29.19(@tanstack/react-query@4.29.19)(react-dom@18.2.0)(react@18.2.0) '@testing-library/jest-dom': specifier: ^5.16.5 version: 5.16.5 @@ -22,7 +31,13 @@ dependencies: version: 14.0.0(react-dom@18.2.0)(react@18.2.0) '@testing-library/user-event': specifier: ^14.4.3 - version: 14.4.3(@testing-library/dom@9.3.0) + version: 14.4.3(@testing-library/dom@9.3.1) + '@ts-rest/core': + specifier: ^3.28.0 + version: 3.28.0(zod@3.22.2) + '@ts-rest/react-query': + specifier: ^3.28.0 + version: 3.28.0(@tanstack/react-query@4.29.19)(@ts-rest/core@3.28.0)(react@18.2.0)(zod@3.22.2) '@vitejs/plugin-react-swc': specifier: ^3.3.1 version: 3.3.1(vite@4.3.8) @@ -106,6 +121,17 @@ packages: '@jridgewell/trace-mapping': 0.3.18 dev: false + /@anatine/zod-openapi@2.2.0(openapi3-ts@4.1.2)(zod@3.22.2): + resolution: {integrity: sha512-Ponwenf2NMIVbs9IuB3YoW7CQPkuRWxhYV/qxBu48DQhmRyipEw/YROVTWnvku7+lwhv1EyP4+h3J5SwrvmfHg==} + peerDependencies: + openapi3-ts: ^4.1.2 + zod: ^3.20.0 + dependencies: + openapi3-ts: 4.1.2 + ts-deepmerge: 6.2.0 + zod: 3.22.2 + dev: false + /@babel/code-frame@7.21.4: resolution: {integrity: sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==} engines: {node: '>=6.9.0'} @@ -113,6 +139,14 @@ packages: '@babel/highlight': 7.18.6 dev: false + /@babel/code-frame@7.22.10: + resolution: {integrity: sha512-/KKIMG4UEL35WmI9OlvMhurwtytjvXoFcGNrOvyG9zIzA8YmPjVtIZUf7b05+TPO7G7/GEmLHDaoCgACHl9hhA==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/highlight': 7.22.10 + chalk: 2.4.2 + dev: false + /@babel/compat-data@7.22.3: resolution: {integrity: sha512-aNtko9OPOwVESUFp3MZfD8Uzxl7JzSeJpd7npIoxCasU37PFbAQRpKglkaKwlHOyeJdrREpo8TW8ldrkYWwvIQ==} engines: {node: '>=6.9.0'} @@ -237,6 +271,11 @@ packages: engines: {node: '>=6.9.0'} dev: false + /@babel/helper-validator-identifier@7.22.5: + resolution: {integrity: sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==} + engines: {node: '>=6.9.0'} + dev: false + /@babel/helper-validator-option@7.21.0: resolution: {integrity: sha512-rmL/B8/f0mKS2baE9ZpyTcTavvEuWhTTW8amjzXNvYG4AwBsqTLikfXsEofsJEfKHf+HQVQbFOHy6o+4cnC/fQ==} engines: {node: '>=6.9.0'} @@ -262,6 +301,15 @@ packages: js-tokens: 4.0.0 dev: false + /@babel/highlight@7.22.10: + resolution: {integrity: sha512-78aUtVcT7MUscr0K5mIEnkwxPE0MaxkR5RxRwuHaQ+JuU5AmTPhY+do2mdzVTnIJJpyBglql2pehuBIWHug+WQ==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-validator-identifier': 7.22.5 + chalk: 2.4.2 + js-tokens: 4.0.0 + dev: false + /@babel/parser@7.22.4: resolution: {integrity: sha512-VLLsx06XkEYqBtE5YGPwfSGwfrjnyPP5oiGty3S8pQLFDFLaS8VwWSIxkTXpcvr5zeYLE6+MBNl2npl/YnfofA==} engines: {node: '>=6.0.0'} @@ -297,6 +345,13 @@ packages: regenerator-runtime: 0.13.11 dev: false + /@babel/runtime@7.22.11: + resolution: {integrity: sha512-ee7jVNlWN09+KftVOu9n7S8gQzD/Z6hN/I8VBRXW4P1+Xe7kJGXMwu8vds4aGIMHZnNbdpSWCfZZtinytpcAvA==} + engines: {node: '>=6.9.0'} + dependencies: + regenerator-runtime: 0.14.0 + dev: false + /@babel/template@7.21.9: resolution: {integrity: sha512-MK0X5k8NKOuWRamiEfc3KEJiHMTkGZNUjzMipqCGDDc6ijRl/B7RGSKVGncu4Ro/HdyzzY6cmoXuKI2Gffk7vQ==} engines: {node: '>=6.9.0'} @@ -960,6 +1015,50 @@ packages: '@swc/core-win32-x64-msvc': 1.3.57 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: false + + /@tanstack/query-core@4.29.19: + resolution: {integrity: sha512-uPe1DukeIpIHpQi6UzIgBcXsjjsDaLnc7hF+zLBKnaUlh7jFE/A+P8t4cU4VzKPMFB/C970n/9SxtpO5hmIRgw==} + dev: false + + /@tanstack/react-query-devtools@4.29.19(@tanstack/react-query@4.29.19)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-rL2xqTPr+7gJvVGwyq8E8CWqqw950N4lZ6ffJeNX0qqymKHxHW1FM6nZaYt7Aufs/bXH0m1L9Sj3kDGQbp0rwg==} + peerDependencies: + '@tanstack/react-query': 4.29.19 + 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.19(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.4 + use-sync-external-store: 1.2.0(react@18.2.0) + dev: false + + /@tanstack/react-query@4.29.19(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-XiTIOHHQ5Cw1WUlHaD4fmVUMhoWjuNJlAeJGq7eM4BraI5z7y8WkZO+NR8PSuRnQGblpuVdjClQbDFtwxTtTUw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-native: '*' + peerDependenciesMeta: + react-dom: + optional: true + react-native: + optional: true + dependencies: + '@tanstack/query-core': 4.29.19 + 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 + /@testing-library/dom@9.3.0: resolution: {integrity: sha512-Dffe68pGwI6WlLRYR2I0piIkyole9cSBH5jGQKCGMRpHW5RHCqAUaqc2Kv0tUyd4dU4DLPKhJIjyKOnjv4tuUw==} engines: {node: '>=14'} @@ -974,6 +1073,20 @@ packages: pretty-format: 27.5.1 dev: false + /@testing-library/dom@9.3.1: + resolution: {integrity: sha512-0DGPd9AR3+iDTjGoMpxIkAsUihHZ3Ai6CneU6bRRrffXMgzCdlNk43jTrD2/5LT6CBb3MWTP8v510JzYtahD2w==} + engines: {node: '>=14'} + dependencies: + '@babel/code-frame': 7.22.10 + '@babel/runtime': 7.22.11 + '@types/aria-query': 5.0.1 + aria-query: 5.1.3 + chalk: 4.1.2 + dom-accessibility-api: 0.5.16 + lz-string: 1.5.0 + pretty-format: 27.5.1 + dev: false + /@testing-library/jest-dom@5.16.5: resolution: {integrity: sha512-N5ixQ2qKpi5OLYfwQmUb/5mSV9LneAcaUfp32pn4yCnpb8r/Yz0pXFPck21dIicKmi+ta5WRAknkZCfA8refMA==} engines: {node: '>=8', npm: '>=6', yarn: '>=1'} @@ -1003,13 +1116,41 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false - /@testing-library/user-event@14.4.3(@testing-library/dom@9.3.0): + /@testing-library/user-event@14.4.3(@testing-library/dom@9.3.1): resolution: {integrity: sha512-kCUc5MEwaEMakkO5x7aoD+DLi02ehmEM2QCGWvNqAS1dV/fAvORWEjnjsEIvml59M7Y5kCkWN6fCCyPOe8OL6Q==} engines: {node: '>=12', npm: '>=6'} peerDependencies: '@testing-library/dom': '>=7.21.4' dependencies: - '@testing-library/dom': 9.3.0 + '@testing-library/dom': 9.3.1 + dev: false + + /@ts-rest/core@3.28.0(zod@3.22.2): + resolution: {integrity: sha512-m0Wn2DgO3O57dsGT5fqxvF/WNFPx/8/dxbqLMV9TYXwLBgaJG4vDgR7qXVIYss/c5vlHTQ0oB9X+PwxhI3ksMg==} + peerDependencies: + zod: ^3.21.0 + peerDependenciesMeta: + zod: + optional: true + dependencies: + zod: 3.22.2 + dev: false + + /@ts-rest/react-query@3.28.0(@tanstack/react-query@4.29.19)(@ts-rest/core@3.28.0)(react@18.2.0)(zod@3.22.2): + resolution: {integrity: sha512-sLMTS48TeDYz5r0sGP2OgmHghe1MvGfHrOWLGjNux13SygDwAWuo5+RMW7YEAtN5CbxJYDIMl6xP86Zc8F2v0g==} + peerDependencies: + '@tanstack/react-query': ^4.18.0 + '@ts-rest/core': 3.28.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + zod: ^3.21.0 + peerDependenciesMeta: + zod: + optional: true + dependencies: + '@tanstack/react-query': 4.29.19(react-dom@18.2.0)(react@18.2.0) + '@ts-rest/core': 3.28.0(zod@3.22.2) + react: 18.2.0 + zod: 3.22.2 dev: false /@types/acorn@4.0.6: @@ -1539,7 +1680,7 @@ packages: normalize-path: 3.0.0 readdirp: 3.6.0 optionalDependencies: - fsevents: 2.3.2 + fsevents: 2.3.3 dev: false /ci-info@3.8.0: @@ -1632,6 +1773,13 @@ packages: keygrip: 1.1.0 dev: false + /copy-anything@3.0.5: + resolution: {integrity: sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==} + engines: {node: '>=12.13'} + dependencies: + is-what: 4.1.15 + dev: false + /cross-spawn@7.0.3: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} engines: {node: '>= 8'} @@ -2425,8 +2573,8 @@ packages: /fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} - /fsevents@2.3.2: - resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + /fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] requiresBuild: true @@ -3012,6 +3160,11 @@ packages: get-intrinsic: 1.2.0 dev: false + /is-what@4.1.15: + resolution: {integrity: sha512-uKua1wfy3Yt+YqsD6mTUEa2zSi3G1oPlqTflgaPJ7z63vUGN5pxFpnQfeSLMFnJDEsdvOtkp1rUWkYjB4YfhgA==} + engines: {node: '>=12.13'} + dev: false + /is-wsl@2.2.0: resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} engines: {node: '>=8'} @@ -3993,6 +4146,12 @@ packages: is-wsl: 2.2.0 dev: false + /openapi3-ts@4.1.2: + resolution: {integrity: sha512-B7gOkwsYMZO7BZXwJzXCuVagym2xhqsrilVvV0dnq2Di4+iLUXKVX9gOK23ZqaAHZOwABXN0QTdW8QnkUTX6DA==} + dependencies: + yaml: 2.3.1 + dev: false + /optionator@0.9.1: resolution: {integrity: sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==} engines: {node: '>= 0.8.0'} @@ -4299,6 +4458,10 @@ packages: resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==} dev: false + /regenerator-runtime@0.14.0: + resolution: {integrity: sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==} + dev: false + /regexp.prototype.flags@1.5.0: resolution: {integrity: sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==} engines: {node: '>= 0.4'} @@ -4355,6 +4518,10 @@ packages: unified: 10.1.2 dev: false + /remove-accents@0.4.2: + resolution: {integrity: sha512-7pXIJqJOq5tFgG1A2Zxti3Ht8jJF337m4sowbuHsW30ZnkQFnDzy9qBNhgzX8ZLW4+UBcXiiR7SwR6pokHsxiA==} + dev: false + /resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -4392,7 +4559,7 @@ packages: engines: {node: '>=14.18.0', npm: '>=8.0.0'} hasBin: true optionalDependencies: - fsevents: 2.3.2 + fsevents: 2.3.3 dev: false /run-applescript@5.0.0: @@ -4633,6 +4800,13 @@ packages: ts-interface-checker: 0.1.13 dev: false + /superjson@1.12.4: + resolution: {integrity: sha512-vkpPQAxdCg9SLfPv5GPC5fnGrui/WryktoN9O5+Zif/14QIMjw+RITf/5LbBh+9QpBFb3KNvJth+puz2H8o6GQ==} + engines: {node: '>=10'} + dependencies: + copy-anything: 3.0.5 + dev: false + /supports-color@5.5.0: resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} engines: {node: '>=4'} @@ -4702,6 +4876,11 @@ packages: engines: {node: '>=14.0.0'} dev: false + /ts-deepmerge@6.2.0: + resolution: {integrity: sha512-2qxI/FZVDPbzh63GwWIZYE7daWKtwXZYuyc8YNq0iTmMUwn4mL0jRLsp6hfFlgbdRSR4x2ppe+E86FnvEpN7Nw==} + engines: {node: '>=14.13.1'} + dev: false + /ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} dev: false @@ -4867,6 +5046,14 @@ packages: dependencies: punycode: 2.3.0 + /use-sync-external-store@1.2.0(react@18.2.0): + resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + dependencies: + react: 18.2.0 + dev: false + /uvu@0.5.6: resolution: {integrity: sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==} engines: {node: '>=8'} @@ -4967,7 +5154,7 @@ packages: resolve: 1.22.2 rollup: 3.21.7 optionalDependencies: - fsevents: 2.3.2 + fsevents: 2.3.3 dev: false /vite@4.3.8: @@ -4999,7 +5186,7 @@ packages: postcss: 8.4.23 rollup: 3.21.7 optionalDependencies: - fsevents: 2.3.2 + fsevents: 2.3.3 dev: false /web-namespaces@2.0.1: @@ -5075,6 +5262,11 @@ packages: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} dev: false + /yaml@2.3.1: + resolution: {integrity: sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==} + engines: {node: '>= 14'} + dev: false + /ylru@1.3.2: resolution: {integrity: sha512-RXRJzMiK6U2ye0BlGGZnmpwJDPgakn6aNQ0A7gHRbD4I0uvK4TW6UqkK1V0pp9jskjJBAXd3dRrbzWkqJ+6cxA==} engines: {node: '>= 4.0.0'} @@ -5084,6 +5276,22 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} + /zod@3.22.2: + resolution: {integrity: sha512-wvWkphh5WQsJbVk1tbx1l1Ly4yg+XecD+Mq280uBGt9wa5BKSWf4Mhp6GmrkPixhMxmabYY7RbzlwVP32pbGCg==} + dev: false + /zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} dev: false + + '@github.com/jiphyeonjeon-42/backend/releases/download/v0.0.11-alpha/contracts.tgz(zod@3.22.2)': + resolution: {tarball: https://github.com/jiphyeonjeon-42/backend/releases/download/v0.0.11-alpha/contracts.tgz} + id: '@github.com/jiphyeonjeon-42/backend/releases/download/v0.0.11-alpha/contracts.tgz' + name: '@jiphyeonjeon-42/contracts' + version: 0.0.11-alpha + dependencies: + '@anatine/zod-openapi': 2.2.0(openapi3-ts@4.1.2)(zod@3.22.2) + openapi3-ts: 4.1.2 + transitivePeerDependencies: + - zod + dev: false diff --git a/src/api/reviews/useGetReviews.ts b/src/api/reviews/useGetReviews.ts index e2fd1ddf..af48911a 100644 --- a/src/api/reviews/useGetReviews.ts +++ b/src/api/reviews/useGetReviews.ts @@ -1,52 +1,40 @@ -import { useState, useEffect } from "react"; -import { useApi } from "../../hook/useApi"; -import { Review } from "../../type"; +import type { contract } from "@jiphyeonjeon-42/contracts"; +import type { ClientInferRequest } from "@ts-rest/core"; +import { useState } from "react"; +import { client } from "~/util/tsRestClient"; -export const useGetReviews = () => { - const [params, setParams] = useState({ - titleOrNickname: "", - page: 1, - disabled: "-1", - }); - - const [result, setResult] = useState({ - reviewList: [], - lastPage: 5, - }); +type QueryArgs = ClientInferRequest["query"]; - const setPage = (page: number) => { - setParams({ ...params, page }); - }; - const setQuery = (query: string) => { - setParams({ ...params, titleOrNickname: query }); - }; - const setSelectedType = (type: string | undefined) => { - if (type) setParams({ ...params, disabled: type }); - }; - - const { request } = useApi("get", "reviews", { - ...params, - page: params.page - 1, - }); - - const refineResponse = (response: any) => { - const { items } = response.data; - const { totalPages } = response.data.meta; - - setResult({ reviewList: items, lastPage: totalPages }); +export const useGetReviews = () => { + const [search, setSearch] = useState(); + const [page, setPage] = useState(1); + const [visibility, setVisibility] = useState<"public" | "hidden" | "all">( + "all", + ); + + const queryArgs: QueryArgs = { + search, + page, + visibility, + perPage: 10, + sort: "desc", }; - useEffect(() => { - request(refineResponse); - }, [params]); + const query = client.reviews.get.useQuery( + ["reviews", queryArgs], + { query: queryArgs }, + { + keepPreviousData: true, + staleTime: 1000 * 60 * 60, + }, + ); return { - page: params.page, + page, setPage, - setQuery, - selectedType: params.disabled, - setSelectedType, - reviewList: result.reviewList as Review[], - lastPage: result.lastPage, + setSearch, + visibility, + setVisibility, + data: query.data?.body, }; }; diff --git a/src/api/reviews/usePatchReviewsId.ts b/src/api/reviews/usePatchReviewsId.ts index d3927f72..f9ccc3b0 100644 --- a/src/api/reviews/usePatchReviewsId.ts +++ b/src/api/reviews/usePatchReviewsId.ts @@ -1,22 +1,26 @@ -import { useEffect, useState } from "react"; -import { useApi } from "../../hook/useApi"; +import { queryClient } from "~/index"; import { useNewDialog } from "../../hook/useNewDialog"; +import { client } from "../../util/tsRestClient"; export const usePatchReviewsId = () => { - const [reviewId, setReviewId] = useState(); - const { request } = useApi("patch", `reviews/${reviewId}`); + const { addErrorDialog, addDialogWithTitleAndMessage } = useNewDialog(); - const { addDialogWithTitleAndMessage } = useNewDialog(); - const onSuccess = () => { - addDialogWithTitleAndMessage("patched", "처리되었습니다", "", () => - window.location.reload(), - ); - }; - - useEffect(() => { - if (reviewId !== undefined) { - request(onSuccess); - } - }, [reviewId]); - return { setReviewId }; + const mutation = client.reviews.patch.useMutation({ + onSuccess: () => queryClient.invalidateQueries(["reviews"]), + onError: err => { + switch (err.status) { + case 401: + return addErrorDialog({ response: { data: err.body } }); + case 404: + return addDialogWithTitleAndMessage( + err.body.toString(), + "리뷰 검색 실패", + "검색한 리뷰가 존재하지 않습니다", + ); + default: + return addErrorDialog(err); + } + }, + }); + return mutation; }; diff --git a/src/component/reviewManagement/ReviewManagement.tsx b/src/component/reviewManagement/ReviewManagement.tsx index 617da4a9..dd67fcef 100644 --- a/src/component/reviewManagement/ReviewManagement.tsx +++ b/src/component/reviewManagement/ReviewManagement.tsx @@ -1,29 +1,24 @@ import { useGetReviews } from "../../api/reviews/useGetReviews"; import { otherManagementTabList } from "../../constant/tablist"; import Banner from "../utils/Banner"; -import Tabs from "../utils/Tabs"; +import Filter from "../utils/Filter"; import Management from "../utils/Management"; +import Tabs from "../utils/Tabs"; import ReviewManagementListItem from "./ReviewManagementListItem"; -import Filter from "../utils/Filter"; + +type Visibility = "all" | "public" | "hidden"; const reviewFilterList = [ - { name: "공개만 보기", type: "0" }, - { name: "비공개만 보기", type: "1" }, -]; + { name: "공개만 보기", type: "public" }, + { name: "비공개만 보기", type: "hidden" }, +] satisfies { name: string; type: Visibility }[]; const ReviewManagement = () => { - const { - page, - setPage, - setQuery, - selectedType, - setSelectedType, - reviewList, - lastPage, - } = useGetReviews(); + const { page, setPage, data, visibility, setVisibility, setSearch } = + useGetReviews(); - const setUndefinedReSelected = (newType: string) => { - setSelectedType(newType === selectedType ? "-1" : newType); + const setVisiblityWithUnset = (value: "public" | "hidden") => { + setVisibility(visibility === value ? "all" : value); }; return ( @@ -32,7 +27,7 @@ const ReviewManagement = () => { ID @@ -46,17 +41,17 @@ const ReviewManagement = () => { <> void} /> - {reviewList.map(review => ( + {data?.items.map(review => ( ))} } page={page} setPage={setPage} - lastPage={lastPage} + lastPage={data?.meta.totalPages ?? 1} /> ); diff --git a/src/component/reviewManagement/ReviewManagementListItem.tsx b/src/component/reviewManagement/ReviewManagementListItem.tsx index 726932da..f81257fe 100644 --- a/src/component/reviewManagement/ReviewManagementListItem.tsx +++ b/src/component/reviewManagement/ReviewManagementListItem.tsx @@ -1,42 +1,42 @@ import { MouseEventHandler } from "react"; import { usePatchReviewsId } from "../../api/reviews/usePatchReviewsId"; -import { Review } from "../../type"; -import Edit from "../../asset/img/edit.svg"; -import Image from "../utils/Image"; import "../../asset/css/ReviewManagementList.css"; +import Edit from "../../asset/img/edit.svg"; import { useNewDialog } from "../../hook/useNewDialog"; +import Image from "../utils/Image"; + +import { contract } from "@jiphyeonjeon-42/contracts"; +import type { ClientInferResponseBody } from "@ts-rest/core"; + +type Review = ClientInferResponseBody< + typeof contract.reviews.get, + 200 +>["items"][number]; type Props = { review: Review; }; const ReviewManagementListItem = ({ review }: Props) => { - const { setReviewId } = usePatchReviewsId(); + const mutation = usePatchReviewsId(); const { addConfirmDialog } = useNewDialog(); - const requestConfirmToUpdate: MouseEventHandler = e => { + const requestConfirmToUpdate: MouseEventHandler = () => { const job = review.disabled ? "공개" : "비공개"; addConfirmDialog( "리뷰확인", `리뷰를 ${job}하시겠습니까?`, `[${review.title}]\n\n리뷰내용 : ${review.content}`, - () => { - setReviewId(review.reviewsId); - }, + () => mutation.mutate({ params: { reviewsId: review.id } }), ); }; return ( -
- {review.reviewsId} +
+ {review.id} - - {review.createdAt.slice(0, 5)} - - - {review.createdAt.slice(5, 10)} - + {review.createdAt.split("T")[0]} {review.nickname} diff --git a/src/index.tsx b/src/index.tsx index 7628acc5..53e22792 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -5,6 +5,8 @@ import * as Sentry from "@sentry/react"; import { BrowserTracing } from "@sentry/tracing"; import App from "./App"; import "./index.css"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; Sentry.init({ dsn: import.meta.env.REACT_APP_SENTRY, @@ -16,10 +18,15 @@ Sentry.init({ : "production", }); +export const queryClient = new QueryClient(); + createRoot(document.getElementById("root")!).render( - - - + + + + + + , ); diff --git a/src/util/tsRestClient.ts b/src/util/tsRestClient.ts new file mode 100644 index 00000000..1d9389da --- /dev/null +++ b/src/util/tsRestClient.ts @@ -0,0 +1,39 @@ +import { initQueryClient } from "@ts-rest/react-query"; +import { contract } from "@jiphyeonjeon-42/contracts"; + +/** + * {@link https://tanstack.com/query/latest/docs/react/overview | @tanstack/react-query }의 쿼리들을 + * {@link https://ts-rest.com/docs/react-query | ts-rest}로 타입 안전하게 감싼 객체입니다. + * + * API 요청 경로를 지정한 후 + * react-query 라이브러리의 {@link https://tanstack.com/query/latest/docs/react/guides/queries | useQuery }및 + * {@link https://tanstack.com/query/latest/docs/react/guides/mutations | useMutation }과 동일하게 사용 가능합니다. + * + * @example + * ```ts + * const mutation = client.reviews.patch.useMutation({ + * onSuccess: ({ body }) => // ... + * + * onError: error => { + * switch (error.status) { + * case 401: + * return console.log(`에러코드: ${error.body.errorCode}`) + * case 404: + * return console.log(error.body.message) + * // message의 타입: "검색한 리뷰가 존재하지 않습니다." + * } + * }, + * }) + * ``` + * + * @remarks 아래 링크를 참고하여 쿼리를 사용할 수 있습니다. + * + * @see https://tanstack.com/query/latest/docs/react/overview + * @see https://ts-rest.com/docs/react-query + */ +export const client = initQueryClient(contract, { + baseUrl: new URL(import.meta.env.REACT_APP_API).origin, + baseHeaders: {}, + credentials: "include", + jsonQuery: true, +});