Merge branch 'master' of gitlab.prodcontest.ru:team-15/project

This commit is contained in:
ITQ
2025-03-01 20:34:56 +03:00
68 changed files with 19871 additions and 220 deletions
+2 -1
View File
@@ -6,7 +6,7 @@ from ninja import Router
from ninja.errors import AuthenticationError
from api.v1.auth import BearerAuth
from api.v1.schemas import BadRequestError, ForbiddenError, NotFoundError
from api.v1.schemas import BadRequestError, ForbiddenError, NotFoundError, ConflictError
from api.v1.user.schemas import (
LoginSchema,
RegisterSchema,
@@ -23,6 +23,7 @@ router = Router(tags=["user"])
response={
status.CREATED: TokenSchema,
status.BAD_REQUEST: BadRequestError,
status.CONFLICT: ConflictError,
},
auth=None,
)
+46 -34
View File
@@ -12,6 +12,7 @@
"autoprefixer": "^10.4.20",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"js-cookie": "^3.0.5",
"katex": "^0.16.21",
"lucide-react": "^0.476.0",
"monaco-editor": "^0.52.2",
@@ -28,9 +29,12 @@
"tailwindcss": "^4.0.9",
"tailwindcss-animate": "^1.0.7",
"vaul": "^1.1.2",
"zod": "^3.24.2",
"zustand": "^5.0.3",
},
"devDependencies": {
"@eslint/js": "^9.21.0",
"@types/js-cookie": "^3.0.6",
"@types/node": "^22.13.5",
"@types/react": "^19.0.10",
"@types/react-dom": "^19.0.4",
@@ -173,69 +177,69 @@
"@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.1.0", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w=="],
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.34.8", "", { "os": "android", "cpu": "arm" }, "sha512-q217OSE8DTp8AFHuNHXo0Y86e1wtlfVrXiAlwkIvGRQv9zbc6mE3sjIVfwI8sYUyNxwOg0j/Vm1RKM04JcWLJw=="],
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.34.9", "", { "os": "android", "cpu": "arm" }, "sha512-qZdlImWXur0CFakn2BJ2znJOdqYZKiedEPEVNTBrpfPjc/YuTGcaYZcdmNFTkUj3DU0ZM/AElcM8Ybww3xVLzA=="],
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.34.8", "", { "os": "android", "cpu": "arm64" }, "sha512-Gigjz7mNWaOL9wCggvoK3jEIUUbGul656opstjaUSGC3eT0BM7PofdAJaBfPFWWkXNVAXbaQtC99OCg4sJv70Q=="],
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.34.9", "", { "os": "android", "cpu": "arm64" }, "sha512-4KW7P53h6HtJf5Y608T1ISKvNIYLWRKMvfnG0c44M6In4DQVU58HZFEVhWINDZKp7FZps98G3gxwC1sb0wXUUg=="],
"@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.34.8", "", { "os": "darwin", "cpu": "arm64" }, "sha512-02rVdZ5tgdUNRxIUrFdcMBZQoaPMrxtwSb+/hOfBdqkatYHR3lZ2A2EGyHq2sGOd0Owk80oV3snlDASC24He3Q=="],
"@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.34.9", "", { "os": "darwin", "cpu": "arm64" }, "sha512-0CY3/K54slrzLDjOA7TOjN1NuLKERBgk9nY5V34mhmuu673YNb+7ghaDUs6N0ujXR7fz5XaS5Aa6d2TNxZd0OQ=="],
"@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.34.8", "", { "os": "darwin", "cpu": "x64" }, "sha512-qIP/elwR/tq/dYRx3lgwK31jkZvMiD6qUtOycLhTzCvrjbZ3LjQnEM9rNhSGpbLXVJYQ3rq39A6Re0h9tU2ynw=="],
"@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.34.9", "", { "os": "darwin", "cpu": "x64" }, "sha512-eOojSEAi/acnsJVYRxnMkPFqcxSMFfrw7r2iD9Q32SGkb/Q9FpUY1UlAu1DH9T7j++gZ0lHjnm4OyH2vCI7l7Q=="],
"@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.34.8", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-IQNVXL9iY6NniYbTaOKdrlVP3XIqazBgJOVkddzJlqnCpRi/yAeSOa8PLcECFSQochzqApIOE1GHNu3pCz+BDA=="],
"@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.34.9", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-2lzjQPJbN5UnHm7bHIUKFMulGTQwdvOkouJDpPysJS+QFBGDJqcfh+CxxtG23Ik/9tEvnebQiylYoazFMAgrYw=="],
"@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.34.8", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TYXcHghgnCqYFiE3FT5QwXtOZqDj5GmaFNTNt3jNC+vh22dc/ukG2cG+pi75QO4kACohZzidsq7yKTKwq/Jq7Q=="],
"@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.34.9", "", { "os": "freebsd", "cpu": "x64" }, "sha512-SLl0hi2Ah2H7xQYd6Qaiu01kFPzQ+hqvdYSoOtHYg/zCIFs6t8sV95kaoqjzjFwuYQLtOI0RZre/Ke0nPaQV+g=="],
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.34.8", "", { "os": "linux", "cpu": "arm" }, "sha512-A4iphFGNkWRd+5m3VIGuqHnG3MVnqKe7Al57u9mwgbyZ2/xF9Jio72MaY7xxh+Y87VAHmGQr73qoKL9HPbXj1g=="],
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.34.9", "", { "os": "linux", "cpu": "arm" }, "sha512-88I+D3TeKItrw+Y/2ud4Tw0+3CxQ2kLgu3QvrogZ0OfkmX/DEppehus7L3TS2Q4lpB+hYyxhkQiYPJ6Mf5/dPg=="],
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.34.8", "", { "os": "linux", "cpu": "arm" }, "sha512-S0lqKLfTm5u+QTxlFiAnb2J/2dgQqRy/XvziPtDd1rKZFXHTyYLoVL58M/XFwDI01AQCDIevGLbQrMAtdyanpA=="],
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.34.9", "", { "os": "linux", "cpu": "arm" }, "sha512-3qyfWljSFHi9zH0KgtEPG4cBXHDFhwD8kwg6xLfHQ0IWuH9crp005GfoUUh/6w9/FWGBwEHg3lxK1iHRN1MFlA=="],
"@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.34.8", "", { "os": "linux", "cpu": "arm64" }, "sha512-jpz9YOuPiSkL4G4pqKrus0pn9aYwpImGkosRKwNi+sJSkz+WU3anZe6hi73StLOQdfXYXC7hUfsQlTnjMd3s1A=="],
"@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.34.9", "", { "os": "linux", "cpu": "arm64" }, "sha512-6TZjPHjKZUQKmVKMUowF3ewHxctrRR09eYyvT5eFv8w/fXarEra83A2mHTVJLA5xU91aCNOUnM+DWFMSbQ0Nxw=="],
"@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.34.8", "", { "os": "linux", "cpu": "arm64" }, "sha512-KdSfaROOUJXgTVxJNAZ3KwkRc5nggDk+06P6lgi1HLv1hskgvxHUKZ4xtwHkVYJ1Rep4GNo+uEfycCRRxht7+Q=="],
"@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.34.9", "", { "os": "linux", "cpu": "arm64" }, "sha512-LD2fytxZJZ6xzOKnMbIpgzFOuIKlxVOpiMAXawsAZ2mHBPEYOnLRK5TTEsID6z4eM23DuO88X0Tq1mErHMVq0A=="],
"@rollup/rollup-linux-loongarch64-gnu": ["@rollup/rollup-linux-loongarch64-gnu@4.34.8", "", { "os": "linux", "cpu": "none" }, "sha512-NyF4gcxwkMFRjgXBM6g2lkT58OWztZvw5KkV2K0qqSnUEqCVcqdh2jN4gQrTn/YUpAcNKyFHfoOZEer9nwo6uQ=="],
"@rollup/rollup-linux-loongarch64-gnu": ["@rollup/rollup-linux-loongarch64-gnu@4.34.9", "", { "os": "linux", "cpu": "none" }, "sha512-dRAgTfDsn0TE0HI6cmo13hemKpVHOEyeciGtvlBTkpx/F65kTvShtY/EVyZEIfxFkV5JJTuQ9tP5HGBS0hfxIg=="],
"@rollup/rollup-linux-powerpc64le-gnu": ["@rollup/rollup-linux-powerpc64le-gnu@4.34.8", "", { "os": "linux", "cpu": "ppc64" }, "sha512-LMJc999GkhGvktHU85zNTDImZVUCJ1z/MbAJTnviiWmmjyckP5aQsHtcujMjpNdMZPT2rQEDBlJfubhs3jsMfw=="],
"@rollup/rollup-linux-powerpc64le-gnu": ["@rollup/rollup-linux-powerpc64le-gnu@4.34.9", "", { "os": "linux", "cpu": "ppc64" }, "sha512-PHcNOAEhkoMSQtMf+rJofwisZqaU8iQ8EaSps58f5HYll9EAY5BSErCZ8qBDMVbq88h4UxaNPlbrKqfWP8RfJA=="],
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.34.8", "", { "os": "linux", "cpu": "none" }, "sha512-xAQCAHPj8nJq1PI3z8CIZzXuXCstquz7cIOL73HHdXiRcKk8Ywwqtx2wrIy23EcTn4aZ2fLJNBB8d0tQENPCmw=="],
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.34.9", "", { "os": "linux", "cpu": "none" }, "sha512-Z2i0Uy5G96KBYKjeQFKbbsB54xFOL5/y1P5wNBsbXB8yE+At3oh0DVMjQVzCJRJSfReiB2tX8T6HUFZ2k8iaKg=="],
"@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.34.8", "", { "os": "linux", "cpu": "s390x" }, "sha512-DdePVk1NDEuc3fOe3dPPTb+rjMtuFw89gw6gVWxQFAuEqqSdDKnrwzZHrUYdac7A7dXl9Q2Vflxpme15gUWQFA=="],
"@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.34.9", "", { "os": "linux", "cpu": "s390x" }, "sha512-U+5SwTMoeYXoDzJX5dhDTxRltSrIax8KWwfaaYcynuJw8mT33W7oOgz0a+AaXtGuvhzTr2tVKh5UO8GVANTxyQ=="],
"@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.34.8", "", { "os": "linux", "cpu": "x64" }, "sha512-8y7ED8gjxITUltTUEJLQdgpbPh1sUQ0kMTmufRF/Ns5tI9TNMNlhWtmPKKHCU0SilX+3MJkZ0zERYYGIVBYHIA=="],
"@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.34.9", "", { "os": "linux", "cpu": "x64" }, "sha512-FwBHNSOjUTQLP4MG7y6rR6qbGw4MFeQnIBrMe161QGaQoBQLqSUEKlHIiVgF3g/mb3lxlxzJOpIBhaP+C+KP2A=="],
"@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.34.8", "", { "os": "linux", "cpu": "x64" }, "sha512-SCXcP0ZpGFIe7Ge+McxY5zKxiEI5ra+GT3QRxL0pMMtxPfpyLAKleZODi1zdRHkz5/BhueUrYtYVgubqe9JBNQ=="],
"@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.34.9", "", { "os": "linux", "cpu": "x64" }, "sha512-cYRpV4650z2I3/s6+5/LONkjIz8MBeqrk+vPXV10ORBnshpn8S32bPqQ2Utv39jCiDcO2eJTuSlPXpnvmaIgRA=="],
"@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.34.8", "", { "os": "win32", "cpu": "arm64" }, "sha512-YHYsgzZgFJzTRbth4h7Or0m5O74Yda+hLin0irAIobkLQFRQd1qWmnoVfwmKm9TXIZVAD0nZ+GEb2ICicLyCnQ=="],
"@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.34.9", "", { "os": "win32", "cpu": "arm64" }, "sha512-z4mQK9dAN6byRA/vsSgQiPeuO63wdiDxZ9yg9iyX2QTzKuQM7T4xlBoeUP/J8uiFkqxkcWndWi+W7bXdPbt27Q=="],
"@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.34.8", "", { "os": "win32", "cpu": "ia32" }, "sha512-r3NRQrXkHr4uWy5TOjTpTYojR9XmF0j/RYgKCef+Ag46FWUTltm5ziticv8LdNsDMehjJ543x/+TJAek/xBA2w=="],
"@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.34.9", "", { "os": "win32", "cpu": "ia32" }, "sha512-KB48mPtaoHy1AwDNkAJfHXvHp24H0ryZog28spEs0V48l3H1fr4i37tiyHsgKZJnCmvxsbATdZGBpbmxTE3a9w=="],
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.34.8", "", { "os": "win32", "cpu": "x64" }, "sha512-U0FaE5O1BCpZSeE6gBl3c5ObhePQSfk9vDRToMmTkbhCOgW4jqvtS5LGyQ76L1fH8sM0keRp4uDTsbjiUyjk0g=="],
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.34.9", "", { "os": "win32", "cpu": "x64" }, "sha512-AyleYRPU7+rgkMWbEh71fQlrzRfeP6SyMnRf9XX4fCdDPAJumdSBqYEcWPMzVQ4ScAl7E4oFfK0GUVn77xSwbw=="],
"@swc/core": ["@swc/core@1.11.1", "", { "dependencies": { "@swc/counter": "^0.1.3", "@swc/types": "^0.1.18" }, "optionalDependencies": { "@swc/core-darwin-arm64": "1.11.1", "@swc/core-darwin-x64": "1.11.1", "@swc/core-linux-arm-gnueabihf": "1.11.1", "@swc/core-linux-arm64-gnu": "1.11.1", "@swc/core-linux-arm64-musl": "1.11.1", "@swc/core-linux-x64-gnu": "1.11.1", "@swc/core-linux-x64-musl": "1.11.1", "@swc/core-win32-arm64-msvc": "1.11.1", "@swc/core-win32-ia32-msvc": "1.11.1", "@swc/core-win32-x64-msvc": "1.11.1" }, "peerDependencies": { "@swc/helpers": "*" }, "optionalPeers": ["@swc/helpers"] }, "sha512-67+lBHZ1lAJQKoOhBHl9DE2iugPYAulRVArZjoF+DnIY3G9wLXCXxw5It0IaCnzvJVvUPxGmr0rHViXKBDP5Vg=="],
"@swc/core": ["@swc/core@1.11.5", "", { "dependencies": { "@swc/counter": "^0.1.3", "@swc/types": "^0.1.19" }, "optionalDependencies": { "@swc/core-darwin-arm64": "1.11.5", "@swc/core-darwin-x64": "1.11.5", "@swc/core-linux-arm-gnueabihf": "1.11.5", "@swc/core-linux-arm64-gnu": "1.11.5", "@swc/core-linux-arm64-musl": "1.11.5", "@swc/core-linux-x64-gnu": "1.11.5", "@swc/core-linux-x64-musl": "1.11.5", "@swc/core-win32-arm64-msvc": "1.11.5", "@swc/core-win32-ia32-msvc": "1.11.5", "@swc/core-win32-x64-msvc": "1.11.5" }, "peerDependencies": { "@swc/helpers": "*" }, "optionalPeers": ["@swc/helpers"] }, "sha512-EVY7zfpehxhTZXOfy508gb3D78ihoGGmvyiTWtlBPjgIaidP1Xw0naHMD78CWiFlZmeDjKXJufGtsEGOnZdmNA=="],
"@swc/core-darwin-arm64": ["@swc/core-darwin-arm64@1.11.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-bJbqZ51JghEZ8WaFetofkfkS3MWsS/V3vDvY+0r+SlLeocZwf8q8/GqcafnElHcU+zLV6yTi13fJwUce6ULiUQ=="],
"@swc/core-darwin-arm64": ["@swc/core-darwin-arm64@1.11.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-GEd1hzEx0mSGkJYMFMGLnrGgjL2rOsOsuYWyjyiA3WLmhD7o+n/EWBDo6mzD/9aeF8dzSPC0TnW216gJbvrNzA=="],
"@swc/core-darwin-x64": ["@swc/core-darwin-x64@1.11.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-9GGEoN0uxkLg3KocOVzMfe9c9/DxESXclsL/U2xVLa3pTFB5YnXhiCP5YBT/3Q7nSGLD+R2ALqkNlDoueUjvPw=="],
"@swc/core-darwin-x64": ["@swc/core-darwin-x64@1.11.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-toz04z9wAClVvQSEY3xzrgyyeWBAfMWcKG4K0ugNvO56h/wczi2ZHRlnAXZW1tghKBk3z6MXqa/srfXgNhffKw=="],
"@swc/core-linux-arm-gnueabihf": ["@swc/core-linux-arm-gnueabihf@1.11.1", "", { "os": "linux", "cpu": "arm" }, "sha512-Lt7l/l0nfSTUzsWcVY3dtOPl5RtgCJ+Ya8IG4Aa3l6c7kLc6Sx4JpylpEIY9yhGidDy/uQ8KUg5kqUPtUrXrvQ=="],
"@swc/core-linux-arm-gnueabihf": ["@swc/core-linux-arm-gnueabihf@1.11.5", "", { "os": "linux", "cpu": "arm" }, "sha512-5SjmKxXdwbBpsYGTpgeXOXMIjS563/ntRGn8Zc12H/c4VfPrRLGhgbJ/48z2XVFyBLcw7BCHZyFuVX1+ZI3W0Q=="],
"@swc/core-linux-arm64-gnu": ["@swc/core-linux-arm64-gnu@1.11.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-oe826cfuGukctTSpDjk7RJRDEJihQMAzvO5tdWK0wcy+zvMPFyH5Fg6cW0X4ST3M7fcV91/1T/iuiiD2SVamYw=="],
"@swc/core-linux-arm64-gnu": ["@swc/core-linux-arm64-gnu@1.11.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-pydIlInHRzRIwB0NHblz3Dx58H/bsi0I5F2deLf9iOmwPNuOGcEEZF1Qatc7YIjP5DFbXK+Dcz+pMUZb2cc2MQ=="],
"@swc/core-linux-arm64-musl": ["@swc/core-linux-arm64-musl@1.11.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-ABb4pnYeQp/JBJS5Qd2apTwOzpzrTebQFUiFjk0WgTKIr9T6SL3tLXMjgvbSXIath+1HnbCKFUwDXNQhgGFFTg=="],
"@swc/core-linux-arm64-musl": ["@swc/core-linux-arm64-musl@1.11.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-LhBHKjkZq5tJF1Lh0NJFpx7ROnCWLckrlIAIdSt9XfOV+zuEXJQOj+NFcM1eNk17GFfFyUMOZyGZxzYq5dveEQ=="],
"@swc/core-linux-x64-gnu": ["@swc/core-linux-x64-gnu@1.11.1", "", { "os": "linux", "cpu": "x64" }, "sha512-E09TcHv40bV0mOHTKquZw0IOcQ+lzzpQjyOhCa7+GBpbS3eg5/35Gu7DfToN2bomz74LPKW/l7jZRG+ZNOYNHQ=="],
"@swc/core-linux-x64-gnu": ["@swc/core-linux-x64-gnu@1.11.5", "", { "os": "linux", "cpu": "x64" }, "sha512-dCi4xkxXlsk5sQYb3i413Cfh7+wMJeBYTvBZTD5xh+/DgRtIcIJLYJ2tNjWC4/C2i5fj+Ze9bKNSdd8weRWZ3A=="],
"@swc/core-linux-x64-musl": ["@swc/core-linux-x64-musl@1.11.1", "", { "os": "linux", "cpu": "x64" }, "sha512-cuW4r7GbvQt9uv+rGdYLHUjDvGjHmr1nYE7iFVk6r4i+byZuXBK6M7P1p+/dTzacshOc05I9n/eUV+Hfjp9a3A=="],
"@swc/core-linux-x64-musl": ["@swc/core-linux-x64-musl@1.11.5", "", { "os": "linux", "cpu": "x64" }, "sha512-K0AC4TreM5Oo/tXNXnE/Gf5+5y/HwUdd7xvUjOpZddcX/RlsbYOKWLgOtA3fdFIuta7XC+vrGKmIhm5l70DSVQ=="],
"@swc/core-win32-arm64-msvc": ["@swc/core-win32-arm64-msvc@1.11.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-H8Q78GwaKnCL4isHx8JRTRi6vUU6iMLbpegS2jzWWC1On7EePhkLx2eR8nEsaRIQB6rc3WqdIj74OgOpNoPi7g=="],
"@swc/core-win32-arm64-msvc": ["@swc/core-win32-arm64-msvc@1.11.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-wzum8sYUsvPY7kgUfuqVYTgIPYmBC8KPksoNM1fz5UfhudU0ciQuYvUBD47GIGOevaoxhLkjPH4CB95vh1mJ9w=="],
"@swc/core-win32-ia32-msvc": ["@swc/core-win32-ia32-msvc@1.11.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-Rx7cZ0OvqMb16fgmUSlPWQbH1+X355IDJhVQpUlpL+ezD/kkWmJix+4u2GVE/LHrfbdyZ4sjjIzSsCQxJV05Mw=="],
"@swc/core-win32-ia32-msvc": ["@swc/core-win32-ia32-msvc@1.11.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-lco7mw0TPRTpVPR6NwggJpjdUkAboGRkLrDHjIsUaR+Y5+0m5FMMkHOMxWXAbrBS5c4ph7QErp4Lma4r9Mn5og=="],
"@swc/core-win32-x64-msvc": ["@swc/core-win32-x64-msvc@1.11.1", "", { "os": "win32", "cpu": "x64" }, "sha512-6bEEC/XU1lwYzUXY7BXj3nhe7iBF9+i9dVo+hbiVxXZMrD0LUd+7urOBM3NtVnDsUaR6Ge/g7aR+OfpgYscKOg=="],
"@swc/core-win32-x64-msvc": ["@swc/core-win32-x64-msvc@1.11.5", "", { "os": "win32", "cpu": "x64" }, "sha512-E+DApLSC6JRK8VkDa4bNsBdD7Qoomx1HvKVZpOXl9v94hUZI5GMExl4vU5isvb+hPWL7rZ0NeI7ITnVLgLJRbA=="],
"@swc/counter": ["@swc/counter@0.1.3", "", {}, "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ=="],
"@swc/types": ["@swc/types@0.1.18", "", { "dependencies": { "@swc/counter": "^0.1.3" } }, "sha512-NZghLaQvF3eFdj2DUjGkpwaunbZYaRcxciHINnwA4n3FrLAI8hKFOBqs2wkcOiLQfWkIdfuG6gBkNFrkPNji5g=="],
"@swc/types": ["@swc/types@0.1.19", "", { "dependencies": { "@swc/counter": "^0.1.3" } }, "sha512-WkAZaAfj44kh/UFdAQcrMP1I0nwRqpt27u+08LMBYMqmQfwwMofYoMh/48NGkMMRfC4ynpfwRbJuu8ErfNloeA=="],
"@tailwindcss/node": ["@tailwindcss/node@4.0.9", "", { "dependencies": { "enhanced-resolve": "^5.18.1", "jiti": "^2.4.2", "tailwindcss": "4.0.9" } }, "sha512-tOJvdI7XfJbARYhxX+0RArAhmuDcczTC46DGCEziqxzzbIaPnfYaIyRT31n4u8lROrsO7Q6u/K9bmQHL2uL1bQ=="],
@@ -275,6 +279,8 @@
"@types/hast": ["@types/hast@3.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ=="],
"@types/js-cookie": ["@types/js-cookie@3.0.6", "", {}, "sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ=="],
"@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
"@types/katex": ["@types/katex@0.16.7", "", {}, "sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ=="],
@@ -283,7 +289,7 @@
"@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="],
"@types/node": ["@types/node@22.13.5", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-+lTU0PxZXn0Dr1NBtC7Y8cR21AJr87dLLU953CWA6pMxxv/UDc7jYAY90upcrie1nRcD6XNG5HOYEDtgW5TxAg=="],
"@types/node": ["@types/node@22.13.8", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-G3EfaZS+iOGYWLLRCEAXdWK9my08oHNZ+FHluRiggIYJPOXzhOiDgpVCUHaUvyIC5/fj7C/p637jdzC666AOKQ=="],
"@types/react": ["@types/react@19.0.10", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-JuRQ9KXLEjaUNjTWpzuR231Z2WpIwczOkBEIvbHNCzQefFIT0L8IqE6NV6ULLyC1SI/i234JnDoMkfg+RjQj2g=="],
@@ -401,7 +407,7 @@
"eslint": ["eslint@9.21.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.19.2", "@eslint/core": "^0.12.0", "@eslint/eslintrc": "^3.3.0", "@eslint/js": "9.21.0", "@eslint/plugin-kit": "^0.2.7", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.2.0", "eslint-visitor-keys": "^4.2.0", "espree": "^10.3.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-KjeihdFqTPhOMXTt7StsDxriV4n66ueuF/jfPNC3j/lduHwr/ijDwJMsF+wyMJethgiKi5wniIE243vi07d3pg=="],
"eslint-plugin-react-hooks": ["eslint-plugin-react-hooks@5.1.0", "", { "peerDependencies": { "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" } }, "sha512-mpJRtPgHN2tNAvZ35AMfqeB3Xqeo273QxrHJsbBEPWODRM4r0yB6jfoROqKEYrOn27UtRPpcpHc2UqyBSuUNTw=="],
"eslint-plugin-react-hooks": ["eslint-plugin-react-hooks@5.2.0", "", { "peerDependencies": { "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" } }, "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg=="],
"eslint-plugin-react-refresh": ["eslint-plugin-react-refresh@0.4.19", "", { "peerDependencies": { "eslint": ">=8.40" } }, "sha512-eyy8pcr/YxSYjBoqIFSrlbn9i/xvxUFa8CjzAYo9cFjgGXqq1hyjihcpZvxRLalpaWmueWR81xn7vuKmAFijDQ=="],
@@ -511,6 +517,8 @@
"jiti": ["jiti@2.4.2", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A=="],
"js-cookie": ["js-cookie@3.0.5", "", {}, "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw=="],
"js-yaml": ["js-yaml@4.1.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="],
"json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="],
@@ -703,7 +711,7 @@
"reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
"rollup": ["rollup@4.34.8", "", { "dependencies": { "@types/estree": "1.0.6" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.34.8", "@rollup/rollup-android-arm64": "4.34.8", "@rollup/rollup-darwin-arm64": "4.34.8", "@rollup/rollup-darwin-x64": "4.34.8", "@rollup/rollup-freebsd-arm64": "4.34.8", "@rollup/rollup-freebsd-x64": "4.34.8", "@rollup/rollup-linux-arm-gnueabihf": "4.34.8", "@rollup/rollup-linux-arm-musleabihf": "4.34.8", "@rollup/rollup-linux-arm64-gnu": "4.34.8", "@rollup/rollup-linux-arm64-musl": "4.34.8", "@rollup/rollup-linux-loongarch64-gnu": "4.34.8", "@rollup/rollup-linux-powerpc64le-gnu": "4.34.8", "@rollup/rollup-linux-riscv64-gnu": "4.34.8", "@rollup/rollup-linux-s390x-gnu": "4.34.8", "@rollup/rollup-linux-x64-gnu": "4.34.8", "@rollup/rollup-linux-x64-musl": "4.34.8", "@rollup/rollup-win32-arm64-msvc": "4.34.8", "@rollup/rollup-win32-ia32-msvc": "4.34.8", "@rollup/rollup-win32-x64-msvc": "4.34.8", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-489gTVMzAYdiZHFVA/ig/iYFllCcWFHMvUHI1rpFmkoUtRlQxqh6/yiNqnYibjMZ2b/+FUQwldG+aLsEt6bglQ=="],
"rollup": ["rollup@4.34.9", "", { "dependencies": { "@types/estree": "1.0.6" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.34.9", "@rollup/rollup-android-arm64": "4.34.9", "@rollup/rollup-darwin-arm64": "4.34.9", "@rollup/rollup-darwin-x64": "4.34.9", "@rollup/rollup-freebsd-arm64": "4.34.9", "@rollup/rollup-freebsd-x64": "4.34.9", "@rollup/rollup-linux-arm-gnueabihf": "4.34.9", "@rollup/rollup-linux-arm-musleabihf": "4.34.9", "@rollup/rollup-linux-arm64-gnu": "4.34.9", "@rollup/rollup-linux-arm64-musl": "4.34.9", "@rollup/rollup-linux-loongarch64-gnu": "4.34.9", "@rollup/rollup-linux-powerpc64le-gnu": "4.34.9", "@rollup/rollup-linux-riscv64-gnu": "4.34.9", "@rollup/rollup-linux-s390x-gnu": "4.34.9", "@rollup/rollup-linux-x64-gnu": "4.34.9", "@rollup/rollup-linux-x64-musl": "4.34.9", "@rollup/rollup-win32-arm64-msvc": "4.34.9", "@rollup/rollup-win32-ia32-msvc": "4.34.9", "@rollup/rollup-win32-x64-msvc": "4.34.9", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-nF5XYqWWp9hx/LrpC8sZvvvmq0TeTjQgaZHYmAgwysT9nh8sWnZhBnM8ZyVbbJFIQBLwHDNoMqsBZBbUo4U8sQ=="],
"run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
@@ -807,6 +815,10 @@
"yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
"zod": ["zod@3.24.2", "", {}, "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ=="],
"zustand": ["zustand@5.0.3", "", { "peerDependencies": { "@types/react": ">=18.0.0", "immer": ">=9.0.6", "react": ">=18.0.0", "use-sync-external-store": ">=1.2.0" }, "optionalPeers": ["@types/react", "immer", "react", "use-sync-external-store"] }, "sha512-14fwWQtU3pH4dE0dOpdMiWjddcH+QzKIgk1cl8epwSE7yag43k/AD/m4L6+K7DytAOr9gGBe3/EXj9g7cdostg=="],
"zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="],
"@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
+24 -3
View File
@@ -1,10 +1,31 @@
<!doctype html>
<html lang="en">
<html lang="ru">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/logo.svg" />
<link rel="icon" type="image/svg+xml" href="/dr.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>мегазордпобеда.рф</title>
<link
rel="preload"
href="/fonts/HSESans-Regular.otf"
as="font"
type="font/otf"
crossorigin="anonymous"
/>
<link
rel="preload"
href="/fonts/HSESans-SemiBold.otf"
as="font"
type="font/otf"
crossorigin="anonymous"
/>
<link
rel="preload"
href="/fonts/HSESans-Bold.otf"
as="font"
type="font/otf"
crossorigin="anonymous"
/>
<title>DATARUSH</title>
</head>
<body>
<div id="root"></div>
+5 -1
View File
@@ -18,6 +18,7 @@
"autoprefixer": "^10.4.20",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"js-cookie": "^3.0.5",
"katex": "^0.16.21",
"lucide-react": "^0.476.0",
"monaco-editor": "^0.52.2",
@@ -33,10 +34,13 @@
"tailwind-merge": "^3.0.2",
"tailwindcss": "^4.0.9",
"tailwindcss-animate": "^1.0.7",
"vaul": "^1.1.2"
"vaul": "^1.1.2",
"zod": "^3.24.2",
"zustand": "^5.0.3"
},
"devDependencies": {
"@eslint/js": "^9.21.0",
"@types/js-cookie": "^3.0.6",
"@types/node": "^22.13.5",
"@types/react": "^19.0.10",
"@types/react-dom": "^19.0.4",
+5
View File
@@ -0,0 +1,5 @@
<svg width="52" height="52" viewBox="0 0 52 52" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="52" height="52" fill="#333333"/>
<path d="M25.796 26.6C25.796 31.352 22.532 35 17.372 35H11.06V18.2H17.372C22.532 18.2 25.796 21.848 25.796 26.6ZM21.764 26.624C21.764 24.056 20.012 22.208 17.3 22.208H15.092V30.992H17.3C20.012 30.992 21.764 29.168 21.764 26.624Z" fill="#FFDD2D"/>
<path d="M28.2631 18.2H34.5031C37.7671 18.2 40.3591 20.792 40.3591 24.032C40.3591 26.336 38.9431 28.304 36.9031 29.144C37.9831 29.456 38.7751 31.52 40.2631 31.52C40.5271 31.52 40.7911 31.472 41.1031 31.352V35C40.3831 35.192 39.7351 35.288 39.1591 35.288C35.1031 35.288 34.4551 30.32 33.1351 29.744H32.2951V35H28.2631V18.2ZM32.2951 21.848V26.096H34.0951C35.2951 26.096 36.3271 25.232 36.3271 23.984C36.3271 22.76 35.2951 21.848 34.0951 21.848H32.2951Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 863 B

-93
View File
@@ -1,93 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="100mm"
height="100mm"
viewBox="0 0 100 100"
version="1.1"
id="svg1"
xml:space="preserve"
inkscape:version="1.4 (86a8ad7, 2024-10-11)"
sodipodi:docname="logo.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm"
inkscape:zoom="1"
inkscape:cx="183"
inkscape:cy="182.5"
inkscape:window-width="1920"
inkscape:window-height="1017"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="layer1" /><defs
id="defs1"><linearGradient
id="linearGradient35"
inkscape:collect="always"><stop
style="stop-color:#ffc265;stop-opacity:1;"
offset="0"
id="stop35" /><stop
style="stop-color:#ff6933;stop-opacity:1;"
offset="1"
id="stop36" /></linearGradient><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient35"
id="linearGradient36"
x1="1.0752909"
y1="67.795866"
x2="99.798593"
y2="67.795866"
gradientUnits="userSpaceOnUse" /></defs><g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"><path
style="font-weight:bold;font-size:77.6829px;font-family:'Adobe Gothic Std';-inkscape-font-specification:'Adobe Gothic Std, Bold';fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.37228;stroke-dasharray:none;stroke-opacity:1"
d="m 38.059464,33.814916 -6.188593,23.94974 c -1.849465,7.624138 -3.485531,15.331149 -4.765929,22.45806 H 26.891544 C 25.682277,72.930062 24.117345,65.471666 22.339015,57.764656 L 16.434954,33.814916 H 3.4175691 L -0.28136,97.542768 H 9.3216293 L 10.388628,73.012933 c 0.355666,-7.872752 0.711333,-17.320054 0.853599,-25.358547 h 0.213399 c 1.2804,7.789879 2.987597,16.408472 4.765929,23.618255 l 6.046326,25.358548 h 7.966925 l 6.686526,-25.772904 c 1.849464,-7.126912 3.841195,-15.579761 5.406127,-23.203899 h 0.213399 c -0.07113,6.463943 0.284533,16.739956 0.569067,25.109934 L 44.10579,97.542768 H 54.135579 L 51.005715,33.814916 Z m 23.829679,0 V 44.09093 h 21.83791 v 0.331483 L 59.755145,90.664471 v 6.878297 H 97.028967 V 87.183886 H 73.341597 V 86.852402 L 96.815576,41.439055 v -7.624139 z"
id="text1"
aria-label="MZ&#10;" /><path
style="font-weight:bold;font-size:77.6829px;font-family:'Adobe Gothic Std';-inkscape-font-specification:'Adobe Gothic Std, Bold';fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.37228;stroke-dasharray:none;stroke-opacity:1"
d="m 38.58037,34.344171 -6.188593,23.94974 c -1.849465,7.624138 -3.485531,15.331148 -4.765929,22.45806 H 27.41245 C 26.203183,73.459317 24.638251,66.00092 22.859921,58.293911 L 16.95586,34.344171 H 3.9384748 L 0.23954565,98.072025 H 9.8425353 L 10.909534,73.542187 C 11.2652,65.669435 11.620866,56.222134 11.763133,48.183641 h 0.213399 c 1.2804,7.789879 2.987597,16.408471 4.765929,23.618255 l 6.046326,25.358547 h 7.966925 l 6.686526,-25.772904 c 1.849464,-7.126912 3.841195,-15.579761 5.406127,-23.203898 h 0.213399 c -0.07113,6.463943 0.284533,16.739955 0.569067,25.109933 l 0.995865,24.778451 H 54.656485 L 51.526621,34.344171 Z m 23.829679,0 v 10.276013 h 21.83791 v 0.331483 L 60.276051,91.193726 v 6.878299 H 97.549873 V 87.713141 H 73.862503 V 87.381656 L 97.336482,41.96831 v -7.624139 z"
id="path21"
aria-label="MZ&#10;" /><path
style="font-weight:bold;font-size:77.6829px;font-family:'Adobe Gothic Std';-inkscape-font-specification:'Adobe Gothic Std, Bold';fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.37228;stroke-dasharray:none;stroke-opacity:1"
d="m 39.101276,34.873425 -6.188593,23.94974 c -1.849465,7.624138 -3.485531,15.331149 -4.765929,22.45806 H 27.933355 C 26.724089,73.988571 25.159157,66.530175 23.380827,58.823165 L 17.476766,34.873425 H 4.4593805 L 0.76045133,98.601283 H 10.363441 L 11.43044,74.071442 c 0.355666,-7.872752 0.711332,-17.320053 0.853599,-25.358547 h 0.213399 c 1.2804,7.789879 2.987597,16.408472 4.765929,23.618255 l 6.046326,25.358548 h 7.966925 l 6.686526,-25.772904 c 1.849464,-7.126912 3.841195,-15.579761 5.406127,-23.203899 h 0.213399 c -0.07113,6.463943 0.284533,16.739956 0.569067,25.109934 l 0.995865,24.778454 H 55.177391 L 52.047527,34.873425 Z m 23.829679,0 v 10.276014 h 21.83791 v 0.331483 L 60.796957,91.72298 v 6.878303 H 98.070775 V 88.242395 H 74.383409 V 87.910911 L 97.857388,42.497564 v -7.624139 z"
id="path22"
aria-label="MZ&#10;" /><path
style="font-weight:bold;font-size:77.6829px;font-family:'Adobe Gothic Std';-inkscape-font-specification:'Adobe Gothic Std, Bold';fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.37228;stroke-dasharray:none;stroke-opacity:1"
d="M 39.622182,35.40268 33.433589,59.35242 C 31.584124,66.976558 29.948058,74.683568 28.66766,81.81048 H 28.454261 C 27.244995,74.517826 25.680063,67.059429 23.901733,59.35242 L 17.997672,35.40268 H 4.9802861 L 1.281357,99.13054 h 9.60299 l 1.066999,-24.529844 c 0.355666,-7.872752 0.711332,-17.320053 0.853599,-25.358546 h 0.213399 c 1.2804,7.789879 2.987597,16.408471 4.765929,23.618255 l 6.046326,25.358544 h 7.966925 L 38.48405,72.446048 c 1.849464,-7.126912 3.841195,-15.57976 5.406127,-23.203898 h 0.213399 c -0.07113,6.463943 0.284533,16.739955 0.569067,25.109933 L 45.668508,99.13054 H 55.698297 L 52.568433,35.40268 Z m 23.829679,0 v 10.276013 h 21.83791 v 0.331483 L 61.317863,92.252235 V 99.13054 H 98.591684 V 88.77165 H 74.904315 V 88.440165 L 98.378289,43.026819 V 35.40268 Z"
id="path23"
aria-label="MZ&#10;" /><path
style="font-weight:bold;font-size:77.6829px;font-family:'Adobe Gothic Std';-inkscape-font-specification:'Adobe Gothic Std, Bold';fill:url(#linearGradient36);fill-opacity:1;stroke:#000000;stroke-width:1.372;stroke-dasharray:none;stroke-opacity:1"
d="m 40.143088,35.931934 -6.188593,23.94974 c -1.849465,7.624138 -3.485531,15.331149 -4.765929,22.45806 H 28.975167 C 27.765901,75.04708 26.200969,67.588684 24.422639,59.881674 L 18.518578,35.931934 H 5.5011918 L 1.8022627,99.659798 h 9.6029903 l 1.066999,-24.529847 c 0.355666,-7.872752 0.711332,-17.320053 0.853599,-25.358547 h 0.213399 c 1.2804,7.789879 2.987597,16.408472 4.765929,23.618255 l 6.046326,25.358548 h 7.966925 l 6.686525,-25.772904 c 1.849465,-7.126912 3.841196,-15.579761 5.406128,-23.203899 h 0.213399 c -0.07113,6.463943 0.284533,16.739956 0.569067,25.109934 l 0.995865,24.77846 H 56.219203 L 53.089339,35.931934 Z m 23.829679,0 v 10.276014 h 21.83791 v 0.331483 L 61.838769,92.781489 v 6.878309 H 99.112593 V 89.300904 H 75.425221 V 88.96942 L 98.899198,43.556073 v -7.624139 z"
id="path24"
aria-label="MZ&#10;" /><g
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-opacity:1;stroke-width:6.78629989;stroke-dasharray:none"
id="g25"
transform="matrix(0.14638629,0,0,0.16509671,32.014583,-5.942102)"><path
d="m 237.09324,93.929616 c 0.041,0.736 -0.013,1.485 -0.198,2.229 l -16.5,66.000004 c -0.832,3.325 -3.812,5.663 -7.238,5.681 l -99,0.5 c -0.013,0 -0.025,0 -0.038,0 H 15.118243 c -3.444,0 -6.4450002,-2.346 -7.2770002,-5.688 L -8.6587571,96.401616 c -0.19,-0.764 -0.245,-1.534 -0.197,-2.289 -6.3829999,-2.011 -11.0259999,-7.984 -11.0259999,-15.023 0,-8.685 7.065,-15.75 15.7499998,-15.75 8.6850001,0 15.7500002,7.065 15.7500002,15.75 0,4.891 -2.2410002,9.267 -5.7500002,12.158 L 26.526243,112.06162 c 5.221,5.261 12.466,8.277 19.878,8.277 8.764,0 17.12,-4.162 22.382,-11.135 L 102.73624,64.219616 c -2.851997,-2.85 -4.617997,-6.788 -4.617997,-11.13 0,-8.685 7.064997,-15.75 15.749997,-15.75 8.685,0 15.75,7.065 15.75,15.75 0,4.212 -1.672,8.035 -4.375,10.864 0.009,0.012 0.02,0.022 0.029,0.035 l 33.704,45.108004 c 5.26,7.04 13.646,11.243 22.435,11.243 7.48,0 14.514,-2.913 19.803,-8.203 l 20.788,-20.788004 c -3.583,-2.89 -5.884,-7.308 -5.884,-12.259 0,-8.685 7.065,-15.75 15.75,-15.75 8.685,0 15.75,7.065 15.75,15.75 0,6.851 -4.405,12.678 -10.525,14.84 z m -18.308,97.910004 c 0,-4.142 -3.358,-7.5 -7.5,-7.5 H 17.285243 c -4.142,0 -7.5000002,3.358 -7.5000002,7.5 v 18 c 0,4.142 3.3580002,7.5 7.5000002,7.5 H 211.28524 c 4.142,0 7.5,-3.358 7.5,-7.5 z"
id="path1"
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:6.7863;stroke-dasharray:none;stroke-opacity:1" /><path
d="m 240.7081,97.134808 c 0.041,0.736 -0.013,1.485 -0.198,2.229 l -16.5,66.000002 c -0.832,3.325 -3.812,5.663 -7.238,5.681 l -99,0.5 c -0.013,0 -0.025,0 -0.038,0 H 18.733108 c -3.444,0 -6.445,-2.346 -7.277,-5.688 L -5.0438922,99.606808 c -0.19,-0.764 -0.245,-1.534 -0.197,-2.289 -6.3829998,-2.011 -11.0259998,-7.984 -11.0259998,-15.023 0,-8.685 7.0649999,-15.75 15.74999973,-15.75 8.68500007,0 15.75000027,7.065 15.75000027,15.75 0,4.891 -2.241,9.267 -5.7500003,12.158 L 30.141108,115.26681 c 5.221,5.261 12.466,8.277 19.878,8.277 8.764,0 17.12,-4.162 22.382,-11.135 L 106.3511,67.424808 c -2.85199,-2.85 -4.61799,-6.788 -4.61799,-11.13 0,-8.685 7.06499,-15.75 15.74999,-15.75 8.685,0 15.75,7.065 15.75,15.75 0,4.212 -1.672,8.035 -4.375,10.864 0.009,0.012 0.02,0.022 0.029,0.035 l 33.704,45.108002 c 5.26,7.04 13.646,11.243 22.435,11.243 7.48,0 14.514,-2.913 19.803,-8.203 l 20.788,-20.788002 c -3.583,-2.89 -5.884,-7.308 -5.884,-12.259 0,-8.685 7.065,-15.75 15.75,-15.75 8.685,0 15.75,7.065 15.75,15.75 0,6.851 -4.405,12.678 -10.525,14.84 z m -18.308,97.910002 c 0,-4.142 -3.358,-7.5 -7.5,-7.5 H 20.900108 c -4.142,0 -7.5,3.358 -7.5,7.5 v 18 c 0,4.142 3.358,7.5 7.5,7.5 H 214.9001 c 4.142,0 7.5,-3.358 7.5,-7.5 z"
id="path32"
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:6.7863;stroke-dasharray:none;stroke-opacity:1" /><path
d="m 244.32296,100.34 c 0.041,0.736 -0.013,1.485 -0.198,2.229 l -16.5,66 c -0.832,3.325 -3.812,5.663 -7.238,5.681 l -99,0.5 c -0.013,0 -0.025,0 -0.038,0 H 22.347973 c -3.444,0 -6.445,-2.346 -7.277,-5.688 l -16.5000003,-66.25 c -0.19,-0.764 -0.245,-1.534 -0.197,-2.289 C -8.0090271,98.512 -12.652027,92.539 -12.652027,85.5 c 0,-8.685 7.0649998,-15.75 15.7499997,-15.75 8.6850003,0 15.7500003,7.065 15.7500003,15.75 0,4.891 -2.241,9.267 -5.75,12.158 l 20.658,20.814 c 5.221,5.261 12.466,8.277 19.878,8.277 8.764,0 17.12,-4.162 22.382,-11.135 L 109.96596,70.63 c -2.85199,-2.85 -4.61799,-6.788 -4.61799,-11.13 0,-8.685 7.06499,-15.75 15.74999,-15.75 8.685,0 15.75,7.065 15.75,15.75 0,4.212 -1.672,8.035 -4.375,10.864 0.009,0.012 0.02,0.022 0.029,0.035 l 33.704,45.108 c 5.26,7.04 13.646,11.243 22.435,11.243 7.48,0 14.514,-2.913 19.803,-8.203 l 20.788,-20.788 c -3.583,-2.89 -5.884,-7.308 -5.884,-12.259 0,-8.685 7.065,-15.75 15.75,-15.75 8.685,0 15.75,7.065 15.75,15.75 0,6.851 -4.405,12.678 -10.525,14.84 z m -18.308,97.91 c 0,-4.142 -3.358,-7.5 -7.5,-7.5 H 24.514973 c -4.142,0 -7.5,3.358 -7.5,7.5 v 18 c 0,4.142 3.358,7.5 7.5,7.5 H 218.51496 c 4.142,0 7.5,-3.358 7.5,-7.5 z"
id="path33"
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:6.7863;stroke-dasharray:none;stroke-opacity:1" /><path
d="m 244.32296,100.34 c 0.041,0.736 -0.013,1.485 -0.198,2.229 l -16.5,66 c -0.832,3.325 -3.812,5.663 -7.238,5.681 l -99,0.5 c -0.013,0 -0.025,0 -0.038,0 H 22.347973 c -3.444,0 -6.445,-2.346 -7.277,-5.688 l -16.5000003,-66.25 c -0.19,-0.764 -0.245,-1.534 -0.197,-2.289 C -8.0090271,98.512 -12.652027,92.539 -12.652027,85.5 c 0,-8.685 7.0649998,-15.75 15.7499997,-15.75 8.6850003,0 15.7500003,7.065 15.7500003,15.75 0,4.891 -2.241,9.267 -5.75,12.158 l 20.658,20.814 c 5.221,5.261 12.466,8.277 19.878,8.277 8.764,0 17.12,-4.162 22.382,-11.135 L 109.96596,70.63 c -2.85199,-2.85 -4.61799,-6.788 -4.61799,-11.13 0,-8.685 7.06499,-15.75 15.74999,-15.75 8.685,0 15.75,7.065 15.75,15.75 0,4.212 -1.672,8.035 -4.375,10.864 0.009,0.012 0.02,0.022 0.029,0.035 l 33.704,45.108 c 5.26,7.04 13.646,11.243 22.435,11.243 7.48,0 14.514,-2.913 19.803,-8.203 l 20.788,-20.788 c -3.583,-2.89 -5.884,-7.308 -5.884,-12.259 0,-8.685 7.065,-15.75 15.75,-15.75 8.685,0 15.75,7.065 15.75,15.75 0,6.851 -4.405,12.678 -10.525,14.84 z m -18.308,97.91 c 0,-4.142 -3.358,-7.5 -7.5,-7.5 H 24.514973 c -4.142,0 -7.5,3.358 -7.5,7.5 v 18 c 0,4.142 3.358,7.5 7.5,7.5 H 218.51496 c 4.142,0 7.5,-3.358 7.5,-7.5 z"
id="path34"
style="fill:#f8ff43;fill-opacity:1;stroke:#000000;stroke-width:6.7863;stroke-dasharray:none;stroke-opacity:1" /></g><g
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-opacity:1;stroke-width:6.78629989;stroke-dasharray:none"
id="g27"
transform="matrix(0.14638629,0,0,0.16509671,32.014583,-5.942102)" /></g></svg>

Before

Width:  |  Height:  |  Size: 13 KiB

+17 -8
View File
@@ -1,21 +1,30 @@
import { Routes, Route } from "react-router";
import "./styles/globals.css";
import Competitions from "./pages/Competitions";
import CompetitionPreview from './pages/CompetitionPreview'
import CompetitionSession from "./pages/CompetitionSession";
import { NavbarLayout } from "./widgets/navbar-layout";
import Competitions from "./pages/Competitions";
import CompetitionPreview from "./pages/CompetitionPreview";
import CompetitionSession from "./pages/CompetitionSession";
import LoginPage from "./pages/Login";
import { AuthLayout } from "./widgets/auth-layout";
const App = () => {
return (
<Routes>
<Route element={<NavbarLayout />}>
<Route path="/" element={<Competitions />} />
<Route path="/competition/:id" element={<CompetitionPreview />} />
</Route>
<Route
<Route path="/login" element={<LoginPage />} />
<Route element={<AuthLayout />}>
<Route element={<NavbarLayout />}>
<Route path="/" element={<Competitions />} />
<Route path="/competition/:id" element={<CompetitionPreview />} />
</Route>
<Route
path="/competition/:id/tasks/:taskId"
element={<CompetitionSession />}
/>
</Route>
</Routes>
);
};
@@ -1,71 +1,77 @@
import React, { useState } from 'react';
import React, { useState } from "react";
import { DataRush } from "@/components/ui/icons/datarush";
import { ChevronDown, User, Settings, BarChart2, LogOut } from "lucide-react";
import { Link } from "react-router";
import {
Sheet,
SheetContent,
SheetHeader,
SheetTitle,
SheetClose
import {
Sheet,
SheetContent,
SheetHeader,
SheetTitle,
SheetClose,
} from "@/components/ui/sheet";
import { useUserStore } from "@/shared/stores/user";
const Header = () => {
const user = useUserStore((state) => state.user);
const [isProfileOpen, setIsProfileOpen] = useState(false);
return (
<header className="bg-card sticky top-0 z-30 flex h-[72px] w-full items-center justify-center">
<header className="bg-card sticky top-0 z-30 flex h-[72px] w-full items-center justify-center px-4 sm:px-6">
<div className="flex w-full max-w-5xl items-center justify-between">
<Link to="/">
<DataRush />
</Link>
<div
className="flex items-center gap-1 cursor-pointer hover:opacity-80 transition-opacity px-2 py-1 rounded-md"
<div
className="flex cursor-pointer items-center gap-1 rounded-md px-2 py-1 transition-opacity hover:opacity-80"
onClick={() => setIsProfileOpen(true)}
>
<span className="text-lg font-semibold font-hse-sans">itqdev</span>
<span className="font-hse-sans text-lg font-semibold">
{user?.username}
</span>
<ChevronDown size={20} />
</div>
</div>
<Sheet open={isProfileOpen} onOpenChange={setIsProfileOpen}>
<SheetContent className="w-[300px] sm:w-[350px] p-0">
<SheetHeader className="border-b py-4 px-5">
<SheetTitle className="font-hse-sans text-lg font-medium">Профиль</SheetTitle>
<SheetContent className="w-[300px] p-0 sm:w-[350px]">
<SheetHeader className="border-b px-5 py-4">
<SheetTitle className="font-hse-sans text-lg font-medium">
Профиль
</SheetTitle>
</SheetHeader>
<div className="py-4 px-2">
<ProfileOption
icon={<User size={18} />}
label="Ваш профиль"
<div className="px-2 py-4">
<ProfileOption
icon={<User size={18} />}
label="Ваш профиль"
onClick={() => {
setIsProfileOpen(false);
}}
}}
/>
<ProfileOption
icon={<Settings size={18} />}
label="Настройки"
<ProfileOption
icon={<Settings size={18} />}
label="Настройки"
onClick={() => {
setIsProfileOpen(false);
}}
}}
/>
<ProfileOption
icon={<BarChart2 size={18} />}
label="Статистика"
<ProfileOption
icon={<BarChart2 size={18} />}
label="Статистика"
onClick={() => {
setIsProfileOpen(false);
}}
}}
/>
<div className="border-t mt-2 pt-2">
<ProfileOption
icon={<LogOut size={18} />}
label="Выйти"
<div className="mt-2 border-t pt-2">
<ProfileOption
icon={<LogOut size={18} />}
label="Выйти"
onClick={() => {
setIsProfileOpen(false);
}}
}}
/>
</div>
</div>
@@ -82,11 +88,16 @@ interface ProfileOptionProps {
className?: string;
}
const ProfileOption: React.FC<ProfileOptionProps> = ({ icon, label, onClick, className }) => {
const ProfileOption: React.FC<ProfileOptionProps> = ({
icon,
label,
onClick,
className,
}) => {
return (
<SheetClose asChild>
<button
className={`flex items-center gap-3 w-full px-3 py-2.5 rounded-md text-left transition-colors hover:bg-gray-100 ${className || ''}`}
<button
className={`flex w-full items-center gap-3 rounded-md px-3 py-2.5 text-left transition-colors hover:bg-gray-100 ${className || ""}`}
onClick={onClick}
>
<span className="text-gray-600">{icon}</span>
@@ -96,4 +107,4 @@ const ProfileOption: React.FC<ProfileOptionProps> = ({ icon, label, onClick, cla
);
};
export { Header };
export { Header };
@@ -5,7 +5,7 @@ import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/shared/lib/utils";
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-[color,box-shadow] disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
"inline-flex cursor-pointer items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-[color,box-shadow] disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
{
variants: {
variant: {
@@ -20,9 +20,9 @@ const buttonVariants = cva(
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-12 px-5 py-3 has-[>svg]:px-3 text-lg font-semibold",
default: "h-11 px-4 text-base font-semibold rounded-xl",
lg: "h-12 px-5 py-3 has-[>svg]:px-3 text-lg font-semibold",
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
icon: "size-9",
},
},
@@ -0,0 +1,5 @@
import { Loader } from "lucide-react";
export const Spinner = (props: React.ComponentProps<typeof Loader>) => (
<Loader className="animate-spin" {...props} />
);
@@ -2,7 +2,7 @@ import { useState } from "react";
import { useParams, Link, useNavigate } from "react-router-dom";
import { Button } from "@/components/ui/button";
import { ArrowLeft } from "lucide-react";
import ReactMarkdown from 'react-markdown';
import ReactMarkdown from "react-markdown";
import { Competition } from "@/shared/types";
import { mockCompetitions, mockTasks } from "@/shared/mocks/mocks";
@@ -14,7 +14,7 @@ const CompetitionPage = () => {
);
const handleContinue = () => {
if (competition?.id) {
if (competition?.id) {
if (mockTasks && mockTasks.length > 0) {
const firstTaskId = mockTasks[0].id;
navigate(`/competition/${competition.id}/tasks/${firstTaskId}`);
@@ -43,19 +43,19 @@ const CompetitionPage = () => {
/>
</div>
<div className="flex gap-8">
<div className="flex flex-col-reverse gap-8 md:flex-row">
<div className="flex flex-1 flex-col gap-5">
<h1 className="text-[34px] leading-11 font-semibold text-balance">
{competition.name}
</h1>
<div className="text-xl leading-10 font-normal prose prose-lg max-w-none">
<ReactMarkdown>
{competition.description || ''}
</ReactMarkdown>
<div className="prose prose-lg max-w-none text-xl leading-10 font-normal">
<ReactMarkdown>{competition.description || ""}</ReactMarkdown>
</div>
</div>
<div className="w-96 *:w-full">
<Button onClick={handleContinue}>Продолжить</Button>
<div className="w-full *:w-full md:w-96">
<Button size={"lg"} onClick={handleContinue}>
Продолжить
</Button>
</div>
</div>
</div>
@@ -63,4 +63,4 @@ const CompetitionPage = () => {
);
};
export default CompetitionPage;
export default CompetitionPage;
@@ -52,4 +52,4 @@ const TaskContent: React.FC<TaskContentProps> = ({ task }) => {
);
};
export default TaskContent;
export default TaskContent;
@@ -6,7 +6,7 @@ import CompetitionHeader from "./components/CompetitionHeader";
import TaskContent from "./components/TaskContent";
import TaskSolution from "./modules/TaskSolution";
const CompetitionSessionPage = () => {
const CompetitionSession = () => {
const { id, taskId } = useParams<{ id: string; taskId?: string }>();
const [tasks] = useState<Task[]>(mockTasks);
const [answer, setAnswer] = useState("");
@@ -21,9 +21,6 @@ const CompetitionSessionPage = () => {
console.log("Submitting answer:", answer);
};
const handleHistoryClick = () => {
console.log("View history");
};
return (
<div className="flex flex-col min-h-screen">
@@ -44,7 +41,6 @@ const CompetitionSessionPage = () => {
answer={answer}
setAnswer={setAnswer}
onSubmit={handleSubmit}
onHistoryClick={handleHistoryClick}
/>
</div>
) : (
@@ -60,4 +56,4 @@ const CompetitionSessionPage = () => {
);
};
export default CompetitionSessionPage;
export default CompetitionSession;
@@ -5,13 +5,11 @@ import { Solution } from "@/shared/types";
import { mockSolutions } from '@/shared/mocks/mocks';
interface ActionButtonsProps {
onHistoryClick: () => void;
onSubmit: () => void;
solutionHistory?: Solution[];
}
const ActionButtons: React.FC<ActionButtonsProps> = ({
onHistoryClick,
onSubmit,
solutionHistory = mockSolutions
}) => {
@@ -19,7 +17,6 @@ const ActionButtons: React.FC<ActionButtonsProps> = ({
const handleHistoryClick = () => {
setIsHistoryOpen(true);
onHistoryClick();
};
return (
@@ -1,6 +1,6 @@
import React, { useRef, useEffect, useState } from 'react';
import * as monaco from 'monaco-editor';
import { Copy, Check } from 'lucide-react'; // Import Lucide React icons
import { Copy, Check } from 'lucide-react';
interface CodeSolutionProps {
answer: string;
@@ -41,4 +41,4 @@ const SolutionStatus: React.FC<SolutionStatusProps> = ({ solution }) => {
);
};
export default SolutionStatus;
export default SolutionStatus;
@@ -12,7 +12,7 @@ interface TaskSolutionProps {
answer: string;
setAnswer: (value: string) => void;
onSubmit: () => void;
onHistoryClick: () => void;
}
const TaskSolution: React.FC<TaskSolutionProps> = ({
@@ -21,7 +21,6 @@ const TaskSolution: React.FC<TaskSolutionProps> = ({
answer,
setAnswer,
onSubmit,
onHistoryClick,
}) => {
const [selectedFile, setSelectedFile] = useState<File | null>(null);
const fileInputRef = useRef<HTMLInputElement>(null);
@@ -46,7 +45,7 @@ const TaskSolution: React.FC<TaskSolutionProps> = ({
<CodeSolution answer={answer} setAnswer={setAnswer} />
)}
<ActionButtons onHistoryClick={onHistoryClick} onSubmit={onSubmit} />
<ActionButtons onSubmit={onSubmit} />
</div>
);
};
@@ -11,13 +11,9 @@ export function CompetitionCard({
competition,
className,
}: CompetitionCardProps) {
return (
<Card
className={cn(
"aspect-square h-full max-h-80 w-auto overflow-hidden",
className,
)}
className={cn("aspect-square h-full w-auto overflow-hidden", className)}
>
<div className="relative h-full overflow-hidden">
<img
@@ -40,7 +36,9 @@ export function CompetitionCard({
</>
)}
</div>
<h3 className="text-xl font-semibold">{competition.name}</h3>
<h3 className="line-clamp-2 text-xl font-semibold">
{competition.name}
</h3>
</div>
</CardContent>
</Card>
@@ -25,7 +25,7 @@ const CompetitionsPage = () => {
);
return (
<div className="flex flex-col gap-8">
<div className="flex flex-col gap-6 sm:gap-8">
<Section>
<SectionHeader>
<SectionTitle>Мои события</SectionTitle>
@@ -50,11 +50,15 @@ const CompetitionsPage = () => {
};
const Section = ({ children }: { children: React.ReactNode }) => {
return <div className="flex flex-col gap-8">{children}</div>;
return <div className="flex flex-col gap-6 sm:gap-8">{children}</div>;
};
const SectionHeader = ({ children }: { children: React.ReactNode }) => {
return <div className="flex h-[58px] items-center gap-2">{children}</div>;
return (
<div className="flex min-h-[58px] flex-col items-center justify-center gap-4 sm:flex-row sm:gap-2">
{children}
</div>
);
};
const SectionTitle = ({ children }: { children: React.ReactNode }) => {
@@ -8,7 +8,7 @@ interface CompetitionGridProps {
export function CompetitionGrid({ competitions }: CompetitionGridProps) {
return (
<div className="grid grid-cols-3 gap-9">
<div className="grid grid-cols-1 gap-5 sm:grid-cols-2 md:grid-cols-3 md:gap-7 lg:gap-9">
{competitions.map((competition) => (
<Link key={competition.id} to={`/competition/${competition.id}`}>
<CompetitionCard competition={competition} />
@@ -0,0 +1,22 @@
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
label?: string;
error?: string;
}
export const Input = ({ label, error, id, ...props }: InputProps) => {
return (
<div className="flex w-full flex-col items-stretch gap-2">
{label && (
<label htmlFor={id} className="text-base font-semibold">
{label}
</label>
)}
<input
id={id}
className="bg-card h-12 rounded-xl border px-4 text-base"
{...props}
/>
{error && <span className="text-red-500">{error}</span>}
</div>
);
};
@@ -0,0 +1,48 @@
import { DataRush } from "@/components/ui/icons/datarush";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { LoginTab } from "./modules/LoginTab";
import { SignupTab } from "./modules/SignupTab";
import React from "react";
import { getToken } from "@/shared/token";
import { useNavigate } from "react-router";
const LoginPage = () => {
const navigate = useNavigate();
React.useEffect(() => {
const token = getToken();
if (token) {
navigate("/");
}
}, []);
return (
<div className="flex h-screen flex-col items-center gap-10 px-4 py-10 sm:gap-18 sm:py-18">
<DataRush size={52} />
<div className="flex w-full max-w-96 flex-col items-center gap-7">
<h1 className="text-center text-4xl font-semibold">
Добро пожаловать!
</h1>
<Tabs
defaultValue="login"
className="flex w-full flex-col items-center gap-7"
>
<TabsList>
<TabsTrigger value="login">Вход</TabsTrigger>
<TabsTrigger value="signup">Регистрация</TabsTrigger>
</TabsList>
<TabsContent value="login" asChild>
<LoginTab />
</TabsContent>
<TabsContent value="signup" asChild>
<SignupTab />
</TabsContent>
</Tabs>
</div>
</div>
);
};
export default LoginPage;
@@ -0,0 +1,74 @@
import { Button } from "@/components/ui/button";
import { Input } from "../components/input";
import { login } from "@/shared/api/auth";
import { saveToken } from "@/shared/token";
import { useNavigate } from "react-router";
import { useState } from "react";
import { Spinner } from "@/components/ui/spinner";
import { ApiError } from "@/shared/api";
export const LoginTab = () => {
const navigate = useNavigate();
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(false);
const loginAction = async (
formData: FormData,
e: React.FormEvent<HTMLFormElement>,
) => {
e.preventDefault();
setLoading(true);
const email = formData.get("email");
const password = formData.get("password");
if (!email || !password) {
setError("Неверное имя пользователя или пароль");
setLoading(false);
return;
}
try {
const token = await login({
email: email.toString(),
password: password.toString(),
});
saveToken(token.token);
navigate("/");
} catch (e) {
if (e instanceof ApiError && (e.status === 400 || e.status === 401)) {
setError("Неверное имя пользователя или пароль");
} else {
setError("Произошла непредвиденная ошибка");
}
}
setLoading(false);
};
return (
<form
className="flex w-full flex-col items-stretch gap-8"
onSubmit={(e) => loginAction(new FormData(e.currentTarget), e)}
>
<div className="flex flex-col items-stretch gap-5">
<Input
id="email"
name="email"
label="Почта"
placeholder="vdeniske@megazord.pobeda"
/>
<Input
id="password"
name="password"
label="Пароль"
placeholder="Введите пароль"
type="password"
/>
</div>
{error && <span className="text-red-500">{error}</span>}
<Button type="submit">{loading ? <Spinner size={16} /> : "Войти"}</Button>
</form>
);
};
@@ -0,0 +1,134 @@
import { Button } from "@/components/ui/button";
import { Input } from "../components/input";
import { z } from "zod";
import React from "react";
import { signup } from "@/shared/api/auth";
import { Spinner } from "@/components/ui/spinner";
import { saveToken } from "@/shared/token";
import { useNavigate } from "react-router";
import { ApiError } from "@/shared/api";
const signupSchema = z.object({
email: z.string().email({ message: "Некорректная почта" }).trim(),
username: z
.string()
.min(1, { message: "Имя пользователя должно быть не меньше 1 знака" })
.max(50, { message: "Имя пользователя должно быть не больше 50 знаков" })
.trim(),
password: z
.string()
.min(8, { message: "Пароль должен быть не меньше 8 знаков" })
.regex(/[a-zA-Z]/, {
message: "Пароль должен содержать хотя бы одну букву",
})
.regex(/[0-9]/, { message: "Пароль должен содержать хотя бы одну цифру" })
.regex(/[^a-zA-Z0-9]/, {
message: "Пароль должен содержать хотя бы один специальный символ",
})
.trim(),
});
interface SignupFormErrors {
username?: string[];
email?: string[];
password?: string[];
message?: string;
}
export const SignupTab = () => {
const navigate = useNavigate();
const [errors, setErrors] = React.useState<SignupFormErrors | null>(null);
const [loading, setLoading] = React.useState(false);
const signupAction = async (
formData: FormData,
e: React.FormEvent<HTMLFormElement>,
) => {
e.preventDefault();
setLoading(true);
const validatedFields = signupSchema.safeParse({
email: formData.get("email"),
username: formData.get("username"),
password: formData.get("password"),
});
if (!validatedFields.success) {
setErrors(validatedFields.error.flatten().fieldErrors);
setLoading(false);
return;
}
try {
const token = await signup({
...validatedFields.data,
});
saveToken(token.token);
navigate("/");
} catch (e) {
if (e instanceof ApiError) {
if (e.status === 400) {
setErrors({ message: "Неверные данные" });
} else if (e.status === 409) {
setErrors({
message:
"Пользователь с такой почтой или именем пользователя уже существует",
});
} else {
setErrors({ message: "Произошла непредвиденная ошибка" });
}
} else {
setErrors({ message: "Произошла непредвиденная ошибка" });
}
}
setLoading(false);
};
return (
<form
className="flex w-full flex-col items-stretch gap-8"
onSubmit={(e) => signupAction(new FormData(e.currentTarget), e)}
>
<div className="flex flex-col items-stretch gap-5">
<Input
id="email"
name="email"
label="Почта"
placeholder="vdeniske@megazord.pobeda"
error={errors?.email?.at(0)}
onChange={() => setErrors(null)}
/>
<Input
id="username"
name="username"
label="Имя пользователя"
placeholder="Введите имя пользователя"
error={errors?.username?.at(0)}
onChange={() => setErrors(null)}
/>
<Input
id="password"
name="password"
label="Пароль"
placeholder="Введите пароль"
type="password"
error={errors?.password?.at(0)}
onChange={() => setErrors(null)}
/>
</div>
{errors?.message && (
<span className="text-red-500">{errors.message}</span>
)}
<Button type="submit" onClick={() => setErrors(null)}>
{loading ? <Spinner size={16} /> : "Зарегистрироваться"}
</Button>
</form>
);
};
+23
View File
@@ -0,0 +1,23 @@
import { authFetch } from ".";
interface AuthResponse {
token: string;
}
export const signup = async (body: {
email: string;
username: string;
password: string;
}) => {
return await authFetch<AuthResponse>("/sign-up", {
method: "POST",
body,
});
};
export const login = async (body: { email: string; password: string }) => {
return await authFetch<AuthResponse>("/sign-in", {
method: "POST",
body,
});
};
+38
View File
@@ -0,0 +1,38 @@
import { ofetch } from "ofetch";
import { getToken, removeToken } from "../token";
const BASE_URL = import.meta.env.VITE_API_ENDPOINT;
export class ApiError extends Error {
response: Response;
status: number;
constructor(response: Response) {
super(response.statusText);
this.response = response;
this.status = response.status;
}
}
export const authFetch = ofetch.create({
baseURL: BASE_URL,
async onResponseError({ response }) {
throw new ApiError(response);
},
});
export const apiFetch = ofetch.create({
baseURL: BASE_URL,
async onRequest({ options }) {
options.headers.set("Authorization", "Bearer " + getToken());
},
async onResponseError({ response }) {
if (response.status === 401) {
removeToken();
window.location.href = "/login";
return;
}
throw new ApiError(response);
},
});
+6
View File
@@ -0,0 +1,6 @@
import { apiFetch } from ".";
import { User } from "../types/user";
export const getCurrentUser = async () => {
return await apiFetch<User>("/me");
};
@@ -0,0 +1,23 @@
import { create } from "zustand";
import { User } from "../types/user";
import { getCurrentUser } from "../api/user";
interface UserState {
user: User | null;
loading: boolean;
fetchUser: () => Promise<void>;
}
const useUserStore = create<UserState>((set) => ({
user: null,
loading: true,
fetchUser: async () => {
set({ loading: true });
const user = await getCurrentUser();
set({ user, loading: false });
},
}));
export { useUserStore };
+15
View File
@@ -0,0 +1,15 @@
import Cookie from "js-cookie";
export const getToken = () => {
return Cookie.get("token");
};
export const saveToken = (token: string) => {
Cookie.set("token", token, {
expires: 30,
});
};
export const removeToken = () => {
Cookie.remove("token");
};
@@ -0,0 +1,5 @@
export interface User {
id: string;
email: string;
username: string;
}
@@ -0,0 +1,17 @@
import { useUserStore } from "@/shared/stores/user";
import React from "react";
import { Outlet } from "react-router";
export const AuthLayout = () => {
const fetchUser = useUserStore((state) => state.fetchUser);
React.useEffect(() => {
async function fetchData() {
await fetchUser();
}
fetchData();
}, []);
return <Outlet />;
};
@@ -5,8 +5,10 @@ const NavbarLayout = () => {
return (
<>
<Header />
<div className="m-auto mt-6 w-full max-w-5xl">
<Outlet />
<div className="px-4 sm:px-6">
<div className="m-auto mt-6 w-full max-w-5xl">
<Outlet />
</div>
</div>
</>
);