diff --git a/index.html b/index.html index f89d6d351..04e761b48 100644 --- a/index.html +++ b/index.html @@ -9,6 +9,6 @@
- + diff --git a/package.json b/package.json index fcb2757eb..dfdbc0cf8 100644 --- a/package.json +++ b/package.json @@ -1,19 +1,23 @@ { - "name": "front-end-2-3", + "homepage": "https://suinkimme.github.io/front_5th_chapter2-3/", + "name": "front-5th-chapter2-3", "private": true, "version": "0.0.0", "type": "module", "scripts": { "dev": "vite", "build": "tsc -b && vite build", + "deploy": "pnpm run build && gh-pages -d dist", "lint": "eslint .", "preview": "vite preview", "test": "vitest", "coverage": "vitest run --coverage" }, "dependencies": { + "@tanstack/react-query": "^5.74.7", "react": "^19.1.0", - "react-dom": "^19.1.0" + "react-dom": "^19.1.0", + "zustand": "^5.0.3" }, "devDependencies": { "@eslint/js": "^9.25.1", @@ -22,6 +26,7 @@ "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.3.0", "@testing-library/user-event": "^14.6.1", + "@types/node": "^22.15.2", "@types/react": "^19.1.2", "@types/react-dom": "^19.1.2", "@vitejs/plugin-react": "^4.4.1", @@ -34,12 +39,19 @@ "jsdom": "^26.1.0", "lucide-react": "^0.503.0", "msw": "^2.7.5", + "gh-pages": "^6.3.0", "prettier": "^3.5.3", "react-router-dom": "^7.5.2", "typescript": "~5.8.3", "typescript-eslint": "^8.31.0", "vite": "^6.3.3", + "vite-tsconfig-paths": "^5.1.4", "vitest": "^3.1.2", "vitest-browser-react": "^0.1.1" + }, + "msw": { + "workerDirectory": [ + "public" + ] } -} +} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e1c5650db..4efb09f58 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,12 +8,18 @@ importers: .: dependencies: + '@tanstack/react-query': + specifier: ^5.74.7 + version: 5.74.7(react@19.1.0) react: specifier: ^19.1.0 version: 19.1.0 react-dom: specifier: ^19.1.0 version: 19.1.0(react@19.1.0) + zustand: + specifier: ^5.0.3 + version: 5.0.3(@types/react@19.1.2)(react@19.1.0) devDependencies: '@eslint/js': specifier: ^9.25.1 @@ -33,6 +39,9 @@ importers: '@testing-library/user-event': specifier: ^14.6.1 version: 14.6.1(@testing-library/dom@10.4.0) + '@types/node': + specifier: ^22.15.2 + version: 22.15.2 '@types/react': specifier: ^19.1.2 version: 19.1.2 @@ -41,7 +50,7 @@ importers: version: 19.1.2(@types/react@19.1.2) '@vitejs/plugin-react': specifier: ^4.4.1 - version: 4.4.1(vite@6.3.3(@types/node@22.8.1)) + version: 4.4.1(vite@6.3.3(@types/node@22.15.2)) axios: specifier: ^1.9.0 version: 1.9.0 @@ -57,6 +66,9 @@ importers: eslint-plugin-react-refresh: specifier: ^0.4.20 version: 0.4.20(eslint@9.25.1) + gh-pages: + specifier: ^6.3.0 + version: 6.3.0 globals: specifier: ^16.0.0 version: 16.0.0 @@ -68,7 +80,7 @@ importers: version: 0.503.0(react@19.1.0) msw: specifier: ^2.7.5 - version: 2.7.5(@types/node@22.8.1)(typescript@5.8.3) + version: 2.7.5(@types/node@22.15.2)(typescript@5.8.3) prettier: specifier: ^3.5.3 version: 3.5.3 @@ -83,10 +95,13 @@ importers: version: 8.31.0(eslint@9.25.1)(typescript@5.8.3) vite: specifier: ^6.3.3 - version: 6.3.3(@types/node@22.8.1) + version: 6.3.3(@types/node@22.15.2) + vite-tsconfig-paths: + specifier: ^5.1.4 + version: 5.1.4(typescript@5.8.3)(vite@6.3.3(@types/node@22.15.2)) vitest: specifier: ^3.1.2 - version: 3.1.2(@types/node@22.8.1)(@vitest/browser@2.1.3)(jsdom@26.1.0)(msw@2.7.5(@types/node@22.8.1)(typescript@5.8.3)) + version: 3.1.2(@types/node@22.15.2)(@vitest/browser@2.1.3)(jsdom@26.1.0)(msw@2.7.5(@types/node@22.15.2)(typescript@5.8.3)) vitest-browser-react: specifier: ^0.1.1 version: 0.1.1(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(@vitest/browser@2.1.3)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(vitest@3.1.2) @@ -899,6 +914,14 @@ packages: cpu: [x64] os: [win32] + '@tanstack/query-core@5.74.7': + resolution: {integrity: sha512-X3StkN/Y6KGHndTjJf8H8th7AX4bKfbRpiVhVqevf0QWlxl6DhyJ0TYG3R0LARa/+xqDwzU9mA4pbJxzPCI29A==} + + '@tanstack/react-query@5.74.7': + resolution: {integrity: sha512-u4o/RIWnnrq26orGZu2NDPwmVof1vtAiiV6KYUXd49GuK+8HX+gyxoAYqIaZogvCE1cqOuZAhQKcrKGYGkrLxg==} + peerDependencies: + react: ^18 || ^19 + '@testing-library/dom@10.4.0': resolution: {integrity: sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==} engines: {node: '>=18'} @@ -955,8 +978,8 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - '@types/node@22.8.1': - resolution: {integrity: sha512-k6Gi8Yyo8EtrNtkHXutUu2corfDf9su95VYVP10aGYMMROM6SAItZi0w1XszA6RtWTHSVp5OeFof37w0IEqCQg==} + '@types/node@22.15.2': + resolution: {integrity: sha512-uKXqKN9beGoMdBfcaTY1ecwz6ctxuJAcUlwE55938g0ZJ8lRxwAZqRz2AJ4pzpt5dHdTPMB863UZ0ESiFUcP7A==} '@types/react-dom@19.1.2': resolution: {integrity: sha512-XGJkWF41Qq305SKWEILa1O8vzhb3aOo3ogBlSmiqNko/WmRb6QIaweuZCXjKygVDXpzXb5wyxKTSOsmkuqj+Qw==} @@ -1130,10 +1153,17 @@ packages: aria-query@5.3.0: resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} + array-union@2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} + assertion-error@2.0.1: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} + async@3.2.6: + resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} + asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} @@ -1211,6 +1241,13 @@ packages: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} engines: {node: '>= 0.8'} + commander@13.1.0: + resolution: {integrity: sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==} + engines: {node: '>=18'} + + commondir@1.0.1: + resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} + concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} @@ -1273,6 +1310,10 @@ packages: detect-node-es@1.1.0: resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} + dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + dom-accessibility-api@0.5.16: resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} @@ -1282,6 +1323,9 @@ packages: electron-to-chromium@1.5.45: resolution: {integrity: sha512-vOzZS6uZwhhbkZbcRyiy99Wg+pYFV5hk+5YaECvx0+Z31NR3Tt5zS6dze2OepT6PCTzVzT0dIJItti+uAW5zmw==} + email-addresses@5.0.0: + resolution: {integrity: sha512-4OIPYlA6JXqtVn8zpHpGiI7vE6EQOAg16aGnDMIAlZVinnoZ8208tW1hAbjWydgN/4PLTT9q+O1K6AH/vALJGw==} + emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -1301,6 +1345,10 @@ packages: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} + escape-string-regexp@1.0.5: + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + engines: {node: '>=0.8.0'} + escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} @@ -1393,10 +1441,26 @@ packages: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} engines: {node: '>=16.0.0'} + filename-reserved-regex@2.0.0: + resolution: {integrity: sha512-lc1bnsSr4L4Bdif8Xb/qrtokGbq5zlsms/CYH8PP+WtCkGNF65DPiQY8vG3SakEdRn8Dlnm+gW/qWKKjS5sZzQ==} + engines: {node: '>=4'} + + filenamify@4.3.0: + resolution: {integrity: sha512-hcFKyUG57yWGAzu1CMt/dPzYZuv+jAJUT85bL8mrXvNe6hWj6yEHEc4EdcgiA6Z3oi1/9wXJdZPXF2dZNgwgOg==} + engines: {node: '>=8'} + fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} + find-cache-dir@3.3.2: + resolution: {integrity: sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==} + engines: {node: '>=8'} + + find-up@4.1.0: + resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} + engines: {node: '>=8'} + find-up@5.0.0: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} @@ -1421,6 +1485,10 @@ packages: resolution: {integrity: sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==} engines: {node: '>= 6'} + fs-extra@11.3.0: + resolution: {integrity: sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==} + engines: {node: '>=14.14'} + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -1438,6 +1506,11 @@ packages: resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} engines: {node: '>=6'} + gh-pages@6.3.0: + resolution: {integrity: sha512-Ot5lU6jK0Eb+sszG8pciXdjMXdBJ5wODvgjR+imihTqsUWF2K6dJ9HST55lgqcs8wWcw6o6wAsUzfcYRhJPXbA==} + engines: {node: '>=10'} + hasBin: true + glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -1458,6 +1531,16 @@ packages: resolution: {integrity: sha512-iInW14XItCXET01CQFqudPOWP2jYMl7T+QRQT+UNcR/iQncN/F0UNpgd76iFkBPgNQb4+X3LV9tLJYzwh+Gl3A==} engines: {node: '>=18'} + globby@11.1.0: + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} + engines: {node: '>=10'} + + globrex@0.1.2: + resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + graphemer@1.4.0: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} @@ -1564,6 +1647,9 @@ packages: engines: {node: '>=6'} hasBin: true + jsonfile@6.1.0: + resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} + keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} @@ -1571,6 +1657,10 @@ packages: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} + locate-path@5.0.0: + resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} + engines: {node: '>=8'} + locate-path@6.0.0: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} @@ -1605,6 +1695,10 @@ packages: magic-string@0.30.17: resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} + make-dir@3.1.0: + resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} + engines: {node: '>=8'} + merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} @@ -1674,14 +1768,26 @@ packages: outvariant@1.4.3: resolution: {integrity: sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==} + p-limit@2.3.0: + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + engines: {node: '>=6'} + p-limit@3.1.0: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} engines: {node: '>=10'} + p-locate@4.1.0: + resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} + engines: {node: '>=8'} + p-locate@5.0.0: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} + p-try@2.2.0: + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + engines: {node: '>=6'} + parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} @@ -1700,6 +1806,10 @@ packages: path-to-regexp@6.3.0: resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==} + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} @@ -1718,6 +1828,10 @@ packages: resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} engines: {node: '>=12'} + pkg-dir@4.2.0: + resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} + engines: {node: '>=8'} + postcss@8.5.3: resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==} engines: {node: ^10 || ^12 || >=14} @@ -1888,6 +2002,10 @@ packages: resolution: {integrity: sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==} engines: {node: '>= 10'} + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -1921,6 +2039,10 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} + strip-outer@1.0.1: + resolution: {integrity: sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==} + engines: {node: '>=0.10.0'} + supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -1981,12 +2103,26 @@ packages: resolution: {integrity: sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==} engines: {node: '>=18'} + trim-repeated@1.0.0: + resolution: {integrity: sha512-pkonvlKk8/ZuR0D5tLW8ljt5I8kmxp2XKymhepUeOdCEfKpZaktSArkLHZt76OB1ZvO9bssUsDty4SWhLvZpLg==} + engines: {node: '>=0.10.0'} + ts-api-utils@2.1.0: resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} engines: {node: '>=18.12'} peerDependencies: typescript: '>=4.8.4' + tsconfck@3.1.5: + resolution: {integrity: sha512-CLDfGgUp7XPswWnezWwsCRxNmgQjhYq3VXHM0/XIRxhVrKw0M1if9agzryh1QS3nxjCROvV+xWxoJO1YctzzWg==} + engines: {node: ^18 || >=20} + hasBin: true + peerDependencies: + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true + tslib@2.8.0: resolution: {integrity: sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==} @@ -2017,13 +2153,17 @@ packages: engines: {node: '>=14.17'} hasBin: true - undici-types@6.19.8: - resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} universalify@0.2.0: resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==} engines: {node: '>= 4.0.0'} + universalify@2.0.1: + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} + engines: {node: '>= 10.0.0'} + update-browserslist-db@1.1.1: resolution: {integrity: sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==} hasBin: true @@ -2061,6 +2201,14 @@ packages: engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true + vite-tsconfig-paths@5.1.4: + resolution: {integrity: sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w==} + peerDependencies: + vite: '*' + peerDependenciesMeta: + vite: + optional: true + vite@6.3.3: resolution: {integrity: sha512-5nXH+QsELbFKhsEfWLkHrvgRpTdGJzqOZ+utSdmPTvwHmvU6ITTm3xx+mRusihkcI8GeC7lCDyn3kDtiki9scw==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} @@ -2229,6 +2377,24 @@ packages: resolution: {integrity: sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==} engines: {node: '>=18'} + zustand@5.0.3: + resolution: {integrity: sha512-14fwWQtU3pH4dE0dOpdMiWjddcH+QzKIgk1cl8epwSE7yag43k/AD/m4L6+K7DytAOr9gGBe3/EXj9g7cdostg==} + engines: {node: '>=12.20.0'} + peerDependencies: + '@types/react': '>=18.0.0' + immer: '>=9.0.6' + react: '>=18.0.0' + use-sync-external-store: '>=1.2.0' + peerDependenciesMeta: + '@types/react': + optional: true + immer: + optional: true + react: + optional: true + use-sync-external-store: + optional: true + snapshots: '@adobe/css-tools@4.4.0': {} @@ -2551,16 +2717,16 @@ snapshots: '@humanwhocodes/retry@0.4.2': {} - '@inquirer/confirm@5.0.1(@types/node@22.8.1)': + '@inquirer/confirm@5.0.1(@types/node@22.15.2)': dependencies: - '@inquirer/core': 10.0.1(@types/node@22.8.1) - '@inquirer/type': 3.0.0(@types/node@22.8.1) - '@types/node': 22.8.1 + '@inquirer/core': 10.0.1(@types/node@22.15.2) + '@inquirer/type': 3.0.0(@types/node@22.15.2) + '@types/node': 22.15.2 - '@inquirer/core@10.0.1(@types/node@22.8.1)': + '@inquirer/core@10.0.1(@types/node@22.15.2)': dependencies: '@inquirer/figures': 1.0.7 - '@inquirer/type': 3.0.0(@types/node@22.8.1) + '@inquirer/type': 3.0.0(@types/node@22.15.2) ansi-escapes: 4.3.2 cli-width: 4.1.0 mute-stream: 2.0.0 @@ -2573,9 +2739,9 @@ snapshots: '@inquirer/figures@1.0.7': {} - '@inquirer/type@3.0.0(@types/node@22.8.1)': + '@inquirer/type@3.0.0(@types/node@22.15.2)': dependencies: - '@types/node': 22.8.1 + '@types/node': 22.15.2 '@jridgewell/gen-mapping@0.3.5': dependencies: @@ -2936,6 +3102,13 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.40.0': optional: true + '@tanstack/query-core@5.74.7': {} + + '@tanstack/react-query@5.74.7(react@19.1.0)': + dependencies: + '@tanstack/query-core': 5.74.7 + react: 19.1.0 + '@testing-library/dom@10.4.0': dependencies: '@babel/code-frame': 7.26.2 @@ -3002,9 +3175,9 @@ snapshots: '@types/json-schema@7.0.15': {} - '@types/node@22.8.1': + '@types/node@22.15.2': dependencies: - undici-types: 6.19.8 + undici-types: 6.21.0 '@types/react-dom@19.1.2(@types/react@19.1.2)': dependencies: @@ -3095,28 +3268,28 @@ snapshots: '@typescript-eslint/types': 8.31.0 eslint-visitor-keys: 4.2.0 - '@vitejs/plugin-react@4.4.1(vite@6.3.3(@types/node@22.8.1))': + '@vitejs/plugin-react@4.4.1(vite@6.3.3(@types/node@22.15.2))': dependencies: '@babel/core': 7.26.10 '@babel/plugin-transform-react-jsx-self': 7.25.9(@babel/core@7.26.10) '@babel/plugin-transform-react-jsx-source': 7.25.9(@babel/core@7.26.10) '@types/babel__core': 7.20.5 react-refresh: 0.17.0 - vite: 6.3.3(@types/node@22.8.1) + vite: 6.3.3(@types/node@22.15.2) transitivePeerDependencies: - supports-color - '@vitest/browser@2.1.3(@types/node@22.8.1)(@vitest/spy@3.1.2)(typescript@5.8.3)(vite@6.3.3(@types/node@22.8.1))(vitest@3.1.2)': + '@vitest/browser@2.1.3(@types/node@22.15.2)(@vitest/spy@3.1.2)(typescript@5.8.3)(vite@6.3.3(@types/node@22.15.2))(vitest@3.1.2)': dependencies: '@testing-library/dom': 10.4.0 '@testing-library/user-event': 14.6.1(@testing-library/dom@10.4.0) - '@vitest/mocker': 2.1.3(@vitest/spy@3.1.2)(msw@2.7.5(@types/node@22.8.1)(typescript@5.8.3))(vite@6.3.3(@types/node@22.8.1)) + '@vitest/mocker': 2.1.3(@vitest/spy@3.1.2)(msw@2.7.5(@types/node@22.15.2)(typescript@5.8.3))(vite@6.3.3(@types/node@22.15.2)) '@vitest/utils': 2.1.3 magic-string: 0.30.17 - msw: 2.7.5(@types/node@22.8.1)(typescript@5.8.3) + msw: 2.7.5(@types/node@22.15.2)(typescript@5.8.3) sirv: 2.0.4 tinyrainbow: 1.2.0 - vitest: 3.1.2(@types/node@22.8.1)(@vitest/browser@2.1.3)(jsdom@26.1.0)(msw@2.7.5(@types/node@22.8.1)(typescript@5.8.3)) + vitest: 3.1.2(@types/node@22.15.2)(@vitest/browser@2.1.3)(jsdom@26.1.0)(msw@2.7.5(@types/node@22.15.2)(typescript@5.8.3)) ws: 8.18.0 transitivePeerDependencies: - '@types/node' @@ -3133,23 +3306,23 @@ snapshots: chai: 5.2.0 tinyrainbow: 2.0.0 - '@vitest/mocker@2.1.3(@vitest/spy@3.1.2)(msw@2.7.5(@types/node@22.8.1)(typescript@5.8.3))(vite@6.3.3(@types/node@22.8.1))': + '@vitest/mocker@2.1.3(@vitest/spy@3.1.2)(msw@2.7.5(@types/node@22.15.2)(typescript@5.8.3))(vite@6.3.3(@types/node@22.15.2))': dependencies: '@vitest/spy': 3.1.2 estree-walker: 3.0.3 magic-string: 0.30.17 optionalDependencies: - msw: 2.7.5(@types/node@22.8.1)(typescript@5.8.3) - vite: 6.3.3(@types/node@22.8.1) + msw: 2.7.5(@types/node@22.15.2)(typescript@5.8.3) + vite: 6.3.3(@types/node@22.15.2) - '@vitest/mocker@3.1.2(msw@2.7.5(@types/node@22.8.1)(typescript@5.8.3))(vite@6.3.3(@types/node@22.8.1))': + '@vitest/mocker@3.1.2(msw@2.7.5(@types/node@22.15.2)(typescript@5.8.3))(vite@6.3.3(@types/node@22.15.2))': dependencies: '@vitest/spy': 3.1.2 estree-walker: 3.0.3 magic-string: 0.30.17 optionalDependencies: - msw: 2.7.5(@types/node@22.8.1)(typescript@5.8.3) - vite: 6.3.3(@types/node@22.8.1) + msw: 2.7.5(@types/node@22.15.2)(typescript@5.8.3) + vite: 6.3.3(@types/node@22.15.2) '@vitest/pretty-format@2.1.3': dependencies: @@ -3177,7 +3350,7 @@ snapshots: '@vitest/utils@2.1.3': dependencies: '@vitest/pretty-format': 2.1.3 - loupe: 3.1.2 + loupe: 3.1.3 tinyrainbow: 1.2.0 '@vitest/utils@3.1.2': @@ -3223,8 +3396,12 @@ snapshots: dependencies: dequal: 2.0.3 + array-union@2.1.0: {} + assertion-error@2.0.1: {} + async@3.2.6: {} + asynckit@0.4.0: {} axios@1.9.0: @@ -3307,6 +3484,10 @@ snapshots: dependencies: delayed-stream: 1.0.0 + commander@13.1.0: {} + + commondir@1.0.1: {} + concat-map@0.0.1: {} convert-source-map@2.0.0: {} @@ -3351,12 +3532,18 @@ snapshots: detect-node-es@1.1.0: {} + dir-glob@3.0.1: + dependencies: + path-type: 4.0.0 + dom-accessibility-api@0.5.16: {} dom-accessibility-api@0.6.3: {} electron-to-chromium@1.5.45: {} + email-addresses@5.0.0: {} + emoji-regex@8.0.0: {} entities@4.5.0: {} @@ -3393,6 +3580,8 @@ snapshots: escalade@3.2.0: {} + escape-string-regexp@1.0.5: {} + escape-string-regexp@4.0.0: {} eslint-plugin-react-hooks@5.2.0(eslint@9.25.1): @@ -3502,10 +3691,29 @@ snapshots: dependencies: flat-cache: 4.0.1 + filename-reserved-regex@2.0.0: {} + + filenamify@4.3.0: + dependencies: + filename-reserved-regex: 2.0.0 + strip-outer: 1.0.1 + trim-repeated: 1.0.0 + fill-range@7.1.1: dependencies: to-regex-range: 5.0.1 + find-cache-dir@3.3.2: + dependencies: + commondir: 1.0.1 + make-dir: 3.1.0 + pkg-dir: 4.2.0 + + find-up@4.1.0: + dependencies: + locate-path: 5.0.0 + path-exists: 4.0.0 + find-up@5.0.0: dependencies: locate-path: 6.0.0 @@ -3526,6 +3734,12 @@ snapshots: combined-stream: 1.0.8 mime-types: 2.1.35 + fs-extra@11.3.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.1.0 + universalify: 2.0.1 + fsevents@2.3.3: optional: true @@ -3535,6 +3749,16 @@ snapshots: get-nonce@1.0.1: {} + gh-pages@6.3.0: + dependencies: + async: 3.2.6 + commander: 13.1.0 + email-addresses: 5.0.0 + filenamify: 4.3.0 + find-cache-dir: 3.3.2 + fs-extra: 11.3.0 + globby: 11.1.0 + glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -3549,6 +3773,19 @@ snapshots: globals@16.0.0: {} + globby@11.1.0: + dependencies: + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.3.2 + ignore: 5.3.2 + merge2: 1.4.1 + slash: 3.0.0 + + globrex@0.1.2: {} + + graceful-fs@4.2.11: {} + graphemer@1.4.0: {} graphql@16.9.0: {} @@ -3649,6 +3886,12 @@ snapshots: json5@2.2.3: {} + jsonfile@6.1.0: + dependencies: + universalify: 2.0.1 + optionalDependencies: + graceful-fs: 4.2.11 + keyv@4.5.4: dependencies: json-buffer: 3.0.1 @@ -3658,6 +3901,10 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 + locate-path@5.0.0: + dependencies: + p-locate: 4.1.0 + locate-path@6.0.0: dependencies: p-locate: 5.0.0 @@ -3686,6 +3933,10 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.0 + make-dir@3.1.0: + dependencies: + semver: 6.3.1 + merge2@1.4.1: {} micromatch@4.0.8: @@ -3713,12 +3964,12 @@ snapshots: ms@2.1.3: {} - msw@2.7.5(@types/node@22.8.1)(typescript@5.8.3): + msw@2.7.5(@types/node@22.15.2)(typescript@5.8.3): dependencies: '@bundled-es-modules/cookie': 2.0.1 '@bundled-es-modules/statuses': 1.0.1 '@bundled-es-modules/tough-cookie': 0.1.6 - '@inquirer/confirm': 5.0.1(@types/node@22.8.1) + '@inquirer/confirm': 5.0.1(@types/node@22.15.2) '@mswjs/interceptors': 0.37.5 '@open-draft/deferred-promise': 2.2.0 '@open-draft/until': 2.1.0 @@ -3759,14 +4010,24 @@ snapshots: outvariant@1.4.3: {} + p-limit@2.3.0: + dependencies: + p-try: 2.2.0 + p-limit@3.1.0: dependencies: yocto-queue: 0.1.0 + p-locate@4.1.0: + dependencies: + p-limit: 2.3.0 + p-locate@5.0.0: dependencies: p-limit: 3.1.0 + p-try@2.2.0: {} + parent-module@1.0.1: dependencies: callsites: 3.1.0 @@ -3781,6 +4042,8 @@ snapshots: path-to-regexp@6.3.0: {} + path-type@4.0.0: {} + pathe@2.0.3: {} pathval@2.0.0: {} @@ -3791,6 +4054,10 @@ snapshots: picomatch@4.0.2: {} + pkg-dir@4.2.0: + dependencies: + find-up: 4.1.0 + postcss@8.5.3: dependencies: nanoid: 3.3.8 @@ -3947,6 +4214,8 @@ snapshots: mrmime: 2.0.0 totalist: 3.0.1 + slash@3.0.0: {} + source-map-js@1.2.1: {} stackback@0.0.2: {} @@ -3973,6 +4242,10 @@ snapshots: strip-json-comments@3.1.1: {} + strip-outer@1.0.1: + dependencies: + escape-string-regexp: 1.0.5 + supports-color@7.2.0: dependencies: has-flag: 4.0.0 @@ -4023,10 +4296,18 @@ snapshots: dependencies: punycode: 2.3.1 + trim-repeated@1.0.0: + dependencies: + escape-string-regexp: 1.0.5 + ts-api-utils@2.1.0(typescript@5.8.3): dependencies: typescript: 5.8.3 + tsconfck@3.1.5(typescript@5.8.3): + optionalDependencies: + typescript: 5.8.3 + tslib@2.8.0: {} turbo-stream@2.4.0: {} @@ -4051,10 +4332,12 @@ snapshots: typescript@5.8.3: {} - undici-types@6.19.8: {} + undici-types@6.21.0: {} universalify@0.2.0: {} + universalify@2.0.1: {} + update-browserslist-db@1.1.1(browserslist@4.24.2): dependencies: browserslist: 4.24.2 @@ -4085,13 +4368,13 @@ snapshots: optionalDependencies: '@types/react': 19.1.2 - vite-node@3.1.2(@types/node@22.8.1): + vite-node@3.1.2(@types/node@22.15.2): dependencies: cac: 6.7.14 debug: 4.4.0 es-module-lexer: 1.6.0 pathe: 2.0.3 - vite: 6.3.3(@types/node@22.8.1) + vite: 6.3.3(@types/node@22.15.2) transitivePeerDependencies: - '@types/node' - jiti @@ -4106,7 +4389,18 @@ snapshots: - tsx - yaml - vite@6.3.3(@types/node@22.8.1): + vite-tsconfig-paths@5.1.4(typescript@5.8.3)(vite@6.3.3(@types/node@22.15.2)): + dependencies: + debug: 4.4.0 + globrex: 0.1.2 + tsconfck: 3.1.5(typescript@5.8.3) + optionalDependencies: + vite: 6.3.3(@types/node@22.15.2) + transitivePeerDependencies: + - supports-color + - typescript + + vite@6.3.3(@types/node@22.15.2): dependencies: esbuild: 0.25.3 fdir: 6.4.4(picomatch@4.0.2) @@ -4115,23 +4409,23 @@ snapshots: rollup: 4.40.0 tinyglobby: 0.2.13 optionalDependencies: - '@types/node': 22.8.1 + '@types/node': 22.15.2 fsevents: 2.3.3 vitest-browser-react@0.1.1(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(@vitest/browser@2.1.3)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(vitest@3.1.2): dependencies: - '@vitest/browser': 2.1.3(@types/node@22.8.1)(@vitest/spy@3.1.2)(typescript@5.8.3)(vite@6.3.3(@types/node@22.8.1))(vitest@3.1.2) + '@vitest/browser': 2.1.3(@types/node@22.15.2)(@vitest/spy@3.1.2)(typescript@5.8.3)(vite@6.3.3(@types/node@22.15.2))(vitest@3.1.2) react: 19.1.0 react-dom: 19.1.0(react@19.1.0) - vitest: 3.1.2(@types/node@22.8.1)(@vitest/browser@2.1.3)(jsdom@26.1.0)(msw@2.7.5(@types/node@22.8.1)(typescript@5.8.3)) + vitest: 3.1.2(@types/node@22.15.2)(@vitest/browser@2.1.3)(jsdom@26.1.0)(msw@2.7.5(@types/node@22.15.2)(typescript@5.8.3)) optionalDependencies: '@types/react': 19.1.2 '@types/react-dom': 19.1.2(@types/react@19.1.2) - vitest@3.1.2(@types/node@22.8.1)(@vitest/browser@2.1.3)(jsdom@26.1.0)(msw@2.7.5(@types/node@22.8.1)(typescript@5.8.3)): + vitest@3.1.2(@types/node@22.15.2)(@vitest/browser@2.1.3)(jsdom@26.1.0)(msw@2.7.5(@types/node@22.15.2)(typescript@5.8.3)): dependencies: '@vitest/expect': 3.1.2 - '@vitest/mocker': 3.1.2(msw@2.7.5(@types/node@22.8.1)(typescript@5.8.3))(vite@6.3.3(@types/node@22.8.1)) + '@vitest/mocker': 3.1.2(msw@2.7.5(@types/node@22.15.2)(typescript@5.8.3))(vite@6.3.3(@types/node@22.15.2)) '@vitest/pretty-format': 3.1.2 '@vitest/runner': 3.1.2 '@vitest/snapshot': 3.1.2 @@ -4148,12 +4442,12 @@ snapshots: tinyglobby: 0.2.13 tinypool: 1.0.2 tinyrainbow: 2.0.0 - vite: 6.3.3(@types/node@22.8.1) - vite-node: 3.1.2(@types/node@22.8.1) + vite: 6.3.3(@types/node@22.15.2) + vite-node: 3.1.2(@types/node@22.15.2) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 22.8.1 - '@vitest/browser': 2.1.3(@types/node@22.8.1)(@vitest/spy@3.1.2)(typescript@5.8.3)(vite@6.3.3(@types/node@22.8.1))(vitest@3.1.2) + '@types/node': 22.15.2 + '@vitest/browser': 2.1.3(@types/node@22.15.2)(@vitest/spy@3.1.2)(typescript@5.8.3)(vite@6.3.3(@types/node@22.15.2))(vitest@3.1.2) jsdom: 26.1.0 transitivePeerDependencies: - jiti @@ -4234,3 +4528,8 @@ snapshots: yocto-queue@0.1.0: {} yoctocolors-cjs@2.1.2: {} + + zustand@5.0.3(@types/react@19.1.2)(react@19.1.0): + optionalDependencies: + '@types/react': 19.1.2 + react: 19.1.0 diff --git a/public/mockServiceWorker.js b/public/mockServiceWorker.js new file mode 100644 index 000000000..8b841baf2 --- /dev/null +++ b/public/mockServiceWorker.js @@ -0,0 +1,307 @@ +/* eslint-disable */ +/* tslint:disable */ + +/** + * Mock Service Worker. + * @see https://github.com/mswjs/msw + * - Please do NOT modify this file. + * - Please do NOT serve this file on production. + */ + +const PACKAGE_VERSION = '2.7.5' +const INTEGRITY_CHECKSUM = '00729d72e3b82faf54ca8b9621dbb96f' +const IS_MOCKED_RESPONSE = Symbol('isMockedResponse') +const activeClientIds = new Set() + +self.addEventListener('install', function () { + self.skipWaiting() +}) + +self.addEventListener('activate', function (event) { + event.waitUntil(self.clients.claim()) +}) + +self.addEventListener('message', async function (event) { + const clientId = event.source.id + + if (!clientId || !self.clients) { + return + } + + const client = await self.clients.get(clientId) + + if (!client) { + return + } + + const allClients = await self.clients.matchAll({ + type: 'window', + }) + + switch (event.data) { + case 'KEEPALIVE_REQUEST': { + sendToClient(client, { + type: 'KEEPALIVE_RESPONSE', + }) + break + } + + case 'INTEGRITY_CHECK_REQUEST': { + sendToClient(client, { + type: 'INTEGRITY_CHECK_RESPONSE', + payload: { + packageVersion: PACKAGE_VERSION, + checksum: INTEGRITY_CHECKSUM, + }, + }) + break + } + + case 'MOCK_ACTIVATE': { + activeClientIds.add(clientId) + + sendToClient(client, { + type: 'MOCKING_ENABLED', + payload: { + client: { + id: client.id, + frameType: client.frameType, + }, + }, + }) + break + } + + case 'MOCK_DEACTIVATE': { + activeClientIds.delete(clientId) + break + } + + case 'CLIENT_CLOSED': { + activeClientIds.delete(clientId) + + const remainingClients = allClients.filter((client) => { + return client.id !== clientId + }) + + // Unregister itself when there are no more clients + if (remainingClients.length === 0) { + self.registration.unregister() + } + + break + } + } +}) + +self.addEventListener('fetch', function (event) { + const { request } = event + + // Bypass navigation requests. + if (request.mode === 'navigate') { + return + } + + // Opening the DevTools triggers the "only-if-cached" request + // that cannot be handled by the worker. Bypass such requests. + if (request.cache === 'only-if-cached' && request.mode !== 'same-origin') { + return + } + + // Bypass all requests when there are no active clients. + // Prevents the self-unregistered worked from handling requests + // after it's been deleted (still remains active until the next reload). + if (activeClientIds.size === 0) { + return + } + + // Generate unique request ID. + const requestId = crypto.randomUUID() + event.respondWith(handleRequest(event, requestId)) +}) + +async function handleRequest(event, requestId) { + const client = await resolveMainClient(event) + const response = await getResponse(event, client, requestId) + + // Send back the response clone for the "response:*" life-cycle events. + // Ensure MSW is active and ready to handle the message, otherwise + // this message will pend indefinitely. + if (client && activeClientIds.has(client.id)) { + ;(async function () { + const responseClone = response.clone() + + sendToClient( + client, + { + type: 'RESPONSE', + payload: { + requestId, + isMockedResponse: IS_MOCKED_RESPONSE in response, + type: responseClone.type, + status: responseClone.status, + statusText: responseClone.statusText, + body: responseClone.body, + headers: Object.fromEntries(responseClone.headers.entries()), + }, + }, + [responseClone.body], + ) + })() + } + + return response +} + +// Resolve the main client for the given event. +// Client that issues a request doesn't necessarily equal the client +// that registered the worker. It's with the latter the worker should +// communicate with during the response resolving phase. +async function resolveMainClient(event) { + const client = await self.clients.get(event.clientId) + + if (activeClientIds.has(event.clientId)) { + return client + } + + if (client?.frameType === 'top-level') { + return client + } + + const allClients = await self.clients.matchAll({ + type: 'window', + }) + + return allClients + .filter((client) => { + // Get only those clients that are currently visible. + return client.visibilityState === 'visible' + }) + .find((client) => { + // Find the client ID that's recorded in the + // set of clients that have registered the worker. + return activeClientIds.has(client.id) + }) +} + +async function getResponse(event, client, requestId) { + const { request } = event + + // Clone the request because it might've been already used + // (i.e. its body has been read and sent to the client). + const requestClone = request.clone() + + function passthrough() { + // Cast the request headers to a new Headers instance + // so the headers can be manipulated with. + const headers = new Headers(requestClone.headers) + + // Remove the "accept" header value that marked this request as passthrough. + // This prevents request alteration and also keeps it compliant with the + // user-defined CORS policies. + const acceptHeader = headers.get('accept') + if (acceptHeader) { + const values = acceptHeader.split(',').map((value) => value.trim()) + const filteredValues = values.filter( + (value) => value !== 'msw/passthrough', + ) + + if (filteredValues.length > 0) { + headers.set('accept', filteredValues.join(', ')) + } else { + headers.delete('accept') + } + } + + return fetch(requestClone, { headers }) + } + + // Bypass mocking when the client is not active. + if (!client) { + return passthrough() + } + + // Bypass initial page load requests (i.e. static assets). + // The absence of the immediate/parent client in the map of the active clients + // means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet + // and is not ready to handle requests. + if (!activeClientIds.has(client.id)) { + return passthrough() + } + + // Notify the client that a request has been intercepted. + const requestBuffer = await request.arrayBuffer() + const clientMessage = await sendToClient( + client, + { + type: 'REQUEST', + payload: { + id: requestId, + url: request.url, + mode: request.mode, + method: request.method, + headers: Object.fromEntries(request.headers.entries()), + cache: request.cache, + credentials: request.credentials, + destination: request.destination, + integrity: request.integrity, + redirect: request.redirect, + referrer: request.referrer, + referrerPolicy: request.referrerPolicy, + body: requestBuffer, + keepalive: request.keepalive, + }, + }, + [requestBuffer], + ) + + switch (clientMessage.type) { + case 'MOCK_RESPONSE': { + return respondWithMock(clientMessage.data) + } + + case 'PASSTHROUGH': { + return passthrough() + } + } + + return passthrough() +} + +function sendToClient(client, message, transferrables = []) { + return new Promise((resolve, reject) => { + const channel = new MessageChannel() + + channel.port1.onmessage = (event) => { + if (event.data && event.data.error) { + return reject(event.data.error) + } + + resolve(event.data) + } + + client.postMessage( + message, + [channel.port2].concat(transferrables.filter(Boolean)), + ) + }) +} + +async function respondWithMock(response) { + // Setting response status code to 0 is a no-op. + // However, when responding with a "Response.error()", the produced Response + // instance will have status code set to 0. Since it's not possible to create + // a Response instance with status code 0, handle that use-case separately. + if (response.status === 0) { + return Response.error() + } + + const mockedResponse = new Response(response.body, response) + + Reflect.defineProperty(mockedResponse, IS_MOCKED_RESPONSE, { + value: true, + enumerable: true, + }) + + return mockedResponse +} diff --git a/src/App.tsx b/src/App.tsx deleted file mode 100644 index 82d35d55b..000000000 --- a/src/App.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { BrowserRouter as Router } from "react-router-dom" -import Header from "./widgets/ui/Header.tsx" -import Footer from "./widgets/ui/Footer.tsx" -import PostsManagerPage from "./pages/PostsManagerPage.tsx" - -const App = () => { - return ( - -
-
-
- -
-
-
- ) -} - -export default App diff --git a/src/app/App.tsx b/src/app/App.tsx new file mode 100644 index 000000000..d933d15b7 --- /dev/null +++ b/src/app/App.tsx @@ -0,0 +1,17 @@ +import { BrowserRouter as Router, Routes, Route } from "react-router-dom" +import { Layout } from "@/widgets/Layout/ui" +import PostsManagerPage from "@/pages/PostsManagerPage" + +const App = () => { + return ( + + + }> + } /> + + + + ) +} + +export default App diff --git a/src/index.css b/src/app/index.css similarity index 70% rename from src/index.css rename to src/app/index.css index 62d46a326..8e1047cca 100644 --- a/src/index.css +++ b/src/app/index.css @@ -1,4 +1,5 @@ -html, body { +html, +body { background: #fff; color: #000; -} \ No newline at end of file +} diff --git a/src/app/main.tsx b/src/app/main.tsx new file mode 100644 index 000000000..db1883263 --- /dev/null +++ b/src/app/main.tsx @@ -0,0 +1,24 @@ +import { StrictMode } from "react" +import ReactDOM from "react-dom/client" +import "./index.css" +import App from "./App.tsx" +import { worker } from "@/shared/api/msw" +import { QueryProvider } from "./providers/QueryProvider.tsx" + +async function main() { + await worker.start({ + serviceWorker: { + url: "/front_5th_chapter2-3/mockServiceWorker.js", + }, + }) + + ReactDOM.createRoot(document.getElementById("root")!).render( + + + + + , + ) +} + +main() diff --git a/src/app/providers/QueryProvider.tsx b/src/app/providers/QueryProvider.tsx new file mode 100644 index 000000000..3452d256e --- /dev/null +++ b/src/app/providers/QueryProvider.tsx @@ -0,0 +1,14 @@ +import { QueryClient, QueryClientProvider } from "@tanstack/react-query" + +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + staleTime: 1000 * 60 * 5, + retry: 1, + }, + }, +}) + +export const QueryProvider = ({ children }: { children: React.ReactNode }) => { + return {children} +} diff --git a/src/entities/comment/api/commentApi.ts b/src/entities/comment/api/commentApi.ts new file mode 100644 index 000000000..4a4a6c62f --- /dev/null +++ b/src/entities/comment/api/commentApi.ts @@ -0,0 +1,13 @@ +import { request } from "@/shared/api/base" +import { ENDPOINTS } from "@/shared/api/endpoints" +import { IComment, INewComment, ICommentResponse } from "@/entities/comment/model/types" + +export const commentApi = { + getCommentsByPostId: (postId: number) => request(ENDPOINTS.COMMENTS.GET_BY_POST(postId)), + createComment: (comment: INewComment) => + request(ENDPOINTS.COMMENTS.CREATE, { method: "POST", body: JSON.stringify(comment) }), + updateComment: (comment: IComment) => + request(ENDPOINTS.COMMENTS.UPDATE(comment.id), { method: "PUT", body: JSON.stringify({ body: comment.body }) }), + deleteComment: (commentId: number) => request(ENDPOINTS.COMMENTS.DELETE(commentId)), + likeComment: (commentId: number) => request(ENDPOINTS.COMMENTS.LIKE(commentId)), +} diff --git a/src/entities/comment/model/constants.ts b/src/entities/comment/model/constants.ts new file mode 100644 index 000000000..d2c6c7ba8 --- /dev/null +++ b/src/entities/comment/model/constants.ts @@ -0,0 +1,4 @@ +export const COMMENT_QUERIES = { + all: ["comments"] as const, + byPostId: (postId: number) => [...COMMENT_QUERIES.all, "post", postId] as const, +} diff --git a/src/entities/comment/model/queries.ts b/src/entities/comment/model/queries.ts new file mode 100644 index 000000000..e199130f8 --- /dev/null +++ b/src/entities/comment/model/queries.ts @@ -0,0 +1,55 @@ +import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query" +import { IComment, INewComment, ICommentResponse } from "@/entities/comment/model/types" +import { commentApi } from "@/entities/comment/api/commentApi" +import { COMMENT_QUERIES } from "@/entities/comment/model/constants" + +export const useCommentsByPostIdQuery = (postId: number, options = {}) => { + return useQuery({ + queryKey: COMMENT_QUERIES.byPostId(postId), + queryFn: () => commentApi.getCommentsByPostId(postId), + ...options, + }) +} + +export const useCreateCommentMutation = () => { + const queryClient = useQueryClient() + return useMutation({ + mutationFn: (comment: INewComment) => commentApi.createComment(comment), + onSuccess: (_, variables) => { + queryClient.invalidateQueries({ queryKey: COMMENT_QUERIES.byPostId(variables.postId) }) + }, + }) +} + +export const useUpdateCommentMutation = () => { + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: (comment: IComment) => commentApi.updateComment(comment), + onSuccess: (_, variables) => { + queryClient.invalidateQueries({ queryKey: COMMENT_QUERIES.byPostId(variables.postId) }) + }, + }) +} + +export const useDeleteCommentMutation = () => { + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: (commentId: number) => commentApi.deleteComment(commentId), + onSuccess: (_, postId) => { + queryClient.invalidateQueries({ queryKey: COMMENT_QUERIES.byPostId(postId) }) + }, + }) +} + +export const useLikeCommentMutation = () => { + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: (commentId: number) => commentApi.likeComment(commentId), + onSuccess: (_, postId) => { + queryClient.invalidateQueries({ queryKey: COMMENT_QUERIES.byPostId(postId) }) + }, + }) +} diff --git a/src/entities/comment/model/types.ts b/src/entities/comment/model/types.ts new file mode 100644 index 000000000..0eaa3dc29 --- /dev/null +++ b/src/entities/comment/model/types.ts @@ -0,0 +1,24 @@ +export interface IComment { + id: number + body: string + postId: number + likes: number + user: { + fullName: string + id: number + username: string + } +} + +export interface INewComment { + body: string + postId: number + userId: number +} + +export interface ICommentResponse { + comments: IComment[] + total: number + skip: number + limit: number +} diff --git a/src/entities/post/api/postApi.ts b/src/entities/post/api/postApi.ts new file mode 100644 index 000000000..bb92332ab --- /dev/null +++ b/src/entities/post/api/postApi.ts @@ -0,0 +1,14 @@ +import { request } from "@/shared/api/base" +import { ENDPOINTS } from "@/shared/api/endpoints" +import { ISelectedPost, INewPost, IPostsResponse, ITag } from "@/entities/post/model/types" + +export const postApi = { + getPosts: (limit: number, skip: number) => request(ENDPOINTS.POSTS.GET(limit, skip)), + searchPosts: (query: string) => request(ENDPOINTS.POSTS.SEARCH(query)), + createPost: (post: INewPost) => request(ENDPOINTS.POSTS.CREATE, { method: "POST", body: JSON.stringify(post) }), + updatePost: (post: ISelectedPost) => + request(ENDPOINTS.POSTS.UPDATE(post.id), { method: "PUT", body: JSON.stringify(post) }), + deletePost: (id: number) => request(ENDPOINTS.POSTS.DELETE(id)), + getTags: () => request(ENDPOINTS.POSTS.GET_TAGS), + getPostsByTag: (tag: string) => request(ENDPOINTS.POSTS.GET_BY_TAG(tag)), +} diff --git a/src/entities/post/model/constants.ts b/src/entities/post/model/constants.ts new file mode 100644 index 000000000..60eacce0a --- /dev/null +++ b/src/entities/post/model/constants.ts @@ -0,0 +1,8 @@ +export const POST_QUERIES = { + all: ["posts"] as const, + list: () => [...POST_QUERIES.all, "list"] as const, + search: (query: string) => [...POST_QUERIES.all, "search", query] as const, + byId: (id: number) => [...POST_QUERIES.all, id] as const, + byTag: (tag: string) => [...POST_QUERIES.all, "tag", tag] as const, + tags: () => [...POST_QUERIES.all, "tags"] as const, +} diff --git a/src/entities/post/model/queries.ts b/src/entities/post/model/queries.ts new file mode 100644 index 000000000..83157fb7a --- /dev/null +++ b/src/entities/post/model/queries.ts @@ -0,0 +1,71 @@ +import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query" +import { INewPost, ISelectedPost, IPostsResponse, ITag } from "@/entities/post/model/types" +import { postApi } from "@/entities/post/api/postApi" +import { POST_QUERIES } from "@/entities/post/model/constants" + +export const usePostsQuery = (limit: number, skip: number, options = {}) => { + return useQuery({ + queryKey: [POST_QUERIES.all, limit, skip], + queryFn: () => postApi.getPosts(limit, skip), + ...options, + }) +} + +export const useSearchPostsQuery = (searchQuery: string, options = {}) => { + return useQuery({ + queryKey: POST_QUERIES.search(searchQuery), + queryFn: () => postApi.searchPosts(searchQuery), + enabled: searchQuery.trim().length >= 2 && (options as { enabled?: boolean }).enabled !== false, + staleTime: 30000, + ...options, + }) +} + +export const useCreatePostMutation = () => { + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: (post: INewPost) => postApi.createPost(post), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: POST_QUERIES.list() }) + }, + }) +} + +export const useUpdatePostMutation = () => { + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: (post: ISelectedPost) => postApi.updatePost(post), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: POST_QUERIES.list() }) + }, + }) +} + +export const useDeletePostMutation = () => { + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: (id: number) => postApi.deletePost(id), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: POST_QUERIES.list() }) + }, + }) +} + +export const useTagsQuery = () => { + return useQuery({ + queryKey: POST_QUERIES.tags(), + queryFn: postApi.getTags, + }) +} + +export const usePostsByTagQuery = (tag: string, options = {}) => { + return useQuery({ + queryKey: POST_QUERIES.byTag(tag), + queryFn: () => postApi.getPostsByTag(tag), + enabled: tag !== "all" && tag !== "" && (options as { enabled?: boolean }).enabled !== false, + ...options, + }) +} diff --git a/src/entities/post/model/types.ts b/src/entities/post/model/types.ts new file mode 100644 index 000000000..5d3318625 --- /dev/null +++ b/src/entities/post/model/types.ts @@ -0,0 +1,48 @@ +export interface IReactions { + likes: number + dislikes: number +} + +export interface ITag { + slug: string + name: string + url: string +} + +export interface IPost { + id: number + title: string + body: string + tags: string[] + reactions: IReactions + views: number + userId: number +} + +export interface IPostWithAuthor extends IPost { + author?: { + id: number + image: string + username: string + } +} + +export interface IPostsResponse { + posts: IPost[] + total: number + skip: number + limit: number +} + +export interface INewPost { + body: string + title: string + userId: number +} + +export interface ISelectedPost extends INewPost { + id: number +} + +export type SortBy = "id" | "title" | "views" | "userId" | "author" | "none" +export type SortOrder = "asc" | "desc" diff --git a/src/entities/user/api/userApi.ts b/src/entities/user/api/userApi.ts new file mode 100644 index 000000000..7b56570b8 --- /dev/null +++ b/src/entities/user/api/userApi.ts @@ -0,0 +1,9 @@ +import { request } from "@/shared/api/base" +import { ENDPOINTS } from "@/shared/api/endpoints" +import { IUsersResponse } from "@/entities/user/model/types" +import { IUser } from "@/entities/user/model/types" + +export const userApi = { + getUsers: () => request(ENDPOINTS.USERS.GET), + getUserById: (id: number) => request(ENDPOINTS.USERS.GET_BY_ID(id)), +} diff --git a/src/entities/user/model/constants.ts b/src/entities/user/model/constants.ts new file mode 100644 index 000000000..570cde450 --- /dev/null +++ b/src/entities/user/model/constants.ts @@ -0,0 +1,5 @@ +export const USER_QUERIES = { + all: ["users"] as const, + list: () => [...USER_QUERIES.all, "list"] as const, + byId: (id: number) => [...USER_QUERIES.all, id] as const, +} diff --git a/src/entities/user/model/queries.ts b/src/entities/user/model/queries.ts new file mode 100644 index 000000000..1548585ca --- /dev/null +++ b/src/entities/user/model/queries.ts @@ -0,0 +1,19 @@ +import { useQuery } from "@tanstack/react-query" +import { userApi } from "@/entities/user/api/userApi" +import { USER_QUERIES } from "@/entities/user/model/constants" +import { IUser } from "@/entities/user/model/types" + +export const useUsersQuery = () => { + return useQuery({ + queryKey: USER_QUERIES.all, + queryFn: userApi.getUsers, + }) +} + +export const useUserByIdQuery = (id: number, options = {}) => { + return useQuery({ + queryKey: USER_QUERIES.byId(id), + queryFn: () => userApi.getUserById(id), + ...options, + }) +} diff --git a/src/entities/user/model/types.ts b/src/entities/user/model/types.ts new file mode 100644 index 000000000..27169c518 --- /dev/null +++ b/src/entities/user/model/types.ts @@ -0,0 +1,83 @@ +export interface IUser { + id: number + firstName: string + lastName: string + maidenName: string + age: number + gender: string + email: string + phone: string + username: string + password: string + birthDate: string + image: string + bloodGroup: string + height: number + weight: number + eyeColor: string + hair: { + color: string + type: string + } + ip: string + address: { + address: string + city: string + state: string + stateCode: string + postalCode: string + coordinates: { + lat: number + lng: number + } + country: string + } + macAddress: string + university: string + bank: { + cardExpire: string + cardNumber: string + cardType: string + currency: string + iban: string + } + company: { + department: string + name: string + title: string + address: { + address: string + city: string + state: string + stateCode: string + postalCode: string + coordinates: { + lat: number + lng: number + } + country: string + } + } + ein: string + ssn: string + userAgent: string + crypto: { + coin: string + wallet: string + network: string + } + role: string +} + +export interface IUsersResponse { + users: IUser[] + total: number + skip: number + limit: number +} + +export interface IPostUser { + id: number + image: string + username: string +} diff --git a/src/features/comment/model/store.ts b/src/features/comment/model/store.ts new file mode 100644 index 000000000..8696a12a2 --- /dev/null +++ b/src/features/comment/model/store.ts @@ -0,0 +1,21 @@ +import { create } from "zustand" +import { IComment } from "@/entities/comment/model/types" +interface CommentStore { + selectedComment: IComment | null + showAddCommentDialog: boolean + showEditCommentDialog: boolean + + setShowAddCommentDialog: (showAddCommentDialog: boolean) => void + setShowEditCommentDialog: (showEditCommentDialog: boolean) => void + setSelectedComment: (selectedComment: IComment | null) => void +} + +export const useCommentStore = create((set) => ({ + showAddCommentDialog: false, + showEditCommentDialog: false, + selectedComment: null, + + setShowAddCommentDialog: (showAddCommentDialog: boolean) => set({ showAddCommentDialog }), + setShowEditCommentDialog: (showEditCommentDialog: boolean) => set({ showEditCommentDialog }), + setSelectedComment: (selectedComment: IComment | null) => set({ selectedComment }), +})) diff --git a/src/features/comment/model/useComment.ts b/src/features/comment/model/useComment.ts new file mode 100644 index 000000000..da47b246d --- /dev/null +++ b/src/features/comment/model/useComment.ts @@ -0,0 +1,21 @@ +import { useCommentsByPostIdQuery } from "@/entities/comment/model/queries" +import { useCommentStore } from "@/features/comment/model/store" +import { usePostStore } from "@/features/post/model/store" + +export const useComment = () => { + const selectedComment = useCommentStore((state) => state.selectedComment) + const setSelectedComment = useCommentStore((state) => state.setSelectedComment) + + const selectedPost = usePostStore((state) => state.selectedPost) + const postId = selectedPost?.id ?? 0 + const { + data: commentsResponse, + isLoading, + error, + } = useCommentsByPostIdQuery(postId, { + enabled: !!postId, + staleTime: 0, + }) + + return { comments: commentsResponse?.comments, isLoading, error, selectedPost, selectedComment, setSelectedComment } +} diff --git a/src/features/comment/model/useCommentCreateForm.ts b/src/features/comment/model/useCommentCreateForm.ts new file mode 100644 index 000000000..18d9401cb --- /dev/null +++ b/src/features/comment/model/useCommentCreateForm.ts @@ -0,0 +1,27 @@ +import { useState, useEffect } from "react" +import { useCreateCommentMutation } from "@/entities/comment/model/queries" +import { useCommentStore } from "@/features/comment/model/store" +import { usePostStore } from "@/features/post/model/store" + +export const useCommentCreateForm = () => { + const selectedPost = usePostStore((state) => state.selectedPost) + const [newComment, setNewComment] = useState({ + body: "", + postId: selectedPost?.id || 0, + userId: 1, + }) + + const showAddCommentDialog = useCommentStore((state) => state.showAddCommentDialog) + const setShowAddCommentDialog = useCommentStore((state) => state.setShowAddCommentDialog) + + const addComment = useCreateCommentMutation() + + useEffect(() => { + setNewComment((prev) => ({ + ...prev, + postId: selectedPost?.id || 0, + })) + }, [selectedPost]) + + return { newComment, setNewComment, addComment, showAddCommentDialog, setShowAddCommentDialog } +} diff --git a/src/features/comment/model/useCommentEditForm.ts b/src/features/comment/model/useCommentEditForm.ts new file mode 100644 index 000000000..224750740 --- /dev/null +++ b/src/features/comment/model/useCommentEditForm.ts @@ -0,0 +1,13 @@ +import { useCommentStore } from "@/features/comment/model/store" +import { useUpdateCommentMutation } from "@/entities/comment/model/queries" + +export const useCommentEditForm = () => { + const selectedComment = useCommentStore((state) => state.selectedComment) + const showEditCommentDialog = useCommentStore((state) => state.showEditCommentDialog) + const setSelectedComment = useCommentStore((state) => state.setSelectedComment) + const setShowEditCommentDialog = useCommentStore((state) => state.setShowEditCommentDialog) + + const updateComment = useUpdateCommentMutation() + + return { showEditCommentDialog, setShowEditCommentDialog, selectedComment, setSelectedComment, updateComment } +} diff --git a/src/features/comment/ui/Comment.tsx b/src/features/comment/ui/Comment.tsx new file mode 100644 index 000000000..bfa2f97c3 --- /dev/null +++ b/src/features/comment/ui/Comment.tsx @@ -0,0 +1,12 @@ +import { CommentTitle, CommentList } from "@/features/comment/ui" + +const Comment = () => { + return ( +
+ + +
+ ) +} + +export default Comment diff --git a/src/features/comment/ui/CommentCreateModal.tsx b/src/features/comment/ui/CommentCreateModal.tsx new file mode 100644 index 000000000..0253c0d82 --- /dev/null +++ b/src/features/comment/ui/CommentCreateModal.tsx @@ -0,0 +1,35 @@ +import { Dialog, DialogContent, DialogHeader, DialogTitle, Textarea, Button } from "@/shared/ui" +import { useCommentCreateForm } from "@/features/comment/model/useCommentCreateForm" + +const CommentCreateModal = () => { + const { newComment, setNewComment, addComment, showAddCommentDialog, setShowAddCommentDialog } = + useCommentCreateForm() + + return ( + + + + 새 댓글 추가 + +
+