diff --git a/cool_todo_manager/package-lock.json b/cool_todo_manager/package-lock.json index b47a309..31a2bca 100644 --- a/cool_todo_manager/package-lock.json +++ b/cool_todo_manager/package-lock.json @@ -16,9 +16,11 @@ "@tailwindcss/vite": "^4.0.6", "@tanstack/react-query": "^5.66.0", "@tanstack/react-query-devtools": "^5.66.0", - "axios": "^1.7.9", + "axios": "^1.8.4", "jotai": "^2.12.0", "js-cookie": "^3.0.5", + "mobx": "^6.13.7", + "mobx-react-lite": "^4.1.0", "react": "^19.0.0", "react-dom": "^19.0.0", "react-redux": "^9.2.0", @@ -217,14 +219,13 @@ } }, "node_modules/@babel/helpers": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.9.tgz", - "integrity": "sha512-Mz/4+y8udxBKdmzt/UjPACs4G3j5SshJJEFFKxlCGPydG4JAHXxjWjAwjd09tf6oINvl1VfMJo+nB7H2YKQ0dA==", + "version": "7.26.10", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.10.tgz", + "integrity": "sha512-UPYc3SauzZ3JGgj87GgZ89JVdC5dj0AoetR5Bw6wj4niittNyFh6+eOGonYvJ1ao6B8lEa3Q3klS7ADZ53bc5g==", "dev": true, - "license": "MIT", "dependencies": { "@babel/template": "^7.26.9", - "@babel/types": "^7.26.9" + "@babel/types": "^7.26.10" }, "engines": { "node": ">=6.9.0" @@ -279,10 +280,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.9.tgz", - "integrity": "sha512-aA63XwOkcl4xxQa3HjPMqOP6LiK0ZDv3mUPYEFXkpHbaFjtGggE1A61FjFzJnB+p7/oy2gA8E+rcBNl/zC1tMg==", - "license": "MIT", + "version": "7.26.10", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.10.tgz", + "integrity": "sha512-2WJMeRQPHKSPemqk/awGrAiuFfzBmOIPXKizAsVhWH9YJqLZ0H+HS4c8loHGgW6utJ3E/ejXQUsiGaQy2NZ9Fw==", "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -335,11 +335,10 @@ } }, "node_modules/@babel/types": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.9.tgz", - "integrity": "sha512-Y3IR1cRnOxOCDvMmNiym7XpXQ93iGDDPHx+Zj+NM+rg0fBaShfQLkg+hKPaZCEvg5N/LeCo4+Rj/i3FuJsIQaw==", + "version": "7.26.10", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.10.tgz", + "integrity": "sha512-emqcG3vHrpxUKTrxcblR36dcrcoRDvKmnL/dCL6ZsHaShW80qxCAcNhzQZrpeM765VzEos+xOi4s+r4IXzTwdQ==", "dev": true, - "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.25.9", "@babel/helper-validator-identifier": "^7.25.9" @@ -349,13 +348,12 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.2.tgz", - "integrity": "sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.1.tgz", + "integrity": "sha512-kfYGy8IdzTGy+z0vFGvExZtxkFlA4zAxgKEahG9KE1ScBjpQnFsNOX8KTU5ojNru5ed5CVoJYXFtoxaq5nFbjQ==", "cpu": [ "ppc64" ], - "license": "MIT", "optional": true, "os": [ "aix" @@ -365,13 +363,12 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.24.2.tgz", - "integrity": "sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.1.tgz", + "integrity": "sha512-dp+MshLYux6j/JjdqVLnMglQlFu+MuVeNrmT5nk6q07wNhCdSnB7QZj+7G8VMUGh1q+vj2Bq8kRsuyA00I/k+Q==", "cpu": [ "arm" ], - "license": "MIT", "optional": true, "os": [ "android" @@ -381,13 +378,12 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.24.2.tgz", - "integrity": "sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.1.tgz", + "integrity": "sha512-50tM0zCJW5kGqgG7fQ7IHvQOcAn9TKiVRuQ/lN0xR+T2lzEFvAi1ZcS8DiksFcEpf1t/GYOeOfCAgDHFpkiSmA==", "cpu": [ "arm64" ], - "license": "MIT", "optional": true, "os": [ "android" @@ -397,13 +393,12 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.24.2.tgz", - "integrity": "sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.1.tgz", + "integrity": "sha512-GCj6WfUtNldqUzYkN/ITtlhwQqGWu9S45vUXs7EIYf+7rCiiqH9bCloatO9VhxsL0Pji+PF4Lz2XXCES+Q8hDw==", "cpu": [ "x64" ], - "license": "MIT", "optional": true, "os": [ "android" @@ -413,13 +408,12 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.2.tgz", - "integrity": "sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.1.tgz", + "integrity": "sha512-5hEZKPf+nQjYoSr/elb62U19/l1mZDdqidGfmFutVUjjUZrOazAtwK+Kr+3y0C/oeJfLlxo9fXb1w7L+P7E4FQ==", "cpu": [ "arm64" ], - "license": "MIT", "optional": true, "os": [ "darwin" @@ -429,13 +423,12 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.24.2.tgz", - "integrity": "sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.1.tgz", + "integrity": "sha512-hxVnwL2Dqs3fM1IWq8Iezh0cX7ZGdVhbTfnOy5uURtao5OIVCEyj9xIzemDi7sRvKsuSdtCAhMKarxqtlyVyfA==", "cpu": [ "x64" ], - "license": "MIT", "optional": true, "os": [ "darwin" @@ -445,13 +438,12 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.2.tgz", - "integrity": "sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.1.tgz", + "integrity": "sha512-1MrCZs0fZa2g8E+FUo2ipw6jw5qqQiH+tERoS5fAfKnRx6NXH31tXBKI3VpmLijLH6yriMZsxJtaXUyFt/8Y4A==", "cpu": [ "arm64" ], - "license": "MIT", "optional": true, "os": [ "freebsd" @@ -461,13 +453,12 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.24.2.tgz", - "integrity": "sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.1.tgz", + "integrity": "sha512-0IZWLiTyz7nm0xuIs0q1Y3QWJC52R8aSXxe40VUxm6BB1RNmkODtW6LHvWRrGiICulcX7ZvyH6h5fqdLu4gkww==", "cpu": [ "x64" ], - "license": "MIT", "optional": true, "os": [ "freebsd" @@ -477,13 +468,12 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.24.2.tgz", - "integrity": "sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.1.tgz", + "integrity": "sha512-NdKOhS4u7JhDKw9G3cY6sWqFcnLITn6SqivVArbzIaf3cemShqfLGHYMx8Xlm/lBit3/5d7kXvriTUGa5YViuQ==", "cpu": [ "arm" ], - "license": "MIT", "optional": true, "os": [ "linux" @@ -493,13 +483,12 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.24.2.tgz", - "integrity": "sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.1.tgz", + "integrity": "sha512-jaN3dHi0/DDPelk0nLcXRm1q7DNJpjXy7yWaWvbfkPvI+7XNSc/lDOnCLN7gzsyzgu6qSAmgSvP9oXAhP973uQ==", "cpu": [ "arm64" ], - "license": "MIT", "optional": true, "os": [ "linux" @@ -509,13 +498,12 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.24.2.tgz", - "integrity": "sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.1.tgz", + "integrity": "sha512-OJykPaF4v8JidKNGz8c/q1lBO44sQNUQtq1KktJXdBLn1hPod5rE/Hko5ugKKZd+D2+o1a9MFGUEIUwO2YfgkQ==", "cpu": [ "ia32" ], - "license": "MIT", "optional": true, "os": [ "linux" @@ -525,13 +513,12 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.24.2.tgz", - "integrity": "sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.1.tgz", + "integrity": "sha512-nGfornQj4dzcq5Vp835oM/o21UMlXzn79KobKlcs3Wz9smwiifknLy4xDCLUU0BWp7b/houtdrgUz7nOGnfIYg==", "cpu": [ "loong64" ], - "license": "MIT", "optional": true, "os": [ "linux" @@ -541,13 +528,12 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.24.2.tgz", - "integrity": "sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.1.tgz", + "integrity": "sha512-1osBbPEFYwIE5IVB/0g2X6i1qInZa1aIoj1TdL4AaAb55xIIgbg8Doq6a5BzYWgr+tEcDzYH67XVnTmUzL+nXg==", "cpu": [ "mips64el" ], - "license": "MIT", "optional": true, "os": [ "linux" @@ -557,13 +543,12 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.24.2.tgz", - "integrity": "sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.1.tgz", + "integrity": "sha512-/6VBJOwUf3TdTvJZ82qF3tbLuWsscd7/1w+D9LH0W/SqUgM5/JJD0lrJ1fVIfZsqB6RFmLCe0Xz3fmZc3WtyVg==", "cpu": [ "ppc64" ], - "license": "MIT", "optional": true, "os": [ "linux" @@ -573,13 +558,12 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.24.2.tgz", - "integrity": "sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.1.tgz", + "integrity": "sha512-nSut/Mx5gnilhcq2yIMLMe3Wl4FK5wx/o0QuuCLMtmJn+WeWYoEGDN1ipcN72g1WHsnIbxGXd4i/MF0gTcuAjQ==", "cpu": [ "riscv64" ], - "license": "MIT", "optional": true, "os": [ "linux" @@ -589,13 +573,12 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.24.2.tgz", - "integrity": "sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.1.tgz", + "integrity": "sha512-cEECeLlJNfT8kZHqLarDBQso9a27o2Zd2AQ8USAEoGtejOrCYHNtKP8XQhMDJMtthdF4GBmjR2au3x1udADQQQ==", "cpu": [ "s390x" ], - "license": "MIT", "optional": true, "os": [ "linux" @@ -605,13 +588,12 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.24.2.tgz", - "integrity": "sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.1.tgz", + "integrity": "sha512-xbfUhu/gnvSEg+EGovRc+kjBAkrvtk38RlerAzQxvMzlB4fXpCFCeUAYzJvrnhFtdeyVCDANSjJvOvGYoeKzFA==", "cpu": [ "x64" ], - "license": "MIT", "optional": true, "os": [ "linux" @@ -621,13 +603,12 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.24.2.tgz", - "integrity": "sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.1.tgz", + "integrity": "sha512-O96poM2XGhLtpTh+s4+nP7YCCAfb4tJNRVZHfIE7dgmax+yMP2WgMd2OecBuaATHKTHsLWHQeuaxMRnCsH8+5g==", "cpu": [ "arm64" ], - "license": "MIT", "optional": true, "os": [ "netbsd" @@ -637,13 +618,12 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.24.2.tgz", - "integrity": "sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.1.tgz", + "integrity": "sha512-X53z6uXip6KFXBQ+Krbx25XHV/NCbzryM6ehOAeAil7X7oa4XIq+394PWGnwaSQ2WRA0KI6PUO6hTO5zeF5ijA==", "cpu": [ "x64" ], - "license": "MIT", "optional": true, "os": [ "netbsd" @@ -653,13 +633,12 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.2.tgz", - "integrity": "sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.1.tgz", + "integrity": "sha512-Na9T3szbXezdzM/Kfs3GcRQNjHzM6GzFBeU1/6IV/npKP5ORtp9zbQjvkDJ47s6BCgaAZnnnu/cY1x342+MvZg==", "cpu": [ "arm64" ], - "license": "MIT", "optional": true, "os": [ "openbsd" @@ -669,13 +648,12 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.24.2.tgz", - "integrity": "sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.1.tgz", + "integrity": "sha512-T3H78X2h1tszfRSf+txbt5aOp/e7TAz3ptVKu9Oyir3IAOFPGV6O9c2naym5TOriy1l0nNf6a4X5UXRZSGX/dw==", "cpu": [ "x64" ], - "license": "MIT", "optional": true, "os": [ "openbsd" @@ -685,13 +663,12 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.24.2.tgz", - "integrity": "sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.1.tgz", + "integrity": "sha512-2H3RUvcmULO7dIE5EWJH8eubZAI4xw54H1ilJnRNZdeo8dTADEZ21w6J22XBkXqGJbe0+wnNJtw3UXRoLJnFEg==", "cpu": [ "x64" ], - "license": "MIT", "optional": true, "os": [ "sunos" @@ -701,13 +678,12 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.24.2.tgz", - "integrity": "sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.1.tgz", + "integrity": "sha512-GE7XvrdOzrb+yVKB9KsRMq+7a2U/K5Cf/8grVFRAGJmfADr/e/ODQ134RK2/eeHqYV5eQRFxb1hY7Nr15fv1NQ==", "cpu": [ "arm64" ], - "license": "MIT", "optional": true, "os": [ "win32" @@ -717,13 +693,12 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.24.2.tgz", - "integrity": "sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.1.tgz", + "integrity": "sha512-uOxSJCIcavSiT6UnBhBzE8wy3n0hOkJsBOzy7HDAuTDE++1DJMRRVCPGisULScHL+a/ZwdXPpXD3IyFKjA7K8A==", "cpu": [ "ia32" ], - "license": "MIT", "optional": true, "os": [ "win32" @@ -733,13 +708,12 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.24.2.tgz", - "integrity": "sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.1.tgz", + "integrity": "sha512-Y1EQdcfwMSeQN/ujR5VayLOJ1BHaK+ssyk0AEzPjC+t1lITgsnccPqFjb6V+LsTp/9Iov4ysfjxLaGJ9RPtkVg==", "cpu": [ "x64" ], - "license": "MIT", "optional": true, "os": [ "win32" @@ -3520,10 +3494,9 @@ "license": "MIT" }, "node_modules/axios": { - "version": "1.7.9", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz", - "integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==", - "license": "MIT", + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz", + "integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==", "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", @@ -3870,11 +3843,10 @@ } }, "node_modules/esbuild": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.2.tgz", - "integrity": "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.1.tgz", + "integrity": "sha512-BGO5LtrGC7vxnqucAe/rmvKdJllfGaYWdyABvyMoXQlfYMb2bbRuReWR5tEGE//4LcNJj9XrkovTqNYRFZHAMQ==", "hasInstallScript": true, - "license": "MIT", "bin": { "esbuild": "bin/esbuild" }, @@ -3882,31 +3854,31 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.24.2", - "@esbuild/android-arm": "0.24.2", - "@esbuild/android-arm64": "0.24.2", - "@esbuild/android-x64": "0.24.2", - "@esbuild/darwin-arm64": "0.24.2", - "@esbuild/darwin-x64": "0.24.2", - "@esbuild/freebsd-arm64": "0.24.2", - "@esbuild/freebsd-x64": "0.24.2", - "@esbuild/linux-arm": "0.24.2", - "@esbuild/linux-arm64": "0.24.2", - "@esbuild/linux-ia32": "0.24.2", - "@esbuild/linux-loong64": "0.24.2", - "@esbuild/linux-mips64el": "0.24.2", - "@esbuild/linux-ppc64": "0.24.2", - "@esbuild/linux-riscv64": "0.24.2", - "@esbuild/linux-s390x": "0.24.2", - "@esbuild/linux-x64": "0.24.2", - "@esbuild/netbsd-arm64": "0.24.2", - "@esbuild/netbsd-x64": "0.24.2", - "@esbuild/openbsd-arm64": "0.24.2", - "@esbuild/openbsd-x64": "0.24.2", - "@esbuild/sunos-x64": "0.24.2", - "@esbuild/win32-arm64": "0.24.2", - "@esbuild/win32-ia32": "0.24.2", - "@esbuild/win32-x64": "0.24.2" + "@esbuild/aix-ppc64": "0.25.1", + "@esbuild/android-arm": "0.25.1", + "@esbuild/android-arm64": "0.25.1", + "@esbuild/android-x64": "0.25.1", + "@esbuild/darwin-arm64": "0.25.1", + "@esbuild/darwin-x64": "0.25.1", + "@esbuild/freebsd-arm64": "0.25.1", + "@esbuild/freebsd-x64": "0.25.1", + "@esbuild/linux-arm": "0.25.1", + "@esbuild/linux-arm64": "0.25.1", + "@esbuild/linux-ia32": "0.25.1", + "@esbuild/linux-loong64": "0.25.1", + "@esbuild/linux-mips64el": "0.25.1", + "@esbuild/linux-ppc64": "0.25.1", + "@esbuild/linux-riscv64": "0.25.1", + "@esbuild/linux-s390x": "0.25.1", + "@esbuild/linux-x64": "0.25.1", + "@esbuild/netbsd-arm64": "0.25.1", + "@esbuild/netbsd-x64": "0.25.1", + "@esbuild/openbsd-arm64": "0.25.1", + "@esbuild/openbsd-x64": "0.25.1", + "@esbuild/sunos-x64": "0.25.1", + "@esbuild/win32-arm64": "0.25.1", + "@esbuild/win32-ia32": "0.25.1", + "@esbuild/win32-x64": "0.25.1" } }, "node_modules/escalade": { @@ -4993,6 +4965,39 @@ "node": "*" } }, + "node_modules/mobx": { + "version": "6.13.7", + "resolved": "https://registry.npmjs.org/mobx/-/mobx-6.13.7.tgz", + "integrity": "sha512-aChaVU/DO5aRPmk1GX8L+whocagUUpBQqoPtJk+cm7UOXUk87J4PeWCh6nNmTTIfEhiR9DI/+FnA8dln/hTK7g==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mobx" + } + }, + "node_modules/mobx-react-lite": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mobx-react-lite/-/mobx-react-lite-4.1.0.tgz", + "integrity": "sha512-QEP10dpHHBeQNv1pks3WnHRCem2Zp636lq54M2nKO2Sarr13pL4u6diQXf65yzXUn0mkk18SyIDCm9UOJYTi1w==", + "dependencies": { + "use-sync-external-store": "^1.4.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mobx" + }, + "peerDependencies": { + "mobx": "^6.9.0", + "react": "^16.8.0 || ^17 || ^18 || ^19" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -5001,16 +5006,15 @@ "license": "MIT" }, "node_modules/nanoid": { - "version": "3.3.8", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", - "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", "funding": [ { "type": "github", "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -5135,9 +5139,9 @@ } }, "node_modules/postcss": { - "version": "8.5.2", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.2.tgz", - "integrity": "sha512-MjOadfU3Ys9KYoX0AdkBlFEF1Vx37uCCeN4ZHnmwm9FfpbsGWMZeBLMmmpY+6Ocqod7mkdZ0DT31OlbsFrLlkA==", + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", + "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", "funding": [ { "type": "opencollective", @@ -5152,7 +5156,6 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "dependencies": { "nanoid": "^3.3.8", "picocolors": "^1.1.1", @@ -5595,7 +5598,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } @@ -5823,13 +5825,12 @@ } }, "node_modules/vite": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.1.0.tgz", - "integrity": "sha512-RjjMipCKVoR4hVfPY6GQTgveinjNuyLw+qruksLDvA5ktI1150VmcMBKmQaEWJhg/j6Uaf6dNCNA0AfdzUb/hQ==", - "license": "MIT", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.2.2.tgz", + "integrity": "sha512-yW7PeMM+LkDzc7CgJuRLMW2Jz0FxMOsVJ8Lv3gpgW9WLcb9cTW+121UEr1hvmfR7w3SegR5ItvYyzVz1vxNJgQ==", "dependencies": { - "esbuild": "^0.24.2", - "postcss": "^8.5.1", + "esbuild": "^0.25.0", + "postcss": "^8.5.3", "rollup": "^4.30.1" }, "bin": { diff --git a/cool_todo_manager/package.json b/cool_todo_manager/package.json index c9a7b8f..3d2fe21 100644 --- a/cool_todo_manager/package.json +++ b/cool_todo_manager/package.json @@ -18,9 +18,11 @@ "@tailwindcss/vite": "^4.0.6", "@tanstack/react-query": "^5.66.0", "@tanstack/react-query-devtools": "^5.66.0", - "axios": "^1.7.9", + "axios": "^1.8.4", "jotai": "^2.12.0", "js-cookie": "^3.0.5", + "mobx": "^6.13.7", + "mobx-react-lite": "^4.1.0", "react": "^19.0.0", "react-dom": "^19.0.0", "react-redux": "^9.2.0", diff --git a/cool_todo_manager/src/App.tsx b/cool_todo_manager/src/App.tsx index 0116187..89e9ce1 100644 --- a/cool_todo_manager/src/App.tsx +++ b/cool_todo_manager/src/App.tsx @@ -1,24 +1,22 @@ import { Theme } from '@radix-ui/themes'; import '@radix-ui/themes/styles.css'; import { QueryClientProvider } from '@tanstack/react-query'; -import { Provider } from 'react-redux'; import { createBrowserRouter, RouterProvider } from 'react-router-dom'; import queryClient from './api/queryClient'; -import { store } from './api/RTKQuery'; import './App.css'; import MyRoutes from './routes/routes'; +import React from 'react'; +import { appStore } from './stores/AppStore'; +import { Ctx } from './context'; const router = createBrowserRouter(MyRoutes); function App() { return ( - - - {/* */} - - - + + + ); } diff --git a/cool_todo_manager/src/api/RTKQuery.ts b/cool_todo_manager/src/api/RTKQuery.ts deleted file mode 100644 index cd93886..0000000 --- a/cool_todo_manager/src/api/RTKQuery.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { configureStore, isRejectedWithValue, Middleware } from '@reduxjs/toolkit'; -import { setupListeners } from '@reduxjs/toolkit/query'; -import { authApi, mainApi } from '../services/mainApi'; - -const loggerMiddleware: Middleware = (_store) => (next) => (action) => { - console.log('dispatching', action); - if(isRejectedWithValue(action)) { - // @ts-ignore - const statusCode = action.payload.status; - if(statusCode === 401) { - console.log('Unauthorized, redirecting to login page'); - localStorage.removeItem('token'); - window.location.href = '/login'; - } - } - let result = next(action); - return result; -}; - -export const store = configureStore({ - reducer: { - [mainApi.reducerPath]: mainApi.reducer, - [authApi.reducerPath]: authApi.reducer, - }, - middleware: (getDefaultMiddleware) => - getDefaultMiddleware() - .prepend(loggerMiddleware) - .concat(mainApi.middleware) - .concat(authApi.middleware), -}); - -setupListeners(store.dispatch); - diff --git a/cool_todo_manager/src/components/AuthWrapper/AuthWrapper.tsx b/cool_todo_manager/src/components/AuthWrapper/AuthWrapper.tsx index 07d3bac..b262154 100644 --- a/cool_todo_manager/src/components/AuthWrapper/AuthWrapper.tsx +++ b/cool_todo_manager/src/components/AuthWrapper/AuthWrapper.tsx @@ -1,7 +1,8 @@ +import { observer } from 'mobx-react-lite'; import { PropsWithChildren } from 'react'; import { Navigate } from 'react-router-dom'; -export default function AuthWrapper(props: PropsWithChildren) { +const AuthWrapper = observer((props: PropsWithChildren) => { if (!localStorage.getItem('token')) { console.log('No token found, redirecting to login'); @@ -9,4 +10,6 @@ export default function AuthWrapper(props: PropsWithChildren) { } return
{props.children}
; -} +}) + +export default AuthWrapper; \ No newline at end of file diff --git a/cool_todo_manager/src/components/CardGroup/CardGroup.tsx b/cool_todo_manager/src/components/CardGroup/CardGroup.tsx index 7185d91..21a1a78 100644 --- a/cool_todo_manager/src/components/CardGroup/CardGroup.tsx +++ b/cool_todo_manager/src/components/CardGroup/CardGroup.tsx @@ -1,30 +1,38 @@ +import { useEffect } from 'react'; import { Droppable } from '@hello-pangea/dnd'; import { Box, Button, Flex, Text } from '@radix-ui/themes'; -import { - useCreateTaskMutation, - useDeleteProjectMutation, - useGetTasksForGroupQuery, -} from '../../services/mainApi'; +import { PlusIcon } from '@radix-ui/react-icons'; +import { observer } from 'mobx-react-lite'; + import TaskCard from '../TaskCard/TaskCard'; import AddUserToProjectDialog from '../dialogs/AddUserToProjectDialog/AddUserToProjectDialog'; import CreateTaskDialog from '../dialogs/CreateTaskDialog/CreateTaskDialog'; import DeleteProjectDialog from '../dialogs/DeleteProjectDialog/CreateTaskDialog'; -import { PlusIcon } from '@radix-ui/react-icons'; +// Импортируем наш mobx-store +import { useStore } from '../../hooks/useStore'; type TCardGroup = { id: string; - title: string; + title: string; }; -export default function CardGroup(props: TCardGroup) { - const { data, isLoading } = useGetTasksForGroupQuery(props.id); - - const [createTaskForGroup] = useCreateTaskMutation(); - const [deleteProject] = useDeleteProjectMutation(); +function CardGroup(props: TCardGroup) { + // При монтировании (и при изменении `props.id`) грузим задачи для проекта + const store = useStore(); + useEffect(() => { + store.mainStore.fetchTasksForGroup(props.id).catch(console.error); + }, [props.id, store.mainStore]); + + + // Получаем задачи для данного проекта из стора (или пустой массив, если их нет) + const tasks = store.mainStore.tasksByProject.get(props.id) ?? []; + const isLoading = store.mainStore.isLoading; // общее isLoading из стора + + // Создать задачу const createTask = (taskText: string, date: string) => { - createTaskForGroup({ + store.mainStore.createTask({ title: taskText, projectId: props.id, assignedUserId: 1, @@ -32,8 +40,9 @@ export default function CardGroup(props: TCardGroup) { }); }; + // Удалить проект (аналог deleteProjectMutation) const deleteGroup = () => { - deleteProject(props.id); + store.mainStore.deleteProject(props.id); }; return ( @@ -41,26 +50,32 @@ export default function CardGroup(props: TCardGroup) { {(provided) => ( {props.title} + - {data && - data.map((task, i) => ( - - ))} + {isLoading && Loading...} + + {!isLoading && tasks.map((task: any, i: number) => ( + + ))} + {provided.placeholder} + + @@ -69,7 +84,7 @@ export default function CardGroup(props: TCardGroup) { @@ -77,3 +92,5 @@ export default function CardGroup(props: TCardGroup) { ); } + +export default observer(CardGroup); diff --git a/cool_todo_manager/src/components/MainBoard/MainBoard.tsx b/cool_todo_manager/src/components/MainBoard/MainBoard.tsx index 41854df..727ca9f 100644 --- a/cool_todo_manager/src/components/MainBoard/MainBoard.tsx +++ b/cool_todo_manager/src/components/MainBoard/MainBoard.tsx @@ -1,28 +1,49 @@ +import { useEffect } from 'react'; +import { observer } from 'mobx-react-lite'; import { DragDropContext } from '@hello-pangea/dnd'; import { Button, Flex, ScrollArea } from '@radix-ui/themes'; -import { - useCreateProjectMutation, - useGetProjectsQuery -} from '../../services/mainApi'; + import CardGroup from '../CardGroup/CardGroup'; import CreateProjectDialog from '../dialogs/CreateProjectDialog/CreateProjectDialog'; +import { useStore } from '../../hooks/useStore'; + +type TDragResult = any; // или определите, если надо + +function MainBoard() { + const store = useStore(); + + // Загрузка проектов при монтировании + useEffect(() => { + store.mainStore.fetchProjects().catch(console.error); + }, []); -export default function MainBoard() { const dragEndHandle = (result: TDragResult) => { - result; + // Логика перетаскивания, если нужно + console.log(result); }; - const [createProject] = useCreateProjectMutation(); - const { data: cringe, isLoading } = useGetProjectsQuery({}); + // Для кнопки "Create project" + const createProject = (newProjectData: any) => { + store.mainStore.createProject(newProjectData); + }; + + const cringe = store.mainStore.projects; + const isLoading = store.mainStore.isLoading; return ( <> + {isLoading &&
Loading Projects...
} + {!isLoading && - (cringe as any[]).map((item: any) => ( - + cringe.map((item: any) => ( + ))}
@@ -34,3 +55,5 @@ export default function MainBoard() { ); } + +export default observer(MainBoard); diff --git a/cool_todo_manager/src/components/TaskCard/TaskCard.tsx b/cool_todo_manager/src/components/TaskCard/TaskCard.tsx index 83a99dd..19ec4e9 100644 --- a/cool_todo_manager/src/components/TaskCard/TaskCard.tsx +++ b/cool_todo_manager/src/components/TaskCard/TaskCard.tsx @@ -1,6 +1,8 @@ import { Draggable } from '@hello-pangea/dnd'; import { Badge, Button, Card, Flex, Text } from '@radix-ui/themes'; -import { useUpdateTaskMutation } from '../../services/mainApi'; +import { observer } from 'mobx-react-lite'; +import { useStore } from '../../hooks/useStore'; +import { useEffect } from 'react'; type TTaskCard = { title?: string; @@ -8,26 +10,36 @@ type TTaskCard = { id?: string; index?: number; status: 'todo' | 'in-progress' | 'completed'; + projectId: string; }; const badgeNames = { todo: 'To do', 'in-progress': 'In progress', completed: 'Completed', -} as const +} as const; const badgeColors = { todo: 'blue', 'in-progress': 'orange', completed: 'green', -} as const +} as const; -export default function TaskCard(props: TTaskCard) { - const [updateTask] = useUpdateTaskMutation(); +const TaskCard = observer((props: TTaskCard) => { + const store = useStore() + const updateStatus = (newStatus: 'todo' | 'in-progress' | 'completed') => { - updateTask({ id: props.id, status: newStatus }); + if (!props.id) return; + store.mainStore.updateTask({ id: props.id, status: newStatus }); }; + // @ts-ignore + console.log(`rerendered with`, store.mainStore.tasksByProject.get(2) ); + + useEffect(() => { + console.log(store.mainStore.fakeData) + }, [store.mainStore.fakeData]) + return ( @@ -36,7 +48,9 @@ export default function TaskCard(props: TTaskCard) { className="!w-62 !flex flex-col gap-2" ref={provided.innerRef} {...provided.draggableProps} + {...provided.dragHandleProps} > +

{store.mainStore.fakeData.toString()}

{props.title} @@ -46,23 +60,39 @@ export default function TaskCard(props: TTaskCard) { {props.status !== 'todo' && ( - )} {props.status !== 'in-progress' && ( - )} {props.status !== 'completed' && ( - )} + )}
); -} +}) + +export default TaskCard; diff --git a/cool_todo_manager/src/components/dialogs/AddUserToProjectDialog/AddUserToProjectDialog.tsx b/cool_todo_manager/src/components/dialogs/AddUserToProjectDialog/AddUserToProjectDialog.tsx index 65727f8..b159fac 100644 --- a/cool_todo_manager/src/components/dialogs/AddUserToProjectDialog/AddUserToProjectDialog.tsx +++ b/cool_todo_manager/src/components/dialogs/AddUserToProjectDialog/AddUserToProjectDialog.tsx @@ -1,28 +1,31 @@ +import { PropsWithChildren, useEffect, useState } from 'react'; +import { observer } from 'mobx-react-lite'; import { Button, Dialog, Flex, Select } from '@radix-ui/themes'; -import { PropsWithChildren, useState } from 'react'; -import { - useAddProjectMemberMutation, - useGetAllUsersQuery, -} from '../../../services/mainApi'; + +// Импортируем сторы +import { useStore } from '../../../hooks/useStore'; type TAddUserToProjectDialog = { projectId: number; } & PropsWithChildren; -export default function AddUserToProjectDialog(props: TAddUserToProjectDialog) { +function AddUserToProjectDialog(props: TAddUserToProjectDialog) { const { projectId, children } = props; - const { data: users, isLoading, error, refetch } = useGetAllUsersQuery({}); - const [addProjectMember, { isLoading: isAdding }] = - useAddProjectMemberMutation(); const [selectedUserId, setSelectedUserId] = useState(null); + const store = useStore(); + + // При открытии диалога (или при ререндере) можно обновлять список пользователей + // (В RTK Query был refetch. Тут можно просто вызвать fetchAllUsers(force=true) если надо всегда свежий список) + useEffect(() => { + store.authStore.fetchAllUsers(); + }, []); + const handleAddUser = async () => { if (selectedUserId) { try { - await addProjectMember({ - projectId, - memberId: selectedUserId, - }).unwrap(); + // В store.mainStore метод addProjectMember(projectId: string, memberId: string/number) + await store.mainStore.addProjectMember(projectId.toString(), selectedUserId.toString()); setSelectedUserId(null); } catch (err) { console.error('Failed to add user:', err); @@ -30,9 +33,12 @@ export default function AddUserToProjectDialog(props: TAddUserToProjectDialog) { } }; + const isLoading = store.authStore.isLoading; + const error = store.authStore.error; + const users = store.authStore.allUsers; return ( - refetch()}> + {children} @@ -42,26 +48,21 @@ export default function AddUserToProjectDialog(props: TAddUserToProjectDialog) { {isLoading ? ( Loading users... ) : error ? ( - Error loading users. + Error loading users: {error} ) : ( - setSelectedUserId(Number(value)) - } + onValueChange={(value) => setSelectedUserId(Number(value))} required > {selectedUserId - ? users?.find( - (user: any) => - user.id === selectedUserId, - )?.username + ? users?.find((user: any) => user.id === selectedUserId)?.username : 'Select a user'} {users?.map((user: any) => ( - + {user.username} ))} @@ -76,13 +77,10 @@ export default function AddUserToProjectDialog(props: TAddUserToProjectDialog) { Cancel + - @@ -90,3 +88,5 @@ export default function AddUserToProjectDialog(props: TAddUserToProjectDialog) { ); } + +export default observer(AddUserToProjectDialog); diff --git a/cool_todo_manager/src/context.ts b/cool_todo_manager/src/context.ts new file mode 100644 index 0000000..a994b6a --- /dev/null +++ b/cool_todo_manager/src/context.ts @@ -0,0 +1,4 @@ +import React from "react"; +import { appStore } from "./stores/AppStore"; + +export const Ctx = React.createContext(appStore); \ No newline at end of file diff --git a/cool_todo_manager/src/hooks/useStore.tsx b/cool_todo_manager/src/hooks/useStore.tsx new file mode 100644 index 0000000..0fe5856 --- /dev/null +++ b/cool_todo_manager/src/hooks/useStore.tsx @@ -0,0 +1,8 @@ +import { useContext } from "react"; +import { Ctx } from "../context"; + +export function useStore() { + const store = useContext(Ctx) + + return store; +} \ No newline at end of file diff --git a/cool_todo_manager/src/pages/auth/LoginPage/LoginPage.tsx b/cool_todo_manager/src/pages/auth/LoginPage/LoginPage.tsx index a5a6bc5..c465cbe 100644 --- a/cool_todo_manager/src/pages/auth/LoginPage/LoginPage.tsx +++ b/cool_todo_manager/src/pages/auth/LoginPage/LoginPage.tsx @@ -1,3 +1,5 @@ +import { useState } from 'react'; +import { observer } from 'mobx-react-lite'; import { Box, Button, @@ -8,18 +10,24 @@ import { TextField, } from '@radix-ui/themes'; import { Form } from 'radix-ui'; -import { useState } from 'react'; -import { useLoginMutation } from '../../../services/mainApi'; -export default function LoginPage() { - const [login, { isError }] = useLoginMutation(); +// Импортируем store.authStore +import { useStore } from '../../../hooks/useStore'; + +function LoginPage() { const [username, setUsername] = useState(''); const [password, setPassword] = useState(''); + + const store = useStore(); + // В RTK Query было: [login, { isError } ] = useLoginMutation(); + // Теперь берем store.authStore.error, store.authStore.isLoading + const isError = !!store.authStore.error; + const isLoading = store.authStore.isLoading; - const submitHandler = (e: React.FormEvent) => { + const submitHandler = async (e: React.FormEvent) => { e.preventDefault(); - login({ username, password }); + await store.authStore.login({ username, password }).catch(() => {}); }; return ( @@ -29,15 +37,11 @@ export default function LoginPage() { Login - + Username is required - Password is required - {isError && ( - {'Unable to login. Please try again.'} + {store.authStore.error || 'Unable to login. Please try again.'} )} - - + Register @@ -88,3 +88,5 @@ export default function LoginPage() { ); } + +export default observer(LoginPage); diff --git a/cool_todo_manager/src/pages/auth/RegisterPage/RegisterPage.tsx b/cool_todo_manager/src/pages/auth/RegisterPage/RegisterPage.tsx index a23a0fb..3750683 100644 --- a/cool_todo_manager/src/pages/auth/RegisterPage/RegisterPage.tsx +++ b/cool_todo_manager/src/pages/auth/RegisterPage/RegisterPage.tsx @@ -1,3 +1,5 @@ +import { useState } from 'react'; +import { observer } from 'mobx-react-lite'; import { Box, Button, @@ -8,17 +10,23 @@ import { TextField, } from '@radix-ui/themes'; import { Form } from 'radix-ui'; -import { useState } from 'react'; -import { useRegisterMutation } from '../../../services/mainApi'; -export default function RegisterPage() { - const [register, { isLoading, error }] = useRegisterMutation(); +// Импортируем store.authStore +import { useStore } from '../../../hooks/useStore'; + + +function RegisterPage() { const [formData, setFormData] = useState({ username: '', email: '', password: '', }); + const store = useStore(); + + const isLoading = store.authStore.isLoading; + const error = store.authStore.error; + const handleChange = (e: React.ChangeEvent) => { setFormData({ ...formData, @@ -29,7 +37,7 @@ export default function RegisterPage() { const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); try { - await register(formData).unwrap(); + await store.authStore.register(formData); } catch (err) { console.error('Failed to register:', err); } @@ -40,15 +48,11 @@ export default function RegisterPage() { Register - + Username is required - Email is required - Email is not valid - Password is required - {error && ( - Error registering user. + Error registering user: {error} )} @@ -122,4 +124,6 @@ export default function RegisterPage() { ); -} \ No newline at end of file +} + +export default observer(RegisterPage); diff --git a/cool_todo_manager/src/pages/main/MainPage.tsx b/cool_todo_manager/src/pages/main/MainPage.tsx index e03c3e5..2f6d8ee 100644 --- a/cool_todo_manager/src/pages/main/MainPage.tsx +++ b/cool_todo_manager/src/pages/main/MainPage.tsx @@ -1,10 +1,14 @@ import { Box } from '@radix-ui/themes' +import { observer } from 'mobx-react-lite' import { Outlet } from 'react-router-dom' -export default function MainPage() { +const MainPage = observer(() => { + return ( ) -} +}) + +export default MainPage \ No newline at end of file diff --git a/cool_todo_manager/src/services/mainApi.ts b/cool_todo_manager/src/services/mainApi.ts deleted file mode 100644 index ddf181c..0000000 --- a/cool_todo_manager/src/services/mainApi.ts +++ /dev/null @@ -1,231 +0,0 @@ -import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'; - -export const mainApi = createApi({ - reducerPath: 'api', - baseQuery: fetchBaseQuery({ - baseUrl: 'http://109.107.166.17:5000/api/', - prepareHeaders: (headers) => { - headers.set( - 'Authorization', - `Bearer ${localStorage.getItem('token')}`, - ); - return headers; - }, - }), - tagTypes: ['Task', 'Project', 'ProjectMembers'], - endpoints: (builder) => ({ - getTasks: builder.query({ - query: () => ({ - url: 'tasks', - method: 'GET', - }), - providesTags: (result) => - result - ? [ - ...result.map((task) => ({ - type: 'Task' as const, - id: task, - })), - { type: 'Task', id: 'LIST' }, - ] - : [{ type: 'Task', id: 'LIST' }], - }), - getTasksForGroup: builder.query({ - query: (id: string) => ({ - url: `tasks/project/${id}`, - method: 'GET', - }), - providesTags: (result) => - result - ? [ - ...result.map((task) => ({ - type: 'Task' as const, - id: task, - })), - { type: 'Task', id: 'LIST' }, - ] - : [{ type: 'Task', id: 'LIST' }], - }), - getTask: builder.query({ - query: (id) => ({ - url: `tasks/${id}`, - method: 'GET', - }), - providesTags: (result, error, id) => [{ type: 'Task', id }], - }), - updateTask: builder.mutation({ - query: (task) => ({ - url: `tasks/${task.id}`, - method: 'PATCH', - body: { status: task.status }, - }), - invalidatesTags: (result, error, id) => [{ type: 'Task', id }], - }), - deleteTask: builder.mutation({ - query: (id) => ({ - url: `tasks/${id}`, - method: 'DELETE', - }), - invalidatesTags: (result, error, id) => [{ type: 'Task', id }], - }), - createTask: builder.mutation({ - query: (newTask) => ({ - url: 'tasks/create', - method: 'POST', - body: newTask, - }), - invalidatesTags: [{ type: 'Task', id: 'LIST' }], - }), - - // PROJECTS - createProject: builder.mutation({ - query: (newProject) => ({ - url: 'projects/create', - method: 'POST', - body: newProject, - }), - invalidatesTags: [{ type: 'Project', id: 'LIST' }], - }), - getProjects: builder.query({ - query: () => ({ - url: 'projects/my', - method: 'GET', - }), - providesTags: (result, error, id) => [ - { type: 'Project', id: 'LIST' }, - ], - }), - getProject: builder.query({ - query: (id) => ({ - url: `projects/${id}`, - method: 'GET', - }), - providesTags: (result, error, id) => [{ type: 'Project', id }], - }), - updateProject: builder.mutation({ - query: ({ id, project }) => ({ - url: `projects/${id}`, - method: 'PATCH', - body: project, - }), - invalidatesTags: (result, error, id) => [{ type: 'Project', id }], - }), - deleteProject: builder.mutation({ - query: (id) => ({ - url: `projects/${id}`, - method: 'DELETE', - }), - invalidatesTags: (result, error, id) => [ - { type: 'Project', id: 'LIST' }, - ], - }), - - // PROJECT MEMBERS - addProjectMember: builder.mutation({ - query: ({ projectId, memberId }) => ({ - url: `projects/${projectId}/members/add`, - method: 'POST', - body: { userId: memberId, role: '' }, - }), - invalidatesTags: (result, error, args) => [ - { type: 'ProjectMembers', id: 'LIST' }, - ], - }), - getProjectMembers: builder.query({ - query: (id) => ({ - url: `projects/${id}/members`, - method: 'GET', - }), - providesTags: (result, error, args) => [ - { type: 'ProjectMembers', id: 'LIST' }, - ], - }), - removeProjectMember: builder.mutation({ - query: ({ projectId, memberId }) => ({ - url: `projects/${projectId}/members/remove`, - method: 'DELETE', - body: { userId: memberId }, - }), - invalidatesTags: (result, error, id) => [ - { type: 'ProjectMembers', id: 'LIST' }, - ], - }), - }), -}); - -export const authApi = createApi({ - reducerPath: 'authApi', - baseQuery: fetchBaseQuery({ - baseUrl: 'http://109.107.166.17:4000/api/', - }), - endpoints: (builder) => ({ - login: builder.mutation({ - query: (credentials) => ({ - url: '/auth/login', - method: 'POST', - body: credentials, - }), - async onQueryStarted(arg, { queryFulfilled }) { - try { - const response = await queryFulfilled; - if (response.data.access_token) { - localStorage.setItem( - 'token', - response.data.access_token, - ); - if (response.meta?.response?.status === 201) - window.location.href = '/'; - } - } catch (error) {} - }, - }), - register: builder.mutation({ - query: (credentials) => ({ - url: '/auth/register', - method: 'POST', - body: credentials, - }), - onQueryStarted: async (arg, { queryFulfilled }) => { - try { - const response = await queryFulfilled; - if (response.meta?.response?.status === 201) - window.location.href = '/login'; - } catch (error) {} - }, - }), - - // USERS - getAllUsers: builder.query({ - query: () => ({ - url: '/users', - method: 'GET', - }), - keepUnusedDataFor: 0, - - }), - }), -}); - -export const { useLoginMutation, useRegisterMutation, useGetAllUsersQuery } = - authApi; - -export const { - useGetTasksQuery, - useCreateTaskMutation, - useUpdateTaskMutation, - useDeleteTaskMutation, - useGetTasksForGroupQuery, -} = mainApi; - -export const { - useGetProjectsQuery, - useCreateProjectMutation, - useUpdateProjectMutation, - useDeleteProjectMutation, -} = mainApi; - -export const { - useRemoveProjectMemberMutation, - useAddProjectMemberMutation, - useGetProjectMembersQuery, -} = mainApi; diff --git a/cool_todo_manager/src/stores/AppStore.ts b/cool_todo_manager/src/stores/AppStore.ts new file mode 100644 index 0000000..570995f --- /dev/null +++ b/cool_todo_manager/src/stores/AppStore.ts @@ -0,0 +1,15 @@ +import { makeAutoObservable, runInAction } from 'mobx'; +import { MainStore } from './MainStore'; +import { AuthStore } from './AuthStore'; + +export class AppStore { + mainStore: MainStore + authStore: AuthStore + + constructor() { + this.mainStore = new MainStore(this) + this.authStore = new AuthStore(this) + makeAutoObservable(this); + } +} +export const appStore = new AppStore() diff --git a/cool_todo_manager/src/stores/AuthStore.ts b/cool_todo_manager/src/stores/AuthStore.ts new file mode 100644 index 0000000..2684838 --- /dev/null +++ b/cool_todo_manager/src/stores/AuthStore.ts @@ -0,0 +1,112 @@ +import { makeAutoObservable, runInAction } from 'mobx'; +import axios from 'axios'; +import { AppStore } from './AppStore'; + +/** + * AuthStore + * Хранит логику для авторизации, регистрации и получения всех пользователей. + */ +export class AuthStore { + appStore: AppStore + + // Можно также хранить здесь данные о текущем пользователе, ошибках и т.д. + isLoading = false; + error: string | null = null; + + // Список всех пользователей (если вам нужно его кэшировать) + allUsers: any[] = []; + + constructor(appStore:any) { + this.appStore = appStore + makeAutoObservable(this); + } + + get token() { + return localStorage.getItem('token') || ''; + } + + /** + * Логин: отправляет запрос на сервер и сохраняет токен в localStorage + */ + async login(credentials: { username: string; password: string }) { + this.isLoading = true; + this.error = null; + try { + const response = await axios.post( + 'http://109.107.166.17:4000/api/auth/login', + credentials, + ); + + runInAction(() => { + if (response.data.access_token) { + localStorage.setItem('token', response.data.access_token); + // Если нужно перенаправить после логина: + window.location.href = '/'; + } + this.isLoading = false; + }); + } catch (error: any) { + runInAction(() => { + this.error = error?.response?.data?.message || 'Ошибка при логине'; + this.isLoading = false; + }); + throw error; + } + } + + /** + * Регистрация: отправляет запрос на сервер + */ + async register(credentials: { username: string; password: string }) { + this.isLoading = true; + this.error = null; + try { + const response = await axios.post( + 'http://109.107.166.17:4000/api/auth/register', + credentials, + ); + + runInAction(() => { + // Если регистрация успешна, например, статус 201 – перенаправляем на /login + if (response.status === 201) { + window.location.href = '/login'; + } + this.isLoading = false; + }); + } catch (error: any) { + runInAction(() => { + this.error = error?.response?.data?.message || 'Ошибка при регистрации'; + this.isLoading = false; + }); + throw error; + } + } + + /** + * Получить всех пользователей + */ + async fetchAllUsers(force = false) { + // Если уже есть пользователи и не запрошен force, просто возвращаем + if (this.allUsers.length && !force) { + return this.allUsers; + } + + this.isLoading = true; + this.error = null; + + try { + const response = await axios.get('http://109.107.166.17:4000/api/users'); + runInAction(() => { + this.allUsers = response.data; + this.isLoading = false; + }); + return this.allUsers; + } catch (error: any) { + runInAction(() => { + this.error = error?.response?.data?.message || 'Ошибка при получении пользователей'; + this.isLoading = false; + }); + throw error; + } + } +} diff --git a/cool_todo_manager/src/stores/MainStore.ts b/cool_todo_manager/src/stores/MainStore.ts new file mode 100644 index 0000000..41fb4d4 --- /dev/null +++ b/cool_todo_manager/src/stores/MainStore.ts @@ -0,0 +1,444 @@ +import { makeAutoObservable, runInAction } from 'mobx'; +import axios from 'axios'; +import { AppStore } from './AppStore'; + +export type Posts = Post[] + +export interface Post { + userId: number + id: number + title: string + body: string +} + +/** + * MainStore + * Хранит логику для задач, проектов и участников проектов + */ +export class MainStore { + appStore: AppStore + + // ----- Общие поля ----- + isLoading = false; + error: string | null = null; + + // ----- Задачи ----- + tasks: any[] = []; + // Кэшированный список задач по ID проекта + tasksByProject = new Map(); + + // ----- Проекты ----- + projects: any[] = []; + // Детальные данные по конкретному проекту (если нужно кэшировать) + projectById = new Map(); + + fakeData: Posts = [] + + // ----- Участники проекта ----- + // Если нужно кэшировать участников по проекту + projectMembersById = new Map(); + + constructor(appStore:any) { + this.appStore = appStore + makeAutoObservable(this); + } + + /** + * Получить токен из localStorage + */ + get token() { + return localStorage.getItem('token') || ''; + } + + /** + * Заготовка для заголовков (проставляем Bearer токен) + */ + get headers() { + return { + Authorization: `Bearer ${this.token}`, + }; + } + + /** + * -------------------------------------------------------------------------- + * ЗАДАЧИ + * -------------------------------------------------------------------------- + */ + + /** + * Получить все задачи (кэширование: если уже загружены, второй раз не грузим) + */ + + async fetchPosts(){ + fetch('https://jsonplaceholder.typicode.com/posts?limit=10') + .then(response => response.json()) + .then(json => { + + this.fakeData = json + console.log(this.fakeData); + + }) + } + + async fetchTasks(force = false) { + + console.error(`Aboba`); + + if (this.tasks.length && !force) { + return this.tasks; + } + this.isLoading = true; + this.error = null; + try { + const response = await axios.get('http://109.107.166.17:5000/api/tasks', { + headers: this.headers, + }); + runInAction(() => { + console.log(`This is all tasks`,response.data); + + this.tasks = response.data; + this.isLoading = false; + }); + return this.tasks; + } catch (error: any) { + runInAction(() => { + this.error = error?.response?.data?.message || 'Ошибка при загрузке задач'; + this.isLoading = false; + }); + throw error; + } + } + + /** + * Получить задачи для конкретного проекта (группы) + */ + async fetchTasksForGroup(projectId: string, force = false) { + if (this.tasksByProject.has(projectId) && !force) { + return this.tasksByProject.get(projectId); + } + console.log(`This is tasks for ${projectId}`); + + + this.isLoading = true; + this.error = null; + try { + const response = await axios.get( + `http://109.107.166.17:5000/api/tasks/project/${projectId}`, + { + headers: this.headers, + }, + ); + runInAction(() => { + console.log(`This is tasks for ${projectId}`,response.data); + + this.tasksByProject.set(projectId, response.data); + this.isLoading = false; + }); + return response.data; + } catch (error: any) { + runInAction(() => { + this.error = error?.response?.data?.message || 'Ошибка при загрузке задач группы'; + this.isLoading = false; + }); + throw error; + } + } + + /** + * Получить одну задачу по ID + */ + async fetchTask(taskId: string) { + this.isLoading = true; + this.error = null; + try { + const response = await axios.get( + `http://109.107.166.17:5000/api/tasks/${taskId}`, + { headers: this.headers }, + ); + return response.data; + } catch (error: any) { + runInAction(() => { + this.error = error?.response?.data?.message || 'Ошибка при загрузке задачи'; + this.isLoading = false; + }); + throw error; + } finally { + runInAction(() => { + this.isLoading = false; + }); + } + } + + /** + * Создать задачу + */ + async createTask(newTask: any) { + try { + await axios.post( + 'http://109.107.166.17:5000/api/tasks/create', + newTask, + { headers: this.headers }, + ); + // Инвалидируем кэш (чтобы при следующем getTasks() данные были актуальны) + this.tasks = []; + } catch (error: any) { + runInAction(() => { + this.error = error?.response?.data?.message || 'Ошибка при создании задачи'; + }); + throw error; + } + } + + /** + * Обновить задачу + */ + async updateTask(task: { id: string; status: string }) { + try { + await axios.patch( + `http://109.107.166.17:5000/api/tasks/${task.id}`, + { status: task.status }, + { headers: this.headers }, + ); + // Локально обновим статус в массиве tasks + runInAction(() => { + const index = this.tasks.findIndex((t) => t.id === task.id); + if (index !== -1) { + this.tasks[index].status = task.status; + } + }); + } catch (error: any) { + runInAction(() => { + this.error = error?.response?.data?.message || 'Ошибка при обновлении задачи'; + }); + throw error; + } + } + + /** + * Удалить задачу + */ + async deleteTask(taskId: string) { + try { + await axios.delete(`http://109.107.166.17:5000/api/tasks/${taskId}`, { + headers: this.headers, + }); + // Удаляем локально + runInAction(() => { + this.tasks = this.tasks.filter((t) => t.id !== taskId); + }); + } catch (error: any) { + runInAction(() => { + this.error = error?.response?.data?.message || 'Ошибка при удалении задачи'; + }); + throw error; + } + } + + /** + * -------------------------------------------------------------------------- + * ПРОЕКТЫ + * -------------------------------------------------------------------------- + */ + + /** + * Получить все проекты текущего пользователя + */ + async fetchProjects(force = false) { + if (this.projects.length && !force) { + return this.projects; + } + this.isLoading = true; + this.error = null; + + try { + const response = await axios.get( + 'http://109.107.166.17:5000/api/projects/my', + { headers: this.headers }, + ); + runInAction(() => { + this.projects = response.data; + this.isLoading = false; + }); + return this.projects; + } catch (error: any) { + runInAction(() => { + this.error = error?.response?.data?.message || 'Ошибка при получении проектов'; + this.isLoading = false; + }); + throw error; + } + } + + /** + * Получить данные одного проекта по ID + */ + async fetchProject(projectId: string) { + // Если хотим кэшировать отдельно проекты по id + if (this.projectById.has(projectId)) { + return this.projectById.get(projectId); + } + + this.isLoading = true; + this.error = null; + try { + const response = await axios.get( + `http://109.107.166.17:5000/api/projects/${projectId}`, + { headers: this.headers }, + ); + runInAction(() => { + this.projectById.set(projectId, response.data); + this.isLoading = false; + }); + return response.data; + } catch (error: any) { + runInAction(() => { + this.error = error?.response?.data?.message || 'Ошибка при получении проекта'; + this.isLoading = false; + }); + throw error; + } + } + + /** + * Создать проект + */ + async createProject(newProject: any) { + try { + await axios.post( + 'http://109.107.166.17:5000/api/projects/create', + newProject, + { headers: this.headers }, + ); + // Инвалидируем кэш + this.projects = []; + } catch (error: any) { + runInAction(() => { + this.error = error?.response?.data?.message || 'Ошибка при создании проекта'; + }); + throw error; + } + } + + /** + * Обновить проект + */ + async updateProject(id: string, projectData: any) { + try { + await axios.patch( + `http://109.107.166.17:5000/api/projects/${id}`, + projectData, + { headers: this.headers }, + ); + // Можно либо перезагрузить список проектов, либо локально обновить + this.fetchProjects(true); + } catch (error: any) { + runInAction(() => { + this.error = error?.response?.data?.message || 'Ошибка при обновлении проекта'; + }); + throw error; + } + } + + /** + * Удалить проект + */ + async deleteProject(id: string) { + try { + await axios.delete(`http://109.107.166.17:5000/api/projects/${id}`, { + headers: this.headers, + }); + // Удаляем локально + runInAction(() => { + this.projects = this.projects.filter((p) => p.id !== id); + }); + } catch (error: any) { + runInAction(() => { + this.error = error?.response?.data?.message || 'Ошибка при удалении проекта'; + }); + throw error; + } + } + + /** + * -------------------------------------------------------------------------- + * УЧАСТНИКИ ПРОЕКТА + * -------------------------------------------------------------------------- + */ + + /** + * Добавить участника в проект + */ + async addProjectMember(projectId: string, memberId: string) { + try { + await axios.post( + `http://109.107.166.17:5000/api/projects/${projectId}/members/add`, + { + userId: memberId, + role: '', + }, + { headers: this.headers }, + ); + // Инвалидируем список участников + this.projectMembersById.delete(projectId); + } catch (error: any) { + runInAction(() => { + this.error = error?.response?.data?.message || 'Ошибка при добавлении участника'; + }); + throw error; + } + } + + /** + * Получить участников проекта + */ + async fetchProjectMembers(projectId: string, force = false) { + if (this.projectMembersById.has(projectId) && !force) { + return this.projectMembersById.get(projectId); + } + this.isLoading = true; + this.error = null; + try { + const response = await axios.get( + `http://109.107.166.17:5000/api/projects/${projectId}/members`, + { + headers: this.headers, + }, + ); + runInAction(() => { + this.projectMembersById.set(projectId, response.data); + this.isLoading = false; + }); + return response.data; + } catch (error: any) { + runInAction(() => { + this.error = error?.response?.data?.message || 'Ошибка при получении участников проекта'; + this.isLoading = false; + }); + throw error; + } + } + + /** + * Удалить участника из проекта + */ + async removeProjectMember(projectId: string, memberId: string) { + try { + await axios.delete( + `http://109.107.166.17:5000/api/projects/${projectId}/members/remove`, + { + data: { userId: memberId }, + headers: this.headers, + }, + ); + // Инвалидируем список участников + this.projectMembersById.delete(projectId); + } catch (error: any) { + runInAction(() => { + this.error = error?.response?.data?.message || 'Ошибка при удалении участника'; + }); + throw error; + } + } +} + +