diff --git a/services/backend/api/v1/auth.py b/services/backend/api/v1/auth.py index bcc8870..1d36ce9 100644 --- a/services/backend/api/v1/auth.py +++ b/services/backend/api/v1/auth.py @@ -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 diff --git a/services/backend/api/v1/user/views.py b/services/backend/api/v1/user/views.py index 477752d..ce2588e 100644 --- a/services/backend/api/v1/user/views.py +++ b/services/backend/api/v1/user/views.py @@ -1,5 +1,6 @@ from http import HTTPStatus as status +from django.contrib.auth.hashers import check_password from django.shortcuts import get_object_or_404 from ninja import Router from ninja.errors import AuthenticationError @@ -46,7 +47,7 @@ def sign_in(request, data: LoginSchema): user = User.objects.filter(email=data.email).first() if not user: raise AuthenticationError - if not user.check_password(data.password): + if not check_password(data.password, user.password): raise AuthenticationError token = BearerAuth.generate_jwt(user) diff --git a/services/frontend/src/components/layout/header.tsx b/services/frontend/src/components/layout/header.tsx index f9a7264..5bc2502 100644 --- a/services/frontend/src/components/layout/header.tsx +++ b/services/frontend/src/components/layout/header.tsx @@ -1,21 +1,99 @@ +import React, { useState } from 'react'; import { DataRush } from "@/components/ui/icons/datarush"; -import { ChevronDown } from "lucide-react"; +import { ChevronDown, User, Settings, BarChart2, LogOut } from "lucide-react"; import { Link } from "react-router"; +import { + Sheet, + SheetContent, + SheetHeader, + SheetTitle, + SheetClose +} from "@/components/ui/sheet"; const Header = () => { + const [isProfileOpen, setIsProfileOpen] = useState(false); + return (
-
- itqdev +
setIsProfileOpen(true)} + > + itqdev
+ + + + + Профиль + + +
+ } + label="Ваш профиль" + onClick={() => { + setIsProfileOpen(false); + }} + /> + + } + label="Настройки" + onClick={() => { + setIsProfileOpen(false); + }} + /> + + } + label="Статистика" + onClick={() => { + setIsProfileOpen(false); + }} + /> + +
+ } + label="Выйти" + onClick={() => { + setIsProfileOpen(false); + }} + /> +
+
+
+
); }; -export { Header }; +interface ProfileOptionProps { + icon: React.ReactNode; + label: string; + onClick: () => void; + className?: string; +} + +const ProfileOption: React.FC = ({ icon, label, onClick, className }) => { + return ( + + + + ); +}; + +export { Header }; \ No newline at end of file diff --git a/services/frontend/src/pages/CompetitionSession/modules/TaskSolution/components/SolutionStatus/index.tsx b/services/frontend/src/pages/CompetitionSession/modules/TaskSolution/components/SolutionStatus/index.tsx index 02b5c8d..008ff8e 100644 --- a/services/frontend/src/pages/CompetitionSession/modules/TaskSolution/components/SolutionStatus/index.tsx +++ b/services/frontend/src/pages/CompetitionSession/modules/TaskSolution/components/SolutionStatus/index.tsx @@ -9,15 +9,15 @@ interface SolutionStatusProps { const SolutionStatus: React.FC = ({ solution }) => { const getStatusText = (status: TaskStatus, score?: number, maxScore?: number) => { switch (status) { - case 'checking': + case TaskStatus.Checking: return 'На проверке'; - case 'wrong': + case TaskStatus.Wrong: return 'Неверный ответ'; - case 'correct': + case TaskStatus.Correct: return `Зачтено ${maxScore}/${maxScore} баллов`; - case 'partial': + case TaskStatus.Partial: return `Зачтено ${score}/${maxScore} баллов`; - case 'uncleared': + case TaskStatus.Uncleared: return 'Не решено'; default: return ''; diff --git a/services/frontend/src/pages/CompetitionSession/utils/utils.ts b/services/frontend/src/pages/CompetitionSession/utils/utils.ts index 22e5420..9ba336e 100644 --- a/services/frontend/src/pages/CompetitionSession/utils/utils.ts +++ b/services/frontend/src/pages/CompetitionSession/utils/utils.ts @@ -1,21 +1,21 @@ import { TaskStatus } from "@/shared/types"; const getTaskBgColor = (status: TaskStatus): string => { switch (status) { - case "uncleared": return "bg-[var(--color-task-uncleared)]"; - case "checking": return "bg-[var(--color-task-checking)]"; - case "correct": return "bg-[var(--color-task-correct)]"; - case "partial": return "bg-[var(--color-task-partial)]"; - case "wrong": return "bg-[var(--color-task-wrong)]"; + case TaskStatus.Uncleared: return "bg-[var(--color-task-uncleared)]"; + case TaskStatus.Checking: return "bg-[var(--color-task-checking)]"; + case TaskStatus.Correct: return "bg-[var(--color-task-correct)]"; + case TaskStatus.Partial: return "bg-[var(--color-task-partial)]"; + case TaskStatus.Wrong: return "bg-[var(--color-task-wrong)]"; } }; const getTaskTextColor = (status: TaskStatus): string => { switch (status) { - case "uncleared": return "text-[var(--color-task-text-uncleared)]"; - case "checking": return "text-[var(--color-task-text-checking)]"; - case "correct": return "text-[var(--color-task-text-correct)]"; - case "partial": return "text-[var(--color-task-text-partial)]"; - case "wrong": return "text-[var(--color-task-text-wrong)]"; + case TaskStatus.Uncleared: return "text-[var(--color-task-text-uncleared)]"; + case TaskStatus.Checking: return "text-[var(--color-task-text-checking)]"; + case TaskStatus.Correct: return "text-[var(--color-task-text-correct)]"; + case TaskStatus.Partial: return "text-[var(--color-task-text-partial)]"; + case TaskStatus.Wrong: return "text-[var(--color-task-text-wrong)]"; } }; diff --git a/services/frontend/src/shared/mocks/mocks.ts b/services/frontend/src/shared/mocks/mocks.ts index d7a543e..b7e2525 100644 --- a/services/frontend/src/shared/mocks/mocks.ts +++ b/services/frontend/src/shared/mocks/mocks.ts @@ -1,4 +1,4 @@ -import { Competition, CompetitionStatus, Solution, Task } from "../types"; +import { Competition, CompetitionStatus, Solution, Task, TaskStatus } from "../types"; const mockCompetitions: Competition[] = [ { @@ -56,49 +56,49 @@ const mockTasks: Task[] = [ { id: "1", number: "1.1", - status: "uncleared", + status: TaskStatus.Uncleared, solutionType: "input" }, { id: "2", number: "1.2", - status: "checking", + status: TaskStatus.Checking, solutionType: "file" }, { id: "3", number: "1.3", - status: "correct", + status: TaskStatus.Correct, solutionType: "code" }, { id: "4", number: "2.1", - status: "partial", + status: TaskStatus.Partial, solutionType: "input" }, { id: "5", number: "2.2", - status: "wrong", + status: TaskStatus.Wrong, solutionType: "file" }, { id: "6", number: "2.3", - status: "uncleared", + status: TaskStatus.Uncleared, solutionType: "code" }, { id: "7", number: "3.1", - status: "checking", + status: TaskStatus.Checking, solutionType: "file" }, { id: "8", number: "3.2", - status: "correct", + status: TaskStatus.Correct, solutionType: "input" }, ]; @@ -107,26 +107,26 @@ const mockTasks: Task[] = [ const mockSolutions: Solution[] = [ { id: '1', - status: 'wrong', + status: TaskStatus.Wrong, date: '1 марта, 08:41', }, { id: '2', - status: 'partial', + status: TaskStatus.Partial, score: 5, maxScore: 10, date: '28 февраля, 15:22', }, { id: '3', - status: 'correct', + status: TaskStatus.Correct, score: 0, maxScore: 10, date: '27 февраля, 12:10', }, { id: '4', - status: 'checking', + status: TaskStatus.Checking, date: '1 марта, 08:41', }, diff --git a/services/frontend/src/shared/types.ts b/services/frontend/src/shared/types.ts index 16741c4..6645a59 100644 --- a/services/frontend/src/shared/types.ts +++ b/services/frontend/src/shared/types.ts @@ -4,6 +4,14 @@ enum CompetitionStatus { Completed = "Завершено", } +enum TaskStatus { + Uncleared = "uncleared", + Checking = "checking", + Correct = "correct", + Partial = "partial", + Wrong = "wrong" +} + interface Competition { id: string; name: string; @@ -13,7 +21,6 @@ interface Competition { description?: string; } -type TaskStatus = "uncleared" | "checking" | "correct" | "partial" | "wrong"; type SolutionType = "input" | "file" | "code"; interface Solution { @@ -30,5 +37,5 @@ interface Task { solutionType: SolutionType; } -export { CompetitionStatus }; -export type { Solution, Competition, TaskStatus, Task }; +export { CompetitionStatus, TaskStatus }; +export type { Solution, Competition, Task }; diff --git a/services/frontend/src/widgets/Navbar/index.tsx b/services/frontend/src/widgets/Navbar/index.tsx deleted file mode 100644 index a1b681a..0000000 --- a/services/frontend/src/widgets/Navbar/index.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { ChevronDown } from "lucide-react"; - -const Navbar = () => { - return ( - - ); -}; - - -export default Navbar