fin
This commit is contained in:
parent
69382f2f4c
commit
bf919daaf8
20
package-lock.json
generated
20
package-lock.json
generated
@ -16,6 +16,7 @@
|
|||||||
"axios": "^1.13.2",
|
"axios": "^1.13.2",
|
||||||
"react": "^19.1.1",
|
"react": "^19.1.1",
|
||||||
"react-dom": "^19.1.1",
|
"react-dom": "^19.1.1",
|
||||||
|
"react-qr-code": "^2.0.18",
|
||||||
"react-router-dom": "^7.9.5",
|
"react-router-dom": "^7.9.5",
|
||||||
"react-toastify": "^11.0.5"
|
"react-toastify": "^11.0.5"
|
||||||
},
|
},
|
||||||
@ -3797,6 +3798,12 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/qr.js": {
|
||||||
|
"version": "0.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/qr.js/-/qr.js-0.0.0.tgz",
|
||||||
|
"integrity": "sha512-c4iYnWb+k2E+vYpRimHqSu575b1/wKl4XFeJGpFmrJQz5I88v9aY2czh7s0w36srfCM1sXgC/xpoJz5dJfq+OQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/queue-microtask": {
|
"node_modules/queue-microtask": {
|
||||||
"version": "1.2.3",
|
"version": "1.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
||||||
@ -3845,6 +3852,19 @@
|
|||||||
"integrity": "sha512-x3Ax3kNSMIIkyVYhWPyO09bu0uttcAIoecO/um/rKGQ4EltYWVYtyiGkS/3xMynrbVQdS69Jhlv8FXUEZehlzA==",
|
"integrity": "sha512-x3Ax3kNSMIIkyVYhWPyO09bu0uttcAIoecO/um/rKGQ4EltYWVYtyiGkS/3xMynrbVQdS69Jhlv8FXUEZehlzA==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/react-qr-code": {
|
||||||
|
"version": "2.0.18",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-qr-code/-/react-qr-code-2.0.18.tgz",
|
||||||
|
"integrity": "sha512-v1Jqz7urLMhkO6jkgJuBYhnqvXagzceg3qJUWayuCK/c6LTIonpWbwxR1f1APGd4xrW/QcQEovNrAojbUz65Tg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"prop-types": "^15.8.1",
|
||||||
|
"qr.js": "0.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-refresh": {
|
"node_modules/react-refresh": {
|
||||||
"version": "0.18.0",
|
"version": "0.18.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz",
|
||||||
|
|||||||
@ -18,6 +18,7 @@
|
|||||||
"axios": "^1.13.2",
|
"axios": "^1.13.2",
|
||||||
"react": "^19.1.1",
|
"react": "^19.1.1",
|
||||||
"react-dom": "^19.1.1",
|
"react-dom": "^19.1.1",
|
||||||
|
"react-qr-code": "^2.0.18",
|
||||||
"react-router-dom": "^7.9.5",
|
"react-router-dom": "^7.9.5",
|
||||||
"react-toastify": "^11.0.5"
|
"react-toastify": "^11.0.5"
|
||||||
},
|
},
|
||||||
|
|||||||
@ -38,7 +38,13 @@ export const deleteUser = async (id: number) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const createUser = async (payload: UserPayload) => {
|
export const createUser = async (payload: UserPayload) => {
|
||||||
const res = await axiosInstance.post<User>("/api/users", payload);
|
const res = await axiosInstance.post<User>("/api/users",{
|
||||||
|
username: payload.username,
|
||||||
|
email: payload.email,
|
||||||
|
email_verified_at: "2025-11-12T10:13:48.000000Z",
|
||||||
|
password: payload.password,
|
||||||
|
type: payload.type,
|
||||||
|
});
|
||||||
return res.data;
|
return res.data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -12,50 +12,45 @@ import HelpOutlineIcon from "@mui/icons-material/HelpOutline";
|
|||||||
import QuizIcon from "@mui/icons-material/Quiz";
|
import QuizIcon from "@mui/icons-material/Quiz";
|
||||||
import CategoryIcon from "@mui/icons-material/Category";
|
import CategoryIcon from "@mui/icons-material/Category";
|
||||||
import ArticleIcon from "@mui/icons-material/Article";
|
import ArticleIcon from "@mui/icons-material/Article";
|
||||||
import AssignmentIndIcon from '@mui/icons-material/AssignmentInd';
|
import AssignmentIndIcon from "@mui/icons-material/AssignmentInd";
|
||||||
import FmdBadIcon from '@mui/icons-material/FmdBad';
|
import FmdBadIcon from "@mui/icons-material/FmdBad";
|
||||||
|
import { useAuth } from "../../context/AuthContext";
|
||||||
|
type MenuItem = {
|
||||||
|
label: string;
|
||||||
|
icon: React.ReactNode;
|
||||||
|
to: string;
|
||||||
|
};
|
||||||
|
|
||||||
const drawerWidth = 240;
|
const drawerWidth = 240;
|
||||||
|
|
||||||
const menuItems = [
|
const adminMenu: MenuItem[] = [
|
||||||
{
|
{ label: "Users", icon: <PeopleIcon />, to: "/dashboard/users" },
|
||||||
label: "Users",
|
{ label: "Questions", icon: <HelpOutlineIcon />, to: "/dashboard/questions" },
|
||||||
icon: <PeopleIcon />,
|
{ label: "Tests", icon: <QuizIcon />, to: "/dashboard/tests" },
|
||||||
to: "/dashboard/users",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Questions",
|
|
||||||
icon: <HelpOutlineIcon />,
|
|
||||||
to: "/dashboard/questions",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Tests",
|
|
||||||
icon: <QuizIcon />,
|
|
||||||
to: "/dashboard/tests",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
label: "User Tests",
|
label: "User Tests",
|
||||||
icon: <AssignmentIndIcon />,
|
icon: <AssignmentIndIcon />,
|
||||||
to: "/dashboard/user-tests",
|
to: "/dashboard/user-tests",
|
||||||
},
|
},
|
||||||
{
|
{ label: "Categories", icon: <CategoryIcon />, to: "/dashboard/categories" },
|
||||||
label: "Categories",
|
{ label: "Hitcounts", icon: <FmdBadIcon />, to: "/dashboard/hitcounts" },
|
||||||
icon: <CategoryIcon />,
|
{ label: "Logs", icon: <ArticleIcon />, to: "/dashboard/logs" },
|
||||||
to: "/dashboard/categories",
|
];
|
||||||
},
|
|
||||||
{
|
const creatorMenu: MenuItem[] = [
|
||||||
label: "Hitcounts",
|
{ label: "Questions", icon: <HelpOutlineIcon />, to: "/dashboard/questions" },
|
||||||
icon: <FmdBadIcon />,
|
{ label: "Tests", icon: <QuizIcon />, to: "/dashboard/tests" },
|
||||||
to: "/dashboard/hitcounts",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Logs",
|
|
||||||
icon: <ArticleIcon />,
|
|
||||||
to: "/dashboard/logs",
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
export const AdminSidebar = () => {
|
export const AdminSidebar = () => {
|
||||||
|
const { user } = useAuth();
|
||||||
|
|
||||||
|
const menuItems =
|
||||||
|
user?.type === "admin"
|
||||||
|
? adminMenu
|
||||||
|
: user?.type === "creator"
|
||||||
|
? creatorMenu
|
||||||
|
: [];
|
||||||
return (
|
return (
|
||||||
<Drawer
|
<Drawer
|
||||||
variant="permanent"
|
variant="permanent"
|
||||||
|
|||||||
@ -95,16 +95,14 @@ function Header() {
|
|||||||
>
|
>
|
||||||
<Typography textAlign="center">Profile</Typography>
|
<Typography textAlign="center">Profile</Typography>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
{user.type === "admin" && (
|
{(user.type === "admin" || user.type === "creator") && (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
handleCloseNavMenu();
|
handleCloseNavMenu();
|
||||||
goToDashboard();
|
goToDashboard();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography textAlign="center">
|
<Typography textAlign="center">Dashboard</Typography>
|
||||||
Dashboard
|
|
||||||
</Typography>
|
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
@ -159,7 +157,7 @@ function Header() {
|
|||||||
>
|
>
|
||||||
Profile
|
Profile
|
||||||
</Button>
|
</Button>
|
||||||
{user.type === "admin" && (
|
{(user.type === "admin" || user.type === "creator") && (
|
||||||
<Button
|
<Button
|
||||||
onClick={goToDashboard}
|
onClick={goToDashboard}
|
||||||
sx={{ my: 2, color: "white", display: "block" }}
|
sx={{ my: 2, color: "white", display: "block" }}
|
||||||
@ -170,13 +168,13 @@ function Header() {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<Button
|
<Button
|
||||||
sx={{ my: 2, color: "white", display: "block" }}
|
sx={{ my: 2, color: "white", display: "block" }}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
handleCloseNavMenu();
|
handleCloseNavMenu();
|
||||||
navigate("/tests");
|
navigate("/tests");
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Tests
|
Tests
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
|
|||||||
18
src/components/ProtectedRoute/AdminProtectedRoute.tsx
Normal file
18
src/components/ProtectedRoute/AdminProtectedRoute.tsx
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import type { ReactNode } from "react";
|
||||||
|
import { useAuth } from "../../context/AuthContext";
|
||||||
|
import Container from "../shared/Container";
|
||||||
|
import Forbidden from "../../pages/Unauthorized/Forbidden";
|
||||||
|
|
||||||
|
interface ProtectedRouteProps {
|
||||||
|
children: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AdminProtectedRoute = ({ children }: ProtectedRouteProps) => {
|
||||||
|
const { user, isLoading } = useAuth();
|
||||||
|
|
||||||
|
if (isLoading) return <Container>Loading...</Container>;
|
||||||
|
|
||||||
|
if (user?.type === "user" || user?.type === "banned") return <Forbidden />;
|
||||||
|
|
||||||
|
return <>{children}</>;
|
||||||
|
};
|
||||||
41
src/components/TestQrModal/TestQrModal.tsx
Normal file
41
src/components/TestQrModal/TestQrModal.tsx
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import QRCode from "react-qr-code";
|
||||||
|
import { Modal, Box, Button } from "@mui/material";
|
||||||
|
|
||||||
|
import type { TestType } from "../shared/types/TestTypes";
|
||||||
|
|
||||||
|
interface QrProps {
|
||||||
|
open: boolean,
|
||||||
|
onClose: () => void,
|
||||||
|
test: TestType
|
||||||
|
}
|
||||||
|
|
||||||
|
const TestQrModal = ({ open, onClose, test }: QrProps) => {
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal open={open} onClose={onClose}>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
position: "absolute",
|
||||||
|
top: "50%",
|
||||||
|
left: "50%",
|
||||||
|
transform: "translate(-50%, -50%)",
|
||||||
|
bgcolor: "white",
|
||||||
|
p: 3,
|
||||||
|
borderRadius: 2,
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
justifyContent: "center",
|
||||||
|
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<QRCode value={String(test.id)} size={200} />
|
||||||
|
<Button onClick={onClose} sx={{ mt: 2 }}>
|
||||||
|
Close
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TestQrModal;
|
||||||
@ -3,6 +3,7 @@ import { useNavigate } from "react-router-dom";
|
|||||||
import type { UserTestType } from "../../components/shared/types/TestTypes";
|
import type { UserTestType } from "../../components/shared/types/TestTypes";
|
||||||
import { useGetTestById } from "../../hooks/tests/useGetTestById";
|
import { useGetTestById } from "../../hooks/tests/useGetTestById";
|
||||||
import { formatDate } from "../../utils/functions";
|
import { formatDate } from "../../utils/functions";
|
||||||
|
import { useAuth } from "../../context/AuthContext";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
userTest: UserTestType;
|
userTest: UserTestType;
|
||||||
@ -11,6 +12,7 @@ type Props = {
|
|||||||
|
|
||||||
const UserTestRow = ({ userTest, onDelete }: Props) => {
|
const UserTestRow = ({ userTest, onDelete }: Props) => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const { user } = useAuth();
|
||||||
|
|
||||||
const testId = userTest.test_id;
|
const testId = userTest.test_id;
|
||||||
let title = "User Test";
|
let title = "User Test";
|
||||||
@ -38,9 +40,7 @@ const UserTestRow = ({ userTest, onDelete }: Props) => {
|
|||||||
|
|
||||||
<TableCell>{userTest.is_completed ? "Yes" : "No"}</TableCell>
|
<TableCell>{userTest.is_completed ? "Yes" : "No"}</TableCell>
|
||||||
|
|
||||||
<TableCell>
|
<TableCell>{formatDate(userTest.created_at)}</TableCell>
|
||||||
{formatDate(userTest.created_at)}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>{formatDate(userTest.closed_at)}</TableCell>
|
<TableCell>{formatDate(userTest.closed_at)}</TableCell>
|
||||||
|
|
||||||
<TableCell>
|
<TableCell>
|
||||||
@ -52,15 +52,16 @@ const UserTestRow = ({ userTest, onDelete }: Props) => {
|
|||||||
>
|
>
|
||||||
View
|
View
|
||||||
</Button>
|
</Button>
|
||||||
|
{user?.type === "admin" && (
|
||||||
<Button
|
<Button
|
||||||
size="small"
|
size="small"
|
||||||
color="error"
|
color="error"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
onClick={() => onDelete(userTest.id)}
|
onClick={() => onDelete(userTest.id)}
|
||||||
>
|
>
|
||||||
Delete
|
Delete
|
||||||
</Button>
|
</Button>
|
||||||
|
)}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -25,10 +25,12 @@ import type { TestType } from "../../components/shared/types/TestTypes";
|
|||||||
import { useGetTests } from "../../hooks/tests/useGetTests";
|
import { useGetTests } from "../../hooks/tests/useGetTests";
|
||||||
import { useDeleteTest } from "../../hooks/tests/useDeleteTest";
|
import { useDeleteTest } from "../../hooks/tests/useDeleteTest";
|
||||||
import { formatDate } from "../../utils/functions";
|
import { formatDate } from "../../utils/functions";
|
||||||
|
import { useAuth } from "../../context/AuthContext";
|
||||||
|
|
||||||
const AdminTestsPage = () => {
|
const AdminTestsPage = () => {
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
const [deleteTestId, setDeleteTestId] = useState<number | null>(null);
|
const [deleteTestId, setDeleteTestId] = useState<number | null>(null);
|
||||||
|
const { user } = useAuth();
|
||||||
|
|
||||||
const { data, isLoading, isError } = useGetTests({ page: currentPage });
|
const { data, isLoading, isError } = useGetTests({ page: currentPage });
|
||||||
const deleteMutation = useDeleteTest();
|
const deleteMutation = useDeleteTest();
|
||||||
@ -109,14 +111,16 @@ const AdminTestsPage = () => {
|
|||||||
>
|
>
|
||||||
Update
|
Update
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
{(user?.type === "admin" || user?.id == test.author_id) && (
|
||||||
size="small"
|
<Button
|
||||||
variant="outlined"
|
size="small"
|
||||||
color="error"
|
variant="outlined"
|
||||||
onClick={() => setDeleteTestId(test.id)}
|
color="error"
|
||||||
>
|
onClick={() => setDeleteTestId(test.id)}
|
||||||
Delete
|
>
|
||||||
</Button>
|
Delete
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@ -44,7 +44,7 @@ const ProfilePage = () => {
|
|||||||
sx={{ marginLeft: "10px" }}
|
sx={{ marginLeft: "10px" }}
|
||||||
variant="contained"
|
variant="contained"
|
||||||
color="secondary"
|
color="secondary"
|
||||||
onClick={() => navigate("/admin")}
|
onClick={() => navigate("/dashboard")}
|
||||||
>
|
>
|
||||||
Admin Dashboard
|
Admin Dashboard
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@ -22,6 +22,7 @@ import { useDeleteQuestion } from "../../hooks/questions/useDeleteQuestion";
|
|||||||
import Container from "../../components/shared/Container";
|
import Container from "../../components/shared/Container";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import type { QuestionType } from "../../components/shared/types/QuestionTypes";
|
import type { QuestionType } from "../../components/shared/types/QuestionTypes";
|
||||||
|
import { useAuth } from "../../context/AuthContext";
|
||||||
|
|
||||||
const QuestionsPage = () => {
|
const QuestionsPage = () => {
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
@ -29,6 +30,7 @@ const QuestionsPage = () => {
|
|||||||
const { data, isLoading, isError } = useQuestions({ page: currentPage });
|
const { data, isLoading, isError } = useQuestions({ page: currentPage });
|
||||||
const deleteMutation = useDeleteQuestion();
|
const deleteMutation = useDeleteQuestion();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const { user } = useAuth();
|
||||||
|
|
||||||
const handleCreateQuestion = () => {
|
const handleCreateQuestion = () => {
|
||||||
navigate("/dashboard/questions/create");
|
navigate("/dashboard/questions/create");
|
||||||
@ -111,14 +113,17 @@ const QuestionsPage = () => {
|
|||||||
>
|
>
|
||||||
Update
|
Update
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
{(user?.type === "admin" ||
|
||||||
variant="outlined"
|
user?.id == question.author.id) && (
|
||||||
color="error"
|
<Button
|
||||||
size="small"
|
variant="outlined"
|
||||||
onClick={() => setDeleteQuestionId(question.id)}
|
color="error"
|
||||||
>
|
size="small"
|
||||||
Delete
|
onClick={() => setDeleteQuestionId(question.id)}
|
||||||
</Button>
|
>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@ -4,10 +4,6 @@ import Container from "../../components/shared/Container";
|
|||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Button,
|
Button,
|
||||||
Dialog,
|
|
||||||
DialogActions,
|
|
||||||
DialogContent,
|
|
||||||
DialogTitle,
|
|
||||||
Pagination,
|
Pagination,
|
||||||
Paper,
|
Paper,
|
||||||
Table,
|
Table,
|
||||||
@ -24,24 +20,17 @@ import { useAuth } from "../../context/AuthContext";
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useGetAllUserTests } from "../../hooks/tests/useGetAllUserTests";
|
import { useGetAllUserTests } from "../../hooks/tests/useGetAllUserTests";
|
||||||
import UserTestRow from "../../components/UserTestRow/UserTestRow";
|
import UserTestRow from "../../components/UserTestRow/UserTestRow";
|
||||||
import { useDeleteUserTest } from "../../hooks/tests/useDeteleUserTest";
|
import TestQrModal from "../../components/TestQrModal/TestQrModal";
|
||||||
|
|
||||||
export const SingleTestPage = () => {
|
export const SingleTestPage = () => {
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
const { user } = useAuth();
|
const { user } = useAuth();
|
||||||
|
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
const [deleteId, setDeleteId] = useState<number | null>(null);
|
|
||||||
const deleteMutation = useDeleteUserTest();
|
|
||||||
const { data: tests } = useGetAllUserTests(currentPage, Number(id));
|
const { data: tests } = useGetAllUserTests(currentPage, Number(id));
|
||||||
|
|
||||||
const handleDelete = () => {
|
const [qrOpen, setQrOpen] = useState(false);
|
||||||
if (deleteId !== null) {
|
|
||||||
deleteMutation.mutate(deleteId, {
|
|
||||||
onSuccess: () => setDeleteId(null),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const { data: test } = useGetTestById(Number(id));
|
const { data: test } = useGetTestById(Number(id));
|
||||||
if (!test) {
|
if (!test) {
|
||||||
@ -49,9 +38,15 @@ export const SingleTestPage = () => {
|
|||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
<Typography variant="h2" sx={{ mb: 3 }}>
|
<Box sx={{ display: "flex", justifyContent: "space-between", mb: 3 }}>
|
||||||
{test?.title}
|
<Typography variant="h2">{test.title}</Typography>
|
||||||
</Typography>
|
|
||||||
|
{(user?.type === "admin" || user?.type === "creator") && (
|
||||||
|
<Button variant="outlined" onClick={() => setQrOpen(true)}>
|
||||||
|
Show QR
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
|
||||||
{test?.questions?.map((q) => (
|
{test?.questions?.map((q) => (
|
||||||
<Box key={q.id} sx={{ mb: 4 }}>
|
<Box key={q.id} sx={{ mb: 4 }}>
|
||||||
@ -95,7 +90,7 @@ export const SingleTestPage = () => {
|
|||||||
<UserTestRow
|
<UserTestRow
|
||||||
key={ut.id}
|
key={ut.id}
|
||||||
userTest={ut}
|
userTest={ut}
|
||||||
onDelete={(id) => setDeleteId(id)}
|
onDelete={() => {}}
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
@ -124,24 +119,10 @@ export const SingleTestPage = () => {
|
|||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Dialog open={deleteId !== null} onClose={() => setDeleteId(null)}>
|
|
||||||
<DialogTitle>Confirm Delete</DialogTitle>
|
|
||||||
<DialogContent>
|
|
||||||
Are you sure you want to delete this user test?
|
|
||||||
</DialogContent>
|
|
||||||
<DialogActions>
|
|
||||||
<Button onClick={() => setDeleteId(null)}>Cancel</Button>
|
|
||||||
<Button
|
|
||||||
color="error"
|
|
||||||
onClick={handleDelete}
|
|
||||||
disabled={deleteMutation.isPending}
|
|
||||||
>
|
|
||||||
Delete
|
|
||||||
</Button>
|
|
||||||
</DialogActions>
|
|
||||||
</Dialog>
|
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
<TestQrModal open={qrOpen} onClose={() => setQrOpen(false)} test={test} />
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
19
src/pages/Unauthorized/Forbidden.tsx
Normal file
19
src/pages/Unauthorized/Forbidden.tsx
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { Box, Typography } from "@mui/material";
|
||||||
|
|
||||||
|
export default function Forbidden() {
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
display="flex"
|
||||||
|
justifyContent="center"
|
||||||
|
alignItems="center"
|
||||||
|
flexDirection="column"
|
||||||
|
minHeight="80vh"
|
||||||
|
>
|
||||||
|
<Typography variant="h2" color="error" mb={2}>
|
||||||
|
403
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="h5">Forbidden</Typography>
|
||||||
|
<Typography>You do not have access to this page.</Typography>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -25,6 +25,7 @@ import AdminTestsPage from "../pages/AdminTestsPage/AdminTestsPage";
|
|||||||
import TestForm from "../pages/TestForm/TestForm";
|
import TestForm from "../pages/TestForm/TestForm";
|
||||||
import QuestionsPage from "../pages/QuestionsPage/QuestionsPage";
|
import QuestionsPage from "../pages/QuestionsPage/QuestionsPage";
|
||||||
import QuestionForm from "../pages/QuestionForm/QuestionForm";
|
import QuestionForm from "../pages/QuestionForm/QuestionForm";
|
||||||
|
import { AdminProtectedRoute } from "../components/ProtectedRoute/AdminProtectedRoute";
|
||||||
|
|
||||||
const router = createBrowserRouter([
|
const router = createBrowserRouter([
|
||||||
{
|
{
|
||||||
@ -89,9 +90,13 @@ const router = createBrowserRouter([
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/dashboard",
|
path: "/dashboard",
|
||||||
element: <AdminLayout />,
|
element: (
|
||||||
|
<AdminProtectedRoute>
|
||||||
|
<AdminLayout />
|
||||||
|
</AdminProtectedRoute>
|
||||||
|
),
|
||||||
children: [
|
children: [
|
||||||
{ index: true, element: <UsersPage /> },
|
{ index: true, element: <QuestionsPage /> },
|
||||||
{
|
{
|
||||||
path: "users",
|
path: "users",
|
||||||
children: [
|
children: [
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user