mirror of
https://gitlab.com/megazordpobeda/DataRush.git
synced 2026-05-23 22:37:10 +00:00
Merge remote-tracking branch 'origin/master'
This commit is contained in:
@@ -166,7 +166,6 @@ http {
|
||||
}
|
||||
|
||||
location /admin/grafana/ {
|
||||
rewrite ^/admin/grafana/(.*) /$1 break;
|
||||
proxy_pass http://grafana:3000/;
|
||||
|
||||
proxy_set_header Host $http_host;
|
||||
|
||||
@@ -4,6 +4,7 @@ from typing import Any
|
||||
import jwt
|
||||
from django.conf import settings
|
||||
from django.http import HttpRequest
|
||||
from ninja.errors import AuthenticationError
|
||||
from ninja.security import HttpBearer
|
||||
|
||||
from apps.user.models import User
|
||||
@@ -11,9 +12,12 @@ from apps.user.models import User
|
||||
|
||||
class BearerAuth(HttpBearer):
|
||||
def authenticate(self, request: HttpRequest, token: str) -> Any | None:
|
||||
data = jwt.decode(token, settings.SECRET_KEY, algorithms=["HS256"])
|
||||
if data["exp"] < datetime.datetime.now().timestamp():
|
||||
return None
|
||||
try:
|
||||
data = jwt.decode(token, settings.SECRET_KEY, algorithms=["HS256"])
|
||||
if data["exp"] < datetime.datetime.now().timestamp():
|
||||
return None
|
||||
except Exception:
|
||||
raise AuthenticationError
|
||||
|
||||
user = User.objects.get(id=data["id"])
|
||||
return user
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
from typing import Literal
|
||||
from uuid import UUID
|
||||
|
||||
from django.http import HttpRequest
|
||||
from ninja import ModelSchema, Schema
|
||||
|
||||
from apps.review.models import Reviewer, Review
|
||||
from apps.review.models import Review, Reviewer
|
||||
from apps.task.models import CompetitionTaskSubmission
|
||||
|
||||
|
||||
@@ -34,4 +33,6 @@ class SubmissionsOut(Schema):
|
||||
|
||||
@staticmethod
|
||||
def resolve_submissions(self, context) -> list[SubmissionOut]:
|
||||
return list(Review.objects.filter(reviewer=context.get("request").auth))
|
||||
return list(
|
||||
Review.objects.filter(reviewer=context.get("request").auth)
|
||||
)
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import logging
|
||||
from http import HTTPStatus as status
|
||||
from uuid import UUID
|
||||
|
||||
@@ -8,7 +7,6 @@ from ninja import Router
|
||||
|
||||
from api.v1 import schemas as global_schemas
|
||||
from api.v1.review import schemas
|
||||
from api.v1.task.schemas import TaskSubmissionIn
|
||||
from apps.task.models import CompetitionTaskSubmission
|
||||
|
||||
router = Router(tags=["review"])
|
||||
@@ -19,9 +17,11 @@ router = Router(tags=["review"])
|
||||
response={
|
||||
status.OK: schemas.SubmissionsOut,
|
||||
},
|
||||
description="Список отправок, на проверку которых назначен ревьюер"
|
||||
description="Список отправок, на проверку которых назначен ревьюер",
|
||||
)
|
||||
def get_submissions(request: HttpRequest, token: str) -> tuple[status, schemas.SubmissionsOut]:
|
||||
def get_submissions(
|
||||
request: HttpRequest, token: str
|
||||
) -> tuple[status, schemas.SubmissionsOut]:
|
||||
return status.OK, schemas.SubmissionsOut()
|
||||
|
||||
|
||||
@@ -36,12 +36,15 @@ def get_submissions(request: HttpRequest, token: str) -> tuple[status, schemas.S
|
||||
def get_reviewer_profile(request: HttpRequest, token: str):
|
||||
return status.OK, request.auth
|
||||
|
||||
|
||||
@router.get(
|
||||
"{token}/submissions/{submition_id}",
|
||||
response={
|
||||
status.OK: schemas.SubmissionOut,
|
||||
},
|
||||
)
|
||||
def get_submission(request: HttpRequest, token: str, submition_id: UUID) -> tuple[status, schemas.SubmissionsOut]:
|
||||
def get_submission(
|
||||
request: HttpRequest, token: str, submition_id: UUID
|
||||
) -> tuple[status, schemas.SubmissionsOut]:
|
||||
submission = get_object_or_404(CompetitionTaskSubmission, id=submition_id)
|
||||
return status.OK, submission
|
||||
|
||||
@@ -13,9 +13,9 @@ from api.v1.task.schemas import (
|
||||
)
|
||||
from apps.competition.models import State
|
||||
from apps.task.models import (
|
||||
CompetitionTaskSubmission,
|
||||
Competition,
|
||||
CompetitionTask,
|
||||
CompetitionTaskSubmission,
|
||||
)
|
||||
|
||||
router = Router(tags=["competition"])
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
@@ -45,7 +46,6 @@ def sign_up(request, data: RegisterSchema):
|
||||
)
|
||||
def sign_in(request, data: LoginSchema):
|
||||
user = User.objects.filter(email=data.email).first()
|
||||
print(check_password(data.password, user.password))
|
||||
if not user:
|
||||
raise AuthenticationError
|
||||
if not check_password(data.password, user.password):
|
||||
|
||||
@@ -6,7 +6,17 @@ from apps.task.admin import CompetitionTaskInline
|
||||
|
||||
@admin.register(Competition)
|
||||
class CompetitionAdmin(admin.ModelAdmin):
|
||||
list_display = ("title", "end_date", "type",)
|
||||
search_fields = ("title", "description",)
|
||||
list_filter = ("type", "participation_type",)
|
||||
list_display = (
|
||||
"title",
|
||||
"end_date",
|
||||
"type",
|
||||
)
|
||||
search_fields = (
|
||||
"title",
|
||||
"description",
|
||||
)
|
||||
list_filter = (
|
||||
"type",
|
||||
"participation_type",
|
||||
)
|
||||
inlines = [CompetitionTaskInline]
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
from datetime import datetime
|
||||
|
||||
from django.db import models
|
||||
from tinymce.models import HTMLField
|
||||
|
||||
from apps.core.models import BaseModel
|
||||
from apps.user.models import User
|
||||
@@ -49,8 +48,6 @@ class Competition(BaseModel):
|
||||
def __str__(self):
|
||||
return self.title
|
||||
|
||||
|
||||
|
||||
class Meta:
|
||||
verbose_name = "соревнование"
|
||||
verbose_name_plural = "соревнования"
|
||||
|
||||
@@ -8,7 +8,7 @@ from django.core.management.base import BaseCommand
|
||||
from django.utils import timezone
|
||||
|
||||
from apps.competition.models import Competition, State
|
||||
from apps.task.models import CompetitionTaskSubmission, CompetitionTask
|
||||
from apps.task.models import CompetitionTask, CompetitionTaskSubmission
|
||||
from apps.user.models import User, UserRole
|
||||
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ class Reviewer(BaseModel):
|
||||
|
||||
token = models.CharField(max_length=100)
|
||||
|
||||
|
||||
class Review(BaseModel):
|
||||
class ReviewStatusChoices(models.TextChoices):
|
||||
NOT_CHECKED = "not_checked"
|
||||
@@ -17,6 +18,10 @@ class Review(BaseModel):
|
||||
CHECKED = "checked"
|
||||
|
||||
reviewer = models.ForeignKey(Reviewer, on_delete=models.CASCADE)
|
||||
submission = models.ForeignKey(CompetitionTaskSubmission, on_delete=models.CASCADE)
|
||||
submission = models.ForeignKey(
|
||||
CompetitionTaskSubmission, on_delete=models.CASCADE
|
||||
)
|
||||
|
||||
state = models.CharField(choices=ReviewStatusChoices.choices, max_length=11)
|
||||
state = models.CharField(
|
||||
choices=ReviewStatusChoices.choices, max_length=11
|
||||
)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from django.db import models
|
||||
from django.contrib.auth.hashers import check_password, make_password
|
||||
from django.db import models
|
||||
|
||||
from apps.core.models import BaseModel
|
||||
|
||||
|
||||
@@ -463,7 +463,7 @@ TINYMCE_DEFAULT_CONFIG = {
|
||||
"alignright alignjustify | bullist numlist outdent indent | "
|
||||
"removeformat | help",
|
||||
"skin": "oxide-dark",
|
||||
"content_css": "dark"
|
||||
"content_css": "dark",
|
||||
}
|
||||
|
||||
# GUID
|
||||
|
||||
@@ -13,7 +13,7 @@ admin.site.index_title = "DataRush"
|
||||
|
||||
urlpatterns = [
|
||||
# tinymce
|
||||
path('tinymce/', include('tinymce.urls')),
|
||||
path("tinymce/", include("tinymce.urls")),
|
||||
# Admin urls
|
||||
path("admin/", admin.site.urls),
|
||||
# API urls
|
||||
|
||||
+46
-34
@@ -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=="],
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 |
@@ -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 " /><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 " /><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 " /><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 " /><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 " /><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 |
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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,
|
||||
});
|
||||
};
|
||||
@@ -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,
|
||||
headers: {
|
||||
Authorization: "Bearer " + getToken(),
|
||||
},
|
||||
async onResponseError({ response }) {
|
||||
if (response.status === 401) {
|
||||
removeToken();
|
||||
window.location.href = "/login";
|
||||
return;
|
||||
}
|
||||
|
||||
throw new ApiError(response);
|
||||
},
|
||||
});
|
||||
@@ -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 };
|
||||
@@ -0,0 +1,17 @@
|
||||
import Cookie from "js-cookie";
|
||||
|
||||
export const getToken = () => {
|
||||
return Cookie.get("token");
|
||||
};
|
||||
|
||||
export const saveToken = (token: string) => {
|
||||
Cookie.set("token", token, {
|
||||
secure: true,
|
||||
sameSite: "Strict",
|
||||
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>
|
||||
</>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user