├── .gitignore
├── .prettierrc
├── .vscode
└── settings.json
├── README.md
├── jsconfig.json
├── package.json
├── public
├── favicon.ico
├── index.html
├── logo192.png
├── logo512.png
├── manifest.json
└── robots.txt
└── src
├── api
├── config.js
├── exam.js
├── mock-data.js
├── user.js
└── utils.js
├── app.jsx
├── assets
└── icons
│ ├── announcementBlack.png
│ ├── announcementWhite.png
│ ├── crown.png
│ ├── diskette.png
│ ├── tickGreen.png
│ ├── tickRed.png
│ └── user.png
├── common-style.js
├── components
├── carousel
│ ├── index.jsx
│ └── styles.js
├── date-time-picker
│ ├── date-time.jsx
│ ├── date.jsx
│ └── time.jsx
├── dialog
│ ├── confirmation.jsx
│ └── index.jsx
├── dropdown-field
│ └── index.jsx
├── exam-banner
│ ├── index.jsx
│ └── styles.js
├── exam-details-card
│ └── index.jsx
├── exam-details-card2
│ ├── index.jsx
│ └── styles.js
├── file-upload-input
│ └── index.jsx
├── header
│ └── index.jsx
├── multi-select-dropdown
│ └── index.jsx
├── navbar
│ └── index.jsx
├── speed-dial
│ └── index.jsx
├── text-editor
│ └── index.jsx
└── text-input-field
│ └── index.jsx
├── index.css
├── index.js
├── layouts
├── a.css
├── login.jsx
├── questionType-result
│ ├── fill-blanks.jsx
│ ├── mcq-multiple.jsx
│ └── mcq-single.jsx
├── register.jsx
└── request-full-screen.jsx
├── pages
├── exam-creation
│ ├── index.jsx
│ ├── page1.jsx
│ ├── page2.jsx
│ ├── page3.jsx
│ └── question-types
│ │ ├── fill-blanks.jsx
│ │ └── mcq.jsx
├── exam-details
│ ├── index.jsx
│ ├── styles.js
│ └── tab-content
│ │ ├── about.jsx
│ │ ├── result.jsx
│ │ └── scores.jsx
├── give-exam
│ ├── index.jsx
│ ├── page1.jsx
│ ├── page2.jsx
│ └── question-options
│ │ ├── fill-blanks.jsx
│ │ ├── mcq-multiple.jsx
│ │ └── mcq-single.jsx
├── home
│ ├── arrows.jsx
│ ├── exams-horizontal-scroll-section.jsx
│ ├── index.jsx
│ └── styles.js
└── profile
│ ├── User.jsx
│ ├── crop-image.jsx
│ ├── crop-img-utils.jsx
│ ├── details-section.jsx
│ ├── exams-list.jsx
│ └── profile-section.jsx
├── redux
├── slices
│ ├── auth.js
│ ├── confirmation-dialog.js
│ └── dialog-visibility.js
└── store.js
└── utilities
├── functions.js
└── theme.js
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
25 | yarn.lock
26 | package-lock.json
27 | *.env
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "trailingComma": "es5",
3 | "tabWidth": 2,
4 | "semi": true,
5 | "singleQuote": true,
6 | "printWidth": 150,
7 | "jsxSingleQuote": true,
8 | "bracketSameLine": true,
9 | "bracketSpacing": true
10 | }
11 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "px-to-rem.px-per-rem": 10
3 | }
4 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Quizzio: Frontend
2 |
3 | Frontend for the Exam Simulation project.
4 |
5 | ## Installation
6 |
7 | Install my-project with npm
8 |
9 | ```bash
10 | npm install
11 | npm start
12 | ```
13 |
14 | ## Demo
15 |
16 | https://quizzio.vercel.app
17 |
18 | ## Color Reference
19 |
20 | | Color | Hex |
21 | | ------- | ---------------------------------------------------------------- |
22 | | Black |  #000000 |
23 | | White |  #ffffff |
24 | | Primary |  #1AB273 |
25 |
26 | ## Authors
27 |
28 | - [@Bihan001](https://www.github.com/Bihan001)
29 | - [@niharika2k00](https://github.com/niharika2k00)
30 |
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": "src"
4 | },
5 | "include": [
6 | "src"
7 | ]
8 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "frontend",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@emotion/react": "^11.5.0",
7 | "@emotion/styled": "^11.3.0",
8 | "@mui/icons-material": "^5.0.5",
9 | "@mui/lab": "^5.0.0-alpha.53",
10 | "@mui/material": "^5.0.6",
11 | "@mui/styles": "^5.0.2",
12 | "@reduxjs/toolkit": "^1.7.1",
13 | "@tinymce/tinymce-react": "^3.13.0",
14 | "axios": "^0.24.0",
15 | "date-fns": "^2.25.0",
16 | "dompurify": "^2.3.4",
17 | "notistack": "^2.0.3",
18 | "react": "^17.0.2",
19 | "react-dom": "^17.0.2",
20 | "react-easy-crop": "^3.5.3",
21 | "react-horizontal-scrolling-menu": "^2.7.1",
22 | "react-redux": "^7.2.6",
23 | "react-router-dom": "^5.2.0",
24 | "react-scripts": "4.0.3",
25 | "react-swipeable-views": "^0.14.0"
26 | },
27 | "scripts": {
28 | "start": "react-scripts start",
29 | "build": "react-scripts build",
30 | "test": "react-scripts test",
31 | "eject": "react-scripts eject"
32 | },
33 | "eslintConfig": {
34 | "extends": [
35 | "react-app",
36 | "react-app/jest"
37 | ]
38 | },
39 | "browserslist": {
40 | "production": [
41 | ">0.2%",
42 | "not dead",
43 | "not op_mini all"
44 | ],
45 | "development": [
46 | "last 1 chrome version",
47 | "last 1 firefox version",
48 | "last 1 safari version"
49 | ]
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niharika2k00/quizzio-frontend/95f471b8c70ef70340fb72d6328d72fb7b55ae38/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | Exam Platform
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niharika2k00/quizzio-frontend/95f471b8c70ef70340fb72d6328d72fb7b55ae38/public/logo192.png
--------------------------------------------------------------------------------
/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niharika2k00/quizzio-frontend/95f471b8c70ef70340fb72d6328d72fb7b55ae38/public/logo512.png
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/src/api/config.js:
--------------------------------------------------------------------------------
1 | import Axios from 'axios';
2 |
3 | export const getHeaders = () => {
4 | return { headers: { Authorization: `Bearer ${localStorage.getItem('AUTH_TOKEN')}` } };
5 | };
6 |
7 | export const axios = Axios.create({
8 | baseURL: true || process.env.NODE_ENV === 'production' ? 'https://quizyfy.herokuapp.com' : 'http://localhost:5000',
9 | });
10 |
--------------------------------------------------------------------------------
/src/api/exam.js:
--------------------------------------------------------------------------------
1 |
2 | import { axios, getHeaders } from './config';
3 | import { examScores } from './mock-data';
4 |
5 | // ------------------------------------------------
6 | // baseURL: 'http://localhost:5000'
7 | // -------------------------------------------------
8 |
9 | export const getExams = (filters) => {
10 | return axios.post('/exam/all', filters);
11 | };
12 |
13 | export const getExamDetails = (id) => {
14 | return axios.get(`/exam/${id}`);
15 | };
16 |
17 | export const createExam = async (data) => {
18 | return axios.post('/exam/create', data, getHeaders());
19 | };
20 |
21 | export const registerInExam = async (data) => {
22 | return axios.post('/exam/register', data, getHeaders());
23 | };
24 |
25 | export const getUserExamRegisterStatus = async (examId) => {
26 | return axios.get(`/exam/exam-registered?examId=${examId}`, getHeaders());
27 | };
28 |
29 | export const startExam = (examId) => {
30 | return axios.get(`/exam/${examId}/start`, getHeaders());
31 | };
32 |
33 | export const submitExamAnswers = (data) => {
34 | return axios.post(`/exam/submit`, data, getHeaders());
35 | };
36 |
37 | export const getExamTags = () => {
38 | return axios.get('/exam/tags');
39 | };
40 |
41 | export const getExamTypes = () => {
42 | return new Promise((resolve, reject) => {
43 | setTimeout(() => {
44 | resolve({
45 | data: [
46 | { label: 'Public', value: 'public' },
47 | { label: 'Private', value: 'private' },
48 | ],
49 | });
50 | }, 100);
51 | });
52 | };
53 |
54 | export const getQuestionTypes = () => {
55 | return axios.get('/exam/question-types');
56 | };
57 |
58 |
59 | export const getExamResult = (examId) => {
60 | return axios.get(`/exam/solution?examId=${examId}`, getHeaders());
61 | };
62 |
63 |
64 | export const getExamScores = (examId) => {
65 | return axios.get(`/exam/scores?examId=${examId}`, getHeaders());
66 | };
67 |
68 |
69 |
70 |
71 | // -----------------------
72 | // DUMMY DATA FORMAT
73 | // -----------------------
74 | /* export const getExamScores = (examId) => {
75 | return axios.get(`/exam/scores?examId=${examId}`, getHeaders());
76 | return new Promise((resolve, reject) => {
77 | setTimeout(() => {
78 | resolve({
79 | data: { data: examScores },
80 | });
81 | }, 1000);
82 | });
83 | }; */
84 |
--------------------------------------------------------------------------------
/src/api/mock-data.js:
--------------------------------------------------------------------------------
1 | export const examFetchedData = {
2 | success: true,
3 | status: 'success',
4 | message: 'Exam Started !',
5 | data: {
6 | id: 'db5ed499-8898-4558-89ca-d8f35a11572f',
7 | name: 'Data Structures and Algorithms',
8 | description: 'This is an annual Exam of DSA held by TTT Community , Participate to win exciting prices !',
9 | image: 'https://image.freepik.com/free-vector/online-exam-isometric-web-banner_33099-2305.jpg',
10 | userId: '8f1576ca-80a5-4f96-a215-04caeb307e15',
11 | tags: ['Programming', 'Data Structures', 'Competitive Programming'],
12 | questions: [
13 | {
14 | id: 1,
15 | question: 'Which alphabets are vowels ?',
16 | options: [
17 | {
18 | id: 1,
19 | data: 'uUUUUUUUUUUU',
20 | },
21 | {
22 | id: 0,
23 | data: 'VVVVVVVVVVVVVVVVVVVv',
24 | },
25 | ],
26 | type: 'multipleOptions',
27 | correctOption: [0, 1],
28 | marks: 5,
29 | negMarks: -2,
30 | },
31 | {
32 | id: 2,
33 | question: 'You are ____',
34 | type: 'fillInTheBlanks',
35 | correctOption: 'Gay',
36 | marks: 5,
37 | negMarks: -2,
38 | },
39 | {
40 | id: 0,
41 | question: 'Number of english alphabets',
42 | options: [
43 | {
44 | id: 1,
45 | data: '20',
46 | },
47 | {
48 | id: 0,
49 | data: '26',
50 | },
51 | ],
52 | type: 'mcq',
53 | correctOption: [0],
54 | marks: 5,
55 | negMarks: -2,
56 | },
57 | ],
58 | startTime: '2021-11-20T05:08:30.000Z',
59 | duration: 33,
60 | ongoing: 1,
61 | finished: 0,
62 | isPrivate: 0,
63 | },
64 | };
65 |
66 | export const examResult = [
67 | {
68 | question: 'What is the return type of printf in C?',
69 | type: 'mcq',
70 | options: [
71 | { id: 1, data: 'char' },
72 | { id: 2, data: 'float' },
73 | { id: 3, data: 'int' },
74 | { id: 4, data: 'void' },
75 | ],
76 | marks: 2,
77 | negMarks: 2,
78 | givenOption: [3],
79 | correctOption: [3],
80 | },
81 | {
82 | question: 'Select the languages that uses an compiler',
83 | type: 'multipleOptions',
84 | options: [
85 | { id: 1, data: 'C++' },
86 | { id: 2, data: 'C' },
87 | { id: 3, data: 'Python' },
88 | { id: 4, data: 'C#' },
89 | ],
90 | marks: 2,
91 | negMarks: 2,
92 | givenOption: [1, 3],
93 | correctOption: [1, 2, 4],
94 | },
95 | {
96 | question: 'Which function is used to print a string in Java?',
97 | type: 'fillInTheBlanks',
98 | marks: 2,
99 | negMarks: 2,
100 | givenOption: 'System.out.println("Hello World");',
101 | correctOption: 'System.out.println',
102 | },
103 | ];
104 |
105 | export const examScores = {
106 | participantId: "76fa9982-46e6-4bee-a9f8-0b0de03b3bbf",
107 | rank: 2,
108 | finishTime: 1641894484307,
109 | totalScore: 1,
110 | topPerformers: [
111 | {
112 | participantId: "dc738946-5461-4e6a-bb4f-dc16d8a94e09",
113 | totalScore: 4,
114 | rank: 1,
115 | finishTime: 1641894515349,
116 | name: "Nero Devil Trigger"
117 | },
118 | {
119 | participantId: "76fa9982-46e6-4bee-a9f8-0b0de03b3bbf",
120 | totalScore: 1,
121 | rank: 2,
122 | finishTime: 1641894484307,
123 | name: "Dante"
124 | },
125 | {
126 | participantId: "dc738946-5461-4e6a-bb4f-dc16d8a94e09",
127 | totalScore: 4,
128 | rank: 1,
129 | finishTime: 1641894515349,
130 | name: "Nero Devil Trigger"
131 | },
132 | {
133 | participantId: "dc738946-5461-4e6a-bb4f-dc16d8a94e09",
134 | totalScore: 4,
135 | rank: 1,
136 | finishTime: 1641894515349,
137 | name: "Nero Devil Trigger"
138 | },
139 | {
140 | participantId: "dc738946-5461-4e6a-bb4f-dc16d8a94e09",
141 | totalScore: 4,
142 | rank: 1,
143 | finishTime: 1641894515349,
144 | name: "Nero Devil Trigger"
145 | },
146 | ]
147 | };
148 |
--------------------------------------------------------------------------------
/src/api/user.js:
--------------------------------------------------------------------------------
1 | import { axios, getHeaders } from './config';
2 | /**
3 | *
4 | * @param {string} email User's Email
5 | * @param {string} password User's Password
6 | * @returns Promise>
7 | * @example
8 | * import {loginWithEmailAndPassword} from 'api/user';
9 | * loginWithEmailAndPassword('bihan', 'abcdefgh').then(res => console.log(res.data)).catch(err => console.log(err));
10 | */
11 |
12 | export const loginWithEmailAndPassword = (email, password) => {
13 | return axios.post('/user/login', { email, password }, getHeaders());
14 | };
15 |
16 | export const registerNewUser = (data) => {
17 | return axios.post('/user/register', data, getHeaders());
18 | };
19 |
20 | export const getCurrentUser = () => {
21 | return axios.get('/user/current', getHeaders());
22 | };
23 |
24 | export const getHostedExams = () => {
25 | return axios.get('/user/exams-hosted', getHeaders());
26 | };
27 |
28 | export const getGivenExams = () => {
29 | return axios.get('/user/exams-given', getHeaders());
30 | };
31 |
--------------------------------------------------------------------------------
/src/api/utils.js:
--------------------------------------------------------------------------------
1 | import { axios, getHeaders } from './config';
2 |
3 | export const uploadImages = (formData) => {
4 | const headers = getHeaders();
5 | headers.headers['Content-Type'] = 'multipart/form-data';
6 | return axios.post('/utils/upload/images', formData, headers);
7 | };
8 |
--------------------------------------------------------------------------------
/src/app.jsx:
--------------------------------------------------------------------------------
1 | import { useState, useMemo, useEffect } from 'react';
2 | import { Switch, Route, useLocation, Redirect } from 'react-router-dom';
3 | import { useSelector, useDispatch } from 'react-redux';
4 | import { SnackbarProvider } from 'notistack';
5 | import CreateExam from 'pages/exam-creation';
6 | import { createTheme, ThemeProvider } from '@mui/material/styles';
7 | import { CssBaseline, Paper, Slide } from '@mui/material';
8 | import Navbar from 'components/navbar';
9 | import Register from 'layouts/register';
10 | import Login from 'layouts/login';
11 | import User from 'pages/profile/User';
12 | import Home from 'pages/home';
13 | import GiveExam from 'pages/give-exam';
14 | import ExamDetails from 'pages/exam-details';
15 | import getDesignTokens from 'utilities/theme';
16 | import { getCurrentUser } from 'api/user';
17 | import { setUserAndToken } from 'redux/slices/auth';
18 | import ConfirmationDialog from 'components/dialog/confirmation';
19 |
20 | const App = () => {
21 | const location = useLocation();
22 | const dispatch = useDispatch();
23 | const { user } = useSelector((state) => state.auth);
24 | const [themeMode, setThemeMode] = useState(localStorage.getItem('theme') || 'light');
25 |
26 | useEffect(() => {
27 | fetchCurrentUser();
28 | }, []);
29 |
30 | const fetchCurrentUser = async () => {
31 | try {
32 | const res = await getCurrentUser();
33 | dispatch(setUserAndToken({ user: res.data.data }));
34 | } catch (err) {
35 | console.error(err);
36 | }
37 | };
38 |
39 | // The dark mode switch would invoke this method
40 | const colorMode = useMemo(
41 | () => ({
42 | toggleColorMode: () => {
43 | setThemeMode((prevMode) => (prevMode === 'light' ? 'dark' : 'light'));
44 | },
45 | }),
46 | []
47 | );
48 |
49 | const toggleTheme = () => {
50 | setThemeMode((t) => {
51 | const newMode = t === 'light' ? 'dark' : 'light';
52 | localStorage.setItem('theme', newMode);
53 | return newMode;
54 | });
55 | };
56 |
57 | // Update the theme only if the mode changes
58 | const theme = useMemo(() => createTheme(getDesignTokens(themeMode)), [themeMode]);
59 |
60 | return (
61 |
62 |
70 |
71 |
72 | {!location.pathname.includes('/give') && }
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 | {/* /exam/:id */}
81 |
82 |
83 |
84 |
85 |
86 |
87 | );
88 | };
89 |
90 | export default App;
91 |
--------------------------------------------------------------------------------
/src/assets/icons/announcementBlack.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niharika2k00/quizzio-frontend/95f471b8c70ef70340fb72d6328d72fb7b55ae38/src/assets/icons/announcementBlack.png
--------------------------------------------------------------------------------
/src/assets/icons/announcementWhite.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niharika2k00/quizzio-frontend/95f471b8c70ef70340fb72d6328d72fb7b55ae38/src/assets/icons/announcementWhite.png
--------------------------------------------------------------------------------
/src/assets/icons/crown.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niharika2k00/quizzio-frontend/95f471b8c70ef70340fb72d6328d72fb7b55ae38/src/assets/icons/crown.png
--------------------------------------------------------------------------------
/src/assets/icons/diskette.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niharika2k00/quizzio-frontend/95f471b8c70ef70340fb72d6328d72fb7b55ae38/src/assets/icons/diskette.png
--------------------------------------------------------------------------------
/src/assets/icons/tickGreen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niharika2k00/quizzio-frontend/95f471b8c70ef70340fb72d6328d72fb7b55ae38/src/assets/icons/tickGreen.png
--------------------------------------------------------------------------------
/src/assets/icons/tickRed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niharika2k00/quizzio-frontend/95f471b8c70ef70340fb72d6328d72fb7b55ae38/src/assets/icons/tickRed.png
--------------------------------------------------------------------------------
/src/assets/icons/user.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niharika2k00/quizzio-frontend/95f471b8c70ef70340fb72d6328d72fb7b55ae38/src/assets/icons/user.png
--------------------------------------------------------------------------------
/src/common-style.js:
--------------------------------------------------------------------------------
1 | import { makeStyles } from '@mui/styles';
2 |
3 | export default makeStyles((theme) => ({
4 | heading: {
5 | fontSize: '2.6rem',
6 | textAlign: 'center',
7 | fontWeight: '600',
8 | color: theme.palette.primary.main,
9 | alignItems: 'center',
10 | justifyContent: 'center',
11 | },
12 | }));
13 |
--------------------------------------------------------------------------------
/src/components/carousel/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import MobileStepper from '@mui/material/MobileStepper';
3 | import KeyboardArrowLeft from '@mui/icons-material/KeyboardArrowLeft';
4 | import KeyboardArrowRight from '@mui/icons-material/KeyboardArrowRight';
5 | import SwipeableViews from 'react-swipeable-views';
6 | import { autoPlay } from 'react-swipeable-views-utils';
7 | import { Button } from '@mui/material';
8 | import { useTheme } from '@emotion/react';
9 | import useStyles from './styles';
10 | import ExamBanner from 'components/exam-banner';
11 | import { ClassNames } from '@emotion/react';
12 |
13 | const AutoPlaySwipeableViews = autoPlay(SwipeableViews);
14 | console.log(AutoPlaySwipeableViews);
15 |
16 | const Carousel = ({ data, type }) => {
17 | const theme = useTheme();
18 | const classes = useStyles();
19 | //Carousel States===========
20 | const [activeStep, setActiveStep] = useState(0);
21 | const [maxSteps, setMaxSteps] = useState(0);
22 | //==========================
23 |
24 | // Map and send each data Object
25 | const Cards = {
26 | 'exam-banner': data.map((examDetail, index) => ),
27 | };
28 |
29 | useEffect(() => {
30 | setMaxSteps(data.length);
31 | }, [data]);
32 |
33 | //Carousel Config functions-==============
34 | const handleNext = () => {
35 | setActiveStep((prevActiveStep) => prevActiveStep + 1);
36 | };
37 | const handleBack = () => {
38 | setActiveStep((prevActiveStep) => prevActiveStep - 1);
39 | };
40 | const handleStepChange = (step) => {
41 | setActiveStep(step);
42 | };
43 | //========================================
44 |
45 | return (
46 | <>
47 |
53 | {Cards[type]}
54 |
55 |
56 |
63 | Next
64 | {theme.direction === 'rtl' ? : }
65 |
66 | }
67 | backButton={
68 |
72 | }
73 | />
74 | >
75 | );
76 | };
77 | export default Carousel;
78 |
--------------------------------------------------------------------------------
/src/components/carousel/styles.js:
--------------------------------------------------------------------------------
1 | import { makeStyles } from '@mui/styles';
2 |
3 | export default makeStyles((theme) => ({
4 | card: {
5 | width: '85vw',
6 | margin: '0 auto',
7 | borderRadius: '.5rem',
8 | },
9 | stepper: {
10 | margin: '0 auto',
11 | width: '60vw',
12 | backgroundColor: 'transparent',
13 | },
14 | }));
15 |
--------------------------------------------------------------------------------
/src/components/date-time-picker/date-time.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { withStyles } from '@mui/styles';
4 | import { TextField, Typography } from '@mui/material';
5 | import DateTimePicker from '@mui/lab/DateTimePicker';
6 | import AdapterDateFns from '@mui/lab/AdapterDateFns';
7 | import LocalizationProvider from '@mui/lab/LocalizationProvider';
8 |
9 | const styles = (theme) => ({
10 | textField: {
11 | width: 192,
12 | },
13 | cssOutlinedInput: {
14 | '&$cssFocused $notchedOutline': {
15 | borderColor: '#1AB273 !important',
16 | },
17 | },
18 | cssFocused: {},
19 | notchedOutline: {
20 | borderWidth: '1px',
21 | borderColor: 'rgba(0, 0, 0, 0.15) !important',
22 | color: 'black',
23 | // backgroundColor:'white'
24 | },
25 | disable: {
26 | color: '#D9D7D7',
27 | fontSize: 12,
28 | height: 1,
29 | },
30 | });
31 |
32 | const DateTimeField = (props) => {
33 | const { name, value, onChange, label, disabled, required, fullWidth = false, style } = props;
34 |
35 | return (
36 | <>
37 |
38 | {label}
39 | {required ? * : null}
40 |
41 |
42 |
43 |
44 | (
49 |
50 | )}
51 | />
52 |
53 |
54 | >
55 | );
56 | };
57 |
58 | export default withStyles(styles)(DateTimeField);
59 |
--------------------------------------------------------------------------------
/src/components/date-time-picker/date.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { withStyles } from '@mui/styles';
4 | import { TextField, Typography } from '@mui/material';
5 | import DesktopDatePicker from '@mui/lab/DesktopDatePicker';
6 | import AdapterDateFns from '@mui/lab/AdapterDateFns';
7 | import LocalizationProvider from '@mui/lab/LocalizationProvider';
8 |
9 | const styles = (theme) => ({
10 | textField: {
11 | width: 192,
12 | },
13 | cssOutlinedInput: {
14 | '&$cssFocused $notchedOutline': {
15 | borderColor: '#1AB273 !important',
16 | },
17 | },
18 | cssFocused: {},
19 | notchedOutline: {
20 | borderWidth: '1px',
21 | borderColor: 'rgba(0, 0, 0, 0.15) !important',
22 | color: 'black',
23 | // backgroundColor:'white'
24 | },
25 | disable: {
26 | color: '#D9D7D7',
27 | fontSize: 12,
28 | height: 1,
29 | },
30 | });
31 |
32 | const DateField = (props) => {
33 | const { name, value, onChange, label, disabled, required, fullWidth = false, style } = props;
34 |
35 | return (
36 | <>
37 |
38 | {label}
39 | {required ? * : null}
40 |
41 |
42 |
43 |
44 | (
50 |
51 | )}
52 | />
53 |
54 |
55 | >
56 | );
57 | };
58 |
59 | export default withStyles(styles)(DateField);
60 |
--------------------------------------------------------------------------------
/src/components/date-time-picker/time.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { withStyles } from '@mui/styles';
4 | import { TextField, Typography } from '@mui/material';
5 | import TimePicker from '@mui/lab/TimePicker';
6 | import AdapterDateFns from '@mui/lab/AdapterDateFns';
7 | import LocalizationProvider from '@mui/lab/LocalizationProvider';
8 |
9 | const styles = (theme) => ({
10 | cssOutlinedInput: {
11 | '&$cssFocused $notchedOutline': {
12 | borderColor: '#1AB273 !important',
13 | },
14 | },
15 | cssFocused: {},
16 | notchedOutline: {
17 | borderWidth: '1px',
18 | borderColor: 'rgba(0, 0, 0, 0.15) !important',
19 | color: 'black',
20 | // backgroundColor:'white'
21 | },
22 | disable: {
23 | color: '#D9D7D7',
24 | fontSize: 12,
25 | height: 1,
26 | },
27 | });
28 |
29 | const DateField = (props) => {
30 | const { label, disabled, name, value, onChange, required, fullWidth = false, style } = props;
31 |
32 | return (
33 | <>
34 |
35 | {label}
36 | {required ? * : null}
37 |
38 |
39 |
40 | (
45 |
46 | )}
47 | />
48 |
49 |
50 | >
51 | );
52 | };
53 |
54 | export default withStyles(styles)(DateField);
55 |
--------------------------------------------------------------------------------
/src/components/dialog/confirmation.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useHistory } from 'react-router-dom';
3 | import { useSelector, useDispatch } from 'react-redux';
4 | import { makeStyles } from '@mui/styles';
5 | import { TextField, Typography, Button } from '@mui/material';
6 | import { Dialog, DialogContent, DialogActions, DialogTitle } from 'components/dialog';
7 | import TickMarkGreen from 'assets/icons/tickGreen.png';
8 | import TickMarkRed from 'assets/icons/tickRed.png';
9 | import { hideConfirmation } from 'redux/slices/confirmation-dialog';
10 |
11 | const useStyles = makeStyles((theme) => ({}));
12 |
13 | function ConfirmationDialog(props) {
14 | const { ...rest } = props;
15 | const { open, title, content, primaryBtnText, onPrimaryBtnClick, secondaryBtnText, onSecondaryBtnClick } = useSelector(
16 | (state) => state.confirmationDialog
17 | );
18 | const classes = useStyles();
19 | const history = useHistory();
20 | const dispatch = useDispatch();
21 |
22 | const handleClose = () => {
23 | dispatch(hideConfirmation());
24 | };
25 |
26 | const handleSecondaryClick = () => {
27 | handleClose();
28 | onSecondaryBtnClick();
29 | };
30 |
31 | const handlePrimaryClick = () => {
32 | handleClose();
33 | onPrimaryBtnClick();
34 | };
35 |
36 | return (
37 |
49 | );
50 | }
51 |
52 | export default ConfirmationDialog;
53 |
--------------------------------------------------------------------------------
/src/components/dialog/index.jsx:
--------------------------------------------------------------------------------
1 | import {
2 | Dialog as MyDialog,
3 | DialogActions as MyDialogActions,
4 | DialogContent as MyDialogContent,
5 | DialogTitle as MyDialogTitle,
6 | } from '@mui/material';
7 |
8 | export const DialogActions = (props) => {
9 | const { children, ...rest } = props;
10 | return {children};
11 | };
12 |
13 | export const DialogContent = (props) => {
14 | const { children, ...rest } = props;
15 | return {children};
16 | };
17 |
18 | export const DialogTitle = (props) => {
19 | const { children, ...rest } = props;
20 | return {children};
21 | };
22 |
23 | export const Dialog = (props) => {
24 | const { fullWidth, maxWidth, open, handleClose, children, ...rest } = props;
25 |
26 | return (
27 |
28 | {children}
29 |
30 | );
31 | };
32 |
--------------------------------------------------------------------------------
/src/components/dropdown-field/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { TextField, Typography, Select, MenuItem } from '@mui/material';
3 |
4 | const Dropdown = ({
5 | label,
6 | disabled,
7 | value,
8 | onChange,
9 | required = false,
10 | options,
11 | name,
12 | fullWidth,
13 | labelStyle = {},
14 | labelClassName,
15 | style = {},
16 | }) => {
17 | return (
18 |
19 |
20 | {label}
21 | {required ? * : null}
22 |
23 |
24 |
25 |
43 |
44 |
45 | );
46 | };
47 |
48 | export default Dropdown;
49 |
--------------------------------------------------------------------------------
/src/components/exam-banner/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import useStyles from './styles';
3 | import { Box } from '@mui/material';
4 |
5 | const ExamBanner = ({ data }) => {
6 | const classes = useStyles();
7 | const [cardHover, setCardHover] = useState(false);
8 |
9 | console.log('data is : ', data);
10 |
11 | return (
12 | <>
13 | setCardHover(true)}
15 | onMouseLeave={() => setCardHover(false)}
16 | style={{
17 | backgroundImage: `linear-gradient(rgba(28, 40, 51,.5), rgba(28, 40, 51,.5)),url(${data.image})`,
18 | color: 'white',
19 | }}
20 | className={classes.examBanner}
21 | >
22 |
34 |
{data.name}
35 |
36 | {data.ongoing ? 'ongoing' : 'upcoming'}
37 |
38 |
39 |
40 |
49 | Registered : {data.numberOfParticipants}
50 |
51 |
52 |
56 | {data.tags.map((tag) => (
57 |
{tag}
58 | ))}
59 |
60 |
61 | >
62 | );
63 | };
64 | export default ExamBanner;
65 |
--------------------------------------------------------------------------------
/src/components/exam-banner/styles.js:
--------------------------------------------------------------------------------
1 | import { makeStyles } from '@mui/styles';
2 |
3 | export default makeStyles((theme) => ({
4 | heading: {
5 | fontSize: '3.2rem',
6 | textAlign: 'center',
7 | marginBottom: '2rem',
8 | },
9 | examBanner: {
10 | display: 'flex',
11 | flexDirection: 'column',
12 | justifyContent: 'space-between',
13 | width: '100%',
14 | height: '55rem',
15 | backgroundPosition: 'center',
16 | backgroundSize: 'cover',
17 | backgroundRepeat: 'no-repeat',
18 | cursor: 'pointer',
19 | transition: '.2s ease',
20 | position: 'relative',
21 | color: 'white',
22 | '&:hover': {},
23 | },
24 | show: {
25 | opacity: 1,
26 | },
27 | hide: {
28 | opacity: 0,
29 | },
30 | ongoing: {
31 | backgroundColor: 'green',
32 | fontSize: '2rem',
33 | height: 'fit-content',
34 | marginLeft: '3rem',
35 | padding: '.5rem',
36 | borderRadius: '.2rem',
37 | },
38 | upcoming: {
39 | backgroundColor: 'blue',
40 | fontSize: '2rem',
41 | height: 'fit-content',
42 | marginLeft: '3rem',
43 | padding: '.5rem',
44 | borderRadius: '.2rem',
45 | },
46 | }));
47 |
--------------------------------------------------------------------------------
/src/components/exam-details-card/index.jsx:
--------------------------------------------------------------------------------
1 |
2 | import { useState } from 'react';
3 | import { Box, Card, CardContent, Button, Typography } from '@mui/material';
4 | import { useHistory } from 'react-router-dom';
5 |
6 |
7 | const ExamDetailsCard = (props) => {
8 |
9 | const history = useHistory();
10 | const { fullWidth, width, data, cardDetails, ...rest } = props;
11 |
12 |
13 | // -----------------------------------------------------
14 | // Time Manupulation
15 | // -----------------------------------------------------
16 | let a = new Date(cardDetails.startTime);
17 | var StartTime = a.toDateString();
18 | var start = a.toLocaleTimeString();
19 |
20 | var addMins = new Date(a.getTime() + cardDetails.duration * 60000);
21 | var event = new Date(addMins);
22 | var end = event.toLocaleTimeString();
23 |
24 |
25 | const redirectToExamDetailsPage = () => {
26 | history.push(`/exam/${cardDetails.id}`);
27 | }
28 |
29 |
30 | return (
31 |
32 |
33 | {cardDetails.name !== null ? cardDetails.name : '-'}
34 |
35 | {cardDetails.startTime !== null ? StartTime : '-'}
36 |
37 |
38 |
39 | Duration
40 |
41 | {cardDetails.duration !== null ? `${cardDetails.duration} mins` : '-'}
42 |
43 |
44 |
45 | No. of Participants
46 |
47 | {cardDetails.numberOfParticipants !== null ? cardDetails.numberOfParticipants : '-'}
48 |
49 |
50 |
51 | Status
52 |
53 | SCHEDULED
54 |
55 |
56 |
57 | Exam Timing
58 |
59 | {cardDetails.timing !== null ? `${start} - ${end}` : '-'}
60 |
61 |
62 |
63 | Exam Visibility
64 |
65 | PUBLIC
66 |
67 |
68 |
69 |
70 |
73 |
74 |
75 |
76 | );
77 | };
78 |
79 |
80 | export default ExamDetailsCard;
81 |
--------------------------------------------------------------------------------
/src/components/exam-details-card2/index.jsx:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react';
2 | import { Box, Card, CardContent, Button, Typography } from '@mui/material';
3 | import useStyles from './styles';
4 | import AccessTimeIcon from '@mui/icons-material/AccessTime';
5 | import { useHistory } from 'react-router-dom';
6 |
7 | const ExamDetailsCard2 = (props) => {
8 | const history = useHistory();
9 | const classes = useStyles();
10 | const { fullWidth, width, data, cardDetails, ...rest } = props;
11 |
12 | return (
13 | history.push(`/exam/${cardDetails.id}`)}
17 | >
18 | {cardDetails && (
19 |
28 |
34 |
35 |
36 |
37 |
38 | {cardDetails.name !== null ? cardDetails.name : '-'}{' '}
39 |
40 |
41 |
49 | {cardDetails.tags.slice(0, 3).map((tag) => (
50 |
51 | ))}
52 |
53 |
65 |
71 |
72 | {new Date(cardDetails.startTime).toDateString()}
73 |
74 |
75 |
76 | )}
77 |
78 | );
79 | };
80 | export default ExamDetailsCard2;
81 |
82 | const Tag = ({ tag }) => {
83 | const classes = useStyles();
84 | return (
85 |
86 | #{tag}
87 |
88 | );
89 | };
90 |
--------------------------------------------------------------------------------
/src/components/exam-details-card2/styles.js:
--------------------------------------------------------------------------------
1 | import { makeStyles } from '@mui/styles';
2 |
3 | export default makeStyles((theme) => ({
4 | img: {
5 | width: '100%',
6 | height: '20.7rem',
7 | borderRadius: '1rem',
8 | transition: '0.3s all ease',
9 | '&:hover': {
10 | transform: 'scale(1.08)',
11 | },
12 | },
13 | tag: {
14 | backgroundColor:
15 | theme.palette.mode === 'dark'
16 | ? theme.palette.grey[800]
17 | : theme.palette.grey[300],
18 | margin: '.5rem',
19 | borderRadius: '.5rem',
20 | padding: '.5rem',
21 | },
22 | }));
23 |
--------------------------------------------------------------------------------
/src/components/file-upload-input/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { TextField, Typography, Button, InputAdornment, IconButton } from '@mui/material';
3 | import PhotoCamera from '@mui/icons-material/PhotoCamera';
4 | import { styled } from '@mui/system';
5 |
6 | const Input = styled('input')({
7 | display: 'none',
8 | });
9 |
10 | const FileUploadField = ({
11 | label,
12 | placeholder,
13 | disabled = false,
14 | required,
15 | name,
16 | type,
17 | onChange,
18 | value,
19 | fullWidth,
20 | row = '',
21 | rowMax = '',
22 | multiline,
23 | style = {},
24 | labelStyle = {},
25 | labelClassName,
26 | className,
27 | variant,
28 | showActionBtn = false,
29 | actionBtnText = '',
30 | accept = 'image/*',
31 | uploadIcon = ,
32 | uploadDisabled = false,
33 | actionOnClick = () => {},
34 | }) => {
35 | return (
36 |
37 |
38 | {label}
39 | {required ? * : null}
40 |
41 |
42 |
43 |
61 |
67 |
68 | ),
69 | }}
70 | />
71 | {showActionBtn && (
72 |
75 | )}
76 |
77 |
78 | );
79 | };
80 |
81 | export default FileUploadField;
82 |
--------------------------------------------------------------------------------
/src/components/header/index.jsx:
--------------------------------------------------------------------------------
1 | import { Paper, Typography } from '@mui/material';
2 | import { makeStyles } from '@mui/styles';
3 |
4 | const useStyles = makeStyles((theme) => ({
5 | root: {
6 | padding: '3rem',
7 | },
8 | }));
9 |
10 | const Header = (props) => {
11 | const classes = useStyles();
12 | const { elevation = 2, title, description, children, className, style, ...rest } = props;
13 | return (
14 |
15 | {title}
16 | {description}
17 | {children}
18 |
19 | );
20 | };
21 |
22 | export default Header;
23 |
--------------------------------------------------------------------------------
/src/components/multi-select-dropdown/index.jsx:
--------------------------------------------------------------------------------
1 | import { useTheme } from '@mui/material/styles';
2 | import Box from '@mui/material/Box';
3 | import Typography from '@mui/material/Typography';
4 | import OutlinedInput from '@mui/material/OutlinedInput';
5 | import MenuItem from '@mui/material/MenuItem';
6 | import Select from '@mui/material/Select';
7 | import Chip from '@mui/material/Chip';
8 |
9 | const ITEM_HEIGHT = 48;
10 | const ITEM_PADDING_TOP = 8;
11 |
12 | const getStyles = (opt, value, theme) => {
13 | return {
14 | fontWeight: value.indexOf(opt) === -1 ? theme.typography.fontWeightRegular : theme.typography.fontWeightMedium,
15 | };
16 | };
17 |
18 | const MultiSelect = (props) => {
19 | const { name, value, onChange, options = [], label, placeholder = '', required, style, disabled, labelStyle, labelClassName } = props;
20 | const theme = useTheme();
21 |
22 | return (
23 | <>
24 |
25 | {label}
26 | {required ? * : null}
27 |
28 |
29 | }
38 | style={{ marginTop: '0.5rem', width: '100%', ...style }}
39 | renderValue={(selected) => (
40 |
41 | {selected.length === 0 && placeholder}
42 | {selected.length > 0 && selected.map((value) => )}
43 |
44 | )}
45 | >
46 | {options.map((opt) => (
47 |
50 | ))}
51 |
52 | >
53 | );
54 | };
55 |
56 | export default MultiSelect;
57 |
--------------------------------------------------------------------------------
/src/components/navbar/index.jsx:
--------------------------------------------------------------------------------
1 | import { Link } from 'react-router-dom';
2 | import { styled, alpha } from '@mui/material/styles';
3 | import { useTheme } from '@mui/styles';
4 | import { useSelector, useDispatch } from 'react-redux';
5 | import { clearUserAndToken } from 'redux/slices/auth';
6 | import { enableVisibility, dialogNames } from 'redux/slices/dialog-visibility';
7 | import AppBar from '@mui/material/AppBar';
8 | import Box from '@mui/material/Box';
9 | import Button from '@mui/material/Button';
10 | import Container from '@mui/material/Container';
11 | import Toolbar from '@mui/material/Toolbar';
12 | import IconButton from '@mui/material/IconButton';
13 | import Typography from '@mui/material/Typography';
14 | import InputBase from '@mui/material/InputBase';
15 | import SearchIcon from '@mui/icons-material/Search';
16 | import DarkModeIcon from '@mui/icons-material/DarkMode';
17 | import LightModeIcon from '@mui/icons-material/LightMode';
18 | import AccountCircleIcon from '@mui/icons-material/AccountCircle';
19 |
20 | const Search = styled('div')(({ theme }) => ({
21 | position: 'relative',
22 | borderRadius: theme.shape.borderRadius,
23 | backgroundColor: alpha(theme.palette.common.white, 0.15),
24 | '&:hover': {
25 | backgroundColor: alpha(theme.palette.common.white, 0.25),
26 | },
27 | marginRight: theme.spacing(2),
28 | marginLeft: 0,
29 | width: '100%',
30 | [theme.breakpoints.up('sm')]: {
31 | marginLeft: theme.spacing(3),
32 | width: 'auto',
33 | },
34 | }));
35 |
36 | const SearchIconWrapper = styled('div')(({ theme }) => ({
37 | padding: theme.spacing(0, 2),
38 | height: '100%',
39 | position: 'absolute',
40 | pointerEvents: 'none',
41 | display: 'flex',
42 | alignItems: 'center',
43 | justifyContent: 'center',
44 | }));
45 |
46 | const StyledInputBase = styled(InputBase)(({ theme }) => ({
47 | color: 'inherit',
48 | '& .MuiInputBase-input': {
49 | padding: theme.spacing(1, 1, 1, 0),
50 | // vertical padding + font size from searchIcon
51 | paddingLeft: `calc(1em + ${theme.spacing(4)})`,
52 | transition: theme.transitions.create('width'),
53 | width: '100%',
54 | [theme.breakpoints.up('md')]: {
55 | width: '20ch',
56 | },
57 | },
58 | }));
59 |
60 | export default function Navbar(props) {
61 |
62 | const { toggleTheme } = props;
63 | const theme = useTheme();
64 | const dispatch = useDispatch();
65 | const { user } = useSelector((state) => state.auth);
66 |
67 | const logout = () => {
68 | dispatch(clearUserAndToken());
69 | };
70 |
71 | return (
72 | <>
73 |
74 |
75 |
76 |
83 | Quizzio
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 | {!user && (
93 | <>
94 |
102 |
110 | >
111 | )}
112 | {!!user && (
113 | <>
114 |
117 |
118 |
119 |
120 |
123 | >
124 | )}
125 | toggleTheme()} sx={{ ml: 1 }}>
126 | {theme.palette.mode === 'dark' ? : }
127 |
128 |
129 |
130 |
131 |
132 | >
133 | );
134 | }
135 |
--------------------------------------------------------------------------------
/src/components/speed-dial/index.jsx:
--------------------------------------------------------------------------------
1 | import MUISpeedDial from '@mui/material/SpeedDial';
2 | import SpeedDialIcon from '@mui/material/SpeedDialIcon';
3 | import SpeedDialAction from '@mui/material/SpeedDialAction';
4 |
5 | const SpeedDial = (props) => {
6 | const {
7 | style = { position: 'absolute', bottom: 16, right: 16 },
8 | icon = ,
9 | actions = [],
10 | onOpen = undefined,
11 | onClose = undefined,
12 | open = undefined,
13 | closeOnActionClick = false,
14 | } = props;
15 | return (
16 |
24 | {actions.map((action, idx) => (
25 | {
27 | action.onClick();
28 | if (closeOnActionClick) onClose();
29 | }}
30 | key={idx}
31 | icon={action.icon}
32 | tooltipTitle={action.name}
33 | />
34 | ))}
35 |
36 | );
37 | };
38 |
39 | export default SpeedDial;
40 |
--------------------------------------------------------------------------------
/src/components/text-editor/index.jsx:
--------------------------------------------------------------------------------
1 | import { Editor } from '@tinymce/tinymce-react';
2 | import tinymce from 'tinymce/tinymce';
3 | import { useTheme } from '@mui/material';
4 | import { uploadImages } from 'api/utils';
5 | import { useSnackbar } from 'notistack';
6 | // Theme
7 | import 'tinymce/themes/silver';
8 | import 'tinymce/themes/mobile';
9 | import 'tinymce/icons/default';
10 | // import 'tinymce/skins/ui/oxide-dark/skin.min.css';
11 | import 'tinymce/skins/ui/oxide/skin.min.css';
12 | // importing the plugin js.
13 | import 'tinymce/plugins/advlist';
14 | import 'tinymce/plugins/autolink';
15 | import 'tinymce/plugins/link';
16 | import 'tinymce/plugins/image';
17 | import 'tinymce/plugins/imagetools';
18 | import 'tinymce/plugins/lists';
19 | import 'tinymce/plugins/charmap';
20 | import 'tinymce/plugins/hr';
21 | import 'tinymce/plugins/anchor';
22 | import 'tinymce/plugins/searchreplace';
23 | import 'tinymce/plugins/wordcount';
24 | import 'tinymce/plugins/code';
25 | import 'tinymce/plugins/fullscreen';
26 | import 'tinymce/plugins/insertdatetime';
27 | import 'tinymce/plugins/media';
28 | import 'tinymce/plugins/nonbreaking';
29 | import 'tinymce/plugins/table';
30 | import 'tinymce/plugins/help';
31 | import 'tinymce/plugins/template';
32 | import 'tinymce/plugins/print';
33 | import 'tinymce/plugins/preview';
34 | import 'tinymce/plugins/paste';
35 | import 'tinymce/plugins/importcss';
36 | import 'tinymce/plugins/autosave';
37 | import 'tinymce/plugins/directionality';
38 | import 'tinymce/plugins/visualblocks';
39 | import 'tinymce/plugins/visualchars';
40 | import 'tinymce/plugins/codesample';
41 | import 'tinymce/plugins/pagebreak';
42 | import 'tinymce/plugins/toc';
43 | import 'tinymce/plugins/textpattern';
44 | import 'tinymce/plugins/noneditable';
45 | import 'tinymce/plugins/noneditable';
46 | import 'tinymce/plugins/quickbars';
47 | import 'tinymce/plugins/emoticons';
48 |
49 | const plugins =
50 | 'print preview paste importcss searchreplace autolink autosave save directionality code visualblocks visualchars fullscreen image link media template codesample table charmap hr pagebreak nonbreaking anchor toc insertdatetime advlist lists wordcount imagetools textpattern noneditable help quickbars emoticons';
51 | const menubar = 'file edit view insert format tools table help';
52 | const toolbar =
53 | 'undo redo | bold italic underline strikethrough | fontselect fontsizeselect formatselect | alignleft aligncenter alignright alignjustify | outdent indent | numlist bullist | forecolor backcolor removeformat | pagebreak | charmap emoticons | fullscreen preview save print | insertfile image media template link anchor codesample | ltr rtl';
54 |
55 | const TinyEditor = (props) => {
56 | const theme = useTheme();
57 | const { enqueueSnackbar } = useSnackbar();
58 | const { width = 700, height = 350, value, onChange } = props;
59 | const handleEditorChange = (e) => {
60 | console.log('Content was updated:', e.target.getContent());
61 | };
62 |
63 | const filePickerHandler = (cb, value, meta) => {
64 | console.log('File Picker!');
65 | let input = document.createElement('input');
66 | input.setAttribute('type', 'file');
67 | // input.setAttribute('accept', 'image/*');
68 | input.onchange = async function () {
69 | // let file = this.files[0];
70 | // let reader = new FileReader();
71 | // reader.onload = function () {
72 | // let id = 'blobid' + new Date().getTime();
73 | // let blobCache = tinymce.activeEditor.editorUpload.blobCache;
74 | // let base64 = reader.result.split(',')[1];
75 | // let blobInfo = blobCache.create(id, file, base64);
76 | // blobCache.add(blobInfo);
77 | // cb(blobInfo.blobUri(), { title: file.name });
78 | // };
79 | // reader.readAsDataURL(file);
80 | try {
81 | const file = this.files[0];
82 | const formData = new FormData();
83 | formData.append('image', file);
84 | const res = await uploadImages(formData);
85 | const { url, public_id } = res.data.data.urls[0];
86 | cb(url, { title: public_id });
87 | } catch (err) {
88 | enqueueSnackbar(err.message, { variant: 'error' });
89 | }
90 | };
91 | input.click();
92 | };
93 |
94 | const editorOptions = {
95 | selector: 'textarea',
96 | skin: theme.palette.mode === 'dark' ? 'oxide-dark' : 'oxide',
97 | content_css: theme.palette.mode === 'dark' ? 'dark' : 'default',
98 | // content_style: [contentCss, contentUiCss].join('\n'),
99 | width,
100 | height,
101 | menubar,
102 | plugins,
103 | toolbar,
104 | toolbar_sticky: true,
105 | autosave_ask_before_unload: true,
106 | autosave_interval: '30s',
107 | autosave_prefix: '{path}{query}-{id}-',
108 | autosave_restore_when_empty: false,
109 | autosave_retention: '2m',
110 | image_advtab: true,
111 | image_title: true,
112 | automatic_uploads: true,
113 | file_picker_types: 'file image media',
114 | file_picker_callback: filePickerHandler,
115 | link_list: [
116 | { title: 'My page 1', value: 'https://www.tiny.cloud' },
117 | { title: 'My page 2', value: 'http://www.moxiecode.com' },
118 | ],
119 | image_list: [
120 | { title: 'My page 1', value: 'https://www.tiny.cloud' },
121 | { title: 'My page 2', value: 'http://www.moxiecode.com' },
122 | ],
123 | image_class_list: [
124 | { title: 'None', value: '' },
125 | { title: 'Some class', value: 'class-name' },
126 | ],
127 | importcss_append: true,
128 | templates: [
129 | {
130 | title: 'New Table',
131 | description: 'creates a new table',
132 | content:
133 | '',
134 | },
135 | {
136 | title: 'Starting my story',
137 | description: 'A cure for writers block',
138 | content: 'Once upon a time...',
139 | },
140 | {
141 | title: 'New list with dates',
142 | description: 'New List with dates',
143 | content:
144 | '',
145 | },
146 | ],
147 | template_cdate_format: '[Date Created (CDATE): %m/%d/%Y : %H:%M:%S]',
148 | template_mdate_format: '[Date Modified (MDATE): %m/%d/%Y : %H:%M:%S]',
149 | image_caption: true,
150 | quickbars_selection_toolbar: 'bold italic | quicklink h2 h3 blockquote quickimage quicktable',
151 | noneditable_noneditable_class: 'mceNonEditable',
152 | toolbar_mode: 'sliding',
153 | contextmenu: 'link image imagetools table',
154 | };
155 |
156 | return (
157 | onChange(value, editorRef)}
163 | />
164 | );
165 | };
166 |
167 | export default TinyEditor;
168 |
--------------------------------------------------------------------------------
/src/components/text-input-field/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { TextField, Typography, Button, InputAdornment, IconButton } from '@mui/material';
3 | import Visibility from '@mui/icons-material/Visibility';
4 |
5 | const TextInputField = ({
6 | label,
7 | placeholder,
8 | disabled = false,
9 | required,
10 | name,
11 | type,
12 | color,
13 | onChange,
14 | value,
15 | fullWidth,
16 | row = '',
17 | rowMax = '',
18 | multiline,
19 | style = {},
20 | labelStyle = {},
21 | labelClassName,
22 | className,
23 | variant,
24 | showActionBtn = false,
25 | actionBtnText = '',
26 | actionOnClick = () => {},
27 | endIcon,
28 | startIcon,
29 | endIconOnClick = () => {},
30 | startIconOnClick = () => {},
31 | InputPropsStyle = {},
32 | }) => {
33 | return (
34 |
35 |
36 | {label}
37 | {required ? * : null}
38 |
39 |
40 |
41 |
62 | startIconOnClick(e)}>
63 | {startIcon}
64 |
65 |
66 | ),
67 | endAdornment: endIcon && (
68 |
69 | endIconOnClick(e)}>
70 | {endIcon}
71 |
72 |
73 | ),
74 | }}
75 | />
76 |
77 | {/* Password */}
78 | {showActionBtn && (
79 |
82 | )}
83 |
84 |
85 | );
86 | };
87 |
88 | export default TextInputField;
89 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&display=swap');
2 |
3 | html {
4 | font-size: 62.5%;
5 | /* We are setting the html font size to 10px i.e. 62.5% of 16px (default) */
6 | /* So 1rem = 10px */
7 | }
8 |
9 | *::-webkit-scrollbar {
10 | width: 5px;
11 | }
12 |
13 | *::-webkit-scrollbar-track {
14 | border-radius: 10px;
15 | box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.1);
16 | }
17 |
18 | *::-webkit-scrollbar-thumb {
19 | border-radius: 10px;
20 | background-color: rgb(211, 211, 211);
21 | }
22 |
23 |
24 | .basicStyle {
25 | margin: 2rem;
26 | }
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import { BrowserRouter } from 'react-router-dom';
4 | import store from 'redux/store';
5 | import { Provider } from 'react-redux';
6 | import './index.css';
7 | import App from './app';
8 |
9 | ReactDOM.render(
10 |
11 |
12 |
13 |
14 | ,
15 | document.getElementById('root')
16 | );
17 |
--------------------------------------------------------------------------------
/src/layouts/a.css:
--------------------------------------------------------------------------------
1 | .css-r97pbf-MuiPaper-root-MuiDialog-paper {
2 | max-width: 580px !important;
3 | }
--------------------------------------------------------------------------------
/src/layouts/login.jsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import { useDispatch, useSelector } from 'react-redux';
3 | import { Button } from '@mui/material';
4 | import { DialogActions, DialogContent, DialogTitle, Dialog } from 'components/dialog';
5 | import { dialogNames, hideVisibility } from 'redux/slices/dialog-visibility';
6 | import { setUserAndToken } from 'redux/slices/auth';
7 | import TextInputField from 'components/text-input-field';
8 | import { loginWithEmailAndPassword } from 'api/user';
9 | import { useSnackbar } from 'notistack';
10 | import './a.css';
11 |
12 | const Login = () => {
13 | const dispatch = useDispatch();
14 | const { enqueueSnackbar } = useSnackbar();
15 | const { [dialogNames.login]: loginVisibility } = useSelector((state) => state.dialogVisibility);
16 | const [email, setEmail] = useState('');
17 | const [password, setPassword] = useState('');
18 |
19 | const handleClose = () => {
20 | dispatch(hideVisibility(dialogNames.login));
21 | };
22 |
23 | const handleLogin = async () => {
24 | try {
25 | const res = await loginWithEmailAndPassword(email, password);
26 | console.log(res);
27 | dispatch(setUserAndToken({ user: res.data.data.user, token: res.data.data.token }));
28 | handleClose();
29 | } catch (err) {
30 | enqueueSnackbar(err.message, { variant: 'error' });
31 | }
32 | };
33 |
34 | return (
35 |
64 | );
65 | };
66 |
67 | export default Login;
68 |
--------------------------------------------------------------------------------
/src/layouts/questionType-result/fill-blanks.jsx:
--------------------------------------------------------------------------------
1 | import TextInputField from 'components/text-input-field';
2 | import { Typography, Alert, AlertTitle } from '@mui/material/';
3 | import { useTheme, makeStyles } from '@mui/styles';
4 |
5 | const useStyles = makeStyles((theme) => ({
6 | incorrectText: {
7 | textFillColor: 'crimson !important',
8 | fontWeight: 500,
9 | color: 'crimson',
10 | },
11 | }));
12 |
13 | const FillBlanks = (props) => {
14 | const { questionId, userAnswer = '', correctAnswer } = props;
15 |
16 | const theme = useTheme();
17 | const classes = useStyles();
18 |
19 | const getInputTextStyle = (isCorrect) => {
20 | const style = { fontWeight: 500 };
21 | if (isCorrect) style.color = theme.palette.success.main;
22 | else style.color = theme.palette.error.main;
23 | return style;
24 | };
25 |
26 | return (
27 | <>
28 |
35 | {userAnswer ? 'Your Answer' : 'Answer'}
36 |
37 |
38 | {userAnswer && (
39 |
46 | )}
47 |
48 | {correctAnswer && correctAnswer.toLowerCase() !== userAnswer.toLowerCase() && (
49 |
50 | Correct Answer : {correctAnswer}
51 |
52 | )}
53 | >
54 | );
55 | };
56 | // ffccd5
57 | // ffb3c1
58 | export default FillBlanks;
59 |
--------------------------------------------------------------------------------
/src/layouts/questionType-result/mcq-multiple.jsx:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react';
2 | import { FormGroup, FormControlLabel, Checkbox, Typography } from '@mui/material';
3 | import { pink } from '@mui/material/colors';
4 | import { useTheme } from '@mui/styles';
5 |
6 | const MCQMultiple = (props) => {
7 | const { questionId, options, userAnswer = '', correctAnswer } = props;
8 | const theme = useTheme();
9 |
10 | // -------------------------------------------------------
11 | // LOGIC FOR THE COLOR OF THE CORRECT AND WRONG
12 | // --------------------------------------------------------
13 | const getBackgroundColor = (optionId) => {
14 | if (correctAnswer.includes(optionId)) return 'rgb(237, 247, 237)';
15 |
16 | if (userAnswer.includes(optionId)) return '#ffccd5';
17 |
18 | return theme.palette.background.default;
19 | };
20 |
21 | const getRedTick = (optionId) => {
22 | if (userAnswer.includes(optionId) && !correctAnswer.includes(optionId))
23 | return {
24 | color: pink[800],
25 | '&.Mui-checked': { color: pink[600] }, // Red CheckBox Tick
26 | };
27 | };
28 |
29 | return (
30 | <>
31 |
32 | {userAnswer ? 'Your Answer' : 'Answer'}
33 |
34 |
35 |
36 | {options.map((o) => (
37 | }
39 | label={o.data}
40 | style={{ backgroundColor: getBackgroundColor(o.id), width: '50%', margin: '0.4rem 0', borderRadius: theme.shape.borderRadius }}
41 | />
42 | ))}
43 |
44 | >
45 | );
46 | };
47 |
48 | export default MCQMultiple;
49 |
--------------------------------------------------------------------------------
/src/layouts/questionType-result/mcq-single.jsx:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react';
2 | import { Radio, RadioGroup, FormControlLabel, FormControl, Typography } from '@mui/material';
3 | import { useTheme } from '@mui/styles';
4 |
5 | const MCQSingle = (props) => {
6 | const { questionId, options, userAnswer = '', correctAnswer } = props;
7 | const theme = useTheme();
8 |
9 | const getBackgroundColor = (optionId) => {
10 | if (optionId === correctAnswer[0]) return 'rgb(237, 247, 237)';
11 | if (optionId === userAnswer[0]) return '#ffccd5';
12 | return theme.palette.background.default;
13 | };
14 |
15 | return (
16 | <>
17 |
18 | {userAnswer ? 'Your Answer' : 'Answer'}
19 |
20 |
21 |
22 |
23 | {options.map((o) => (
24 | }
27 | label={o.data}
28 | style={{ backgroundColor: getBackgroundColor(o.id), margin: '0.4rem 0', borderRadius: theme.shape.borderRadius }}
29 | />
30 | ))}
31 |
32 |
33 | >
34 | );
35 | };
36 |
37 | export default MCQSingle;
38 |
--------------------------------------------------------------------------------
/src/layouts/register.jsx:
--------------------------------------------------------------------------------
1 | import { useDispatch, useSelector } from 'react-redux';
2 | import { useState } from 'react';
3 | import { DialogActions, DialogContent, DialogTitle, Dialog } from 'components/dialog';
4 | import { dialogNames, hideVisibility, enableVisibility } from 'redux/slices/dialog-visibility';
5 | import { Grid, Button } from '@mui/material';
6 | import { makeStyles, useTheme } from '@mui/styles';
7 | import TextInputField from 'components/text-input-field';
8 | import DatePicker from 'components/date-time-picker/date';
9 | import DropdownField from 'components/dropdown-field';
10 | import Visibility from '@mui/icons-material/Visibility';
11 | import VisibilityOff from '@mui/icons-material/VisibilityOff';
12 | import { useSnackbar } from 'notistack';
13 | import './a.css';
14 |
15 | import { registerNewUser } from 'api/user';
16 | import { setUserAndToken } from 'redux/slices/auth';
17 |
18 | const genders = [
19 | { label: 'Male', value: 'male' },
20 | { label: 'Female', value: 'female' },
21 | { label: 'Other', value: 'other' },
22 | ];
23 |
24 | const useStyles = makeStyles((theme) => ({
25 | MaxWidth: {
26 | maxWidth: '600px !important',
27 | },
28 | }));
29 |
30 | const Register = () => {
31 | const classes = useStyles();
32 | const { enqueueSnackbar } = useSnackbar();
33 | const [showPassword, setShowPassword] = useState(false);
34 | const [newUserData, setNewUserData] = useState({
35 | name: '',
36 | email: '',
37 | bio: '',
38 | dob: '',
39 | address: '',
40 | image: '',
41 | institution: '',
42 | password: '',
43 | gender: genders[0].value,
44 | phoneNumber: '',
45 | });
46 | const dispatch = useDispatch();
47 |
48 | const { [dialogNames.register]: registerVisibility } = useSelector((state) => state.dialogVisibility);
49 |
50 | const handleClose = () => {
51 | dispatch(hideVisibility(dialogNames.register));
52 | };
53 |
54 | const handleDataChange = (key, value) => {
55 | setNewUserData((u) => ({ ...u, [key]: value }));
56 | };
57 |
58 | const handleSubmit = async () => {
59 | try {
60 | const body = JSON.parse(JSON.stringify(newUserData));
61 | body.dob = '2018-03-29T13:34:00.000';
62 | const res = await registerNewUser(body);
63 | console.log(res);
64 | dispatch(setUserAndToken({ user: res.data.data.user, token: res.data.data.token }));
65 | handleClose();
66 | } catch (err) {
67 | enqueueSnackbar(err.message, { variant: 'error' });
68 | }
69 | };
70 |
71 | return (
72 |
188 | );
189 | };
190 |
191 | export default Register;
192 |
--------------------------------------------------------------------------------
/src/layouts/request-full-screen.jsx:
--------------------------------------------------------------------------------
1 |
2 | import { Typography, Button } from '@mui/material';
3 | import { useTheme } from '@mui/styles';
4 |
5 | const RequestFullScreen = (props) => {
6 |
7 | const theme = useTheme();
8 | const { EnterFullScreen = () => { } } = props;
9 |
10 | return (
11 |
14 |
15 | Enter Full Screen to continue
16 |
17 |
20 |
21 | );
22 | };
23 |
24 | export default RequestFullScreen;
25 |
--------------------------------------------------------------------------------
/src/pages/exam-creation/index.jsx:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react';
2 | import { useHistory } from 'react-router-dom';
3 | import { Container, Stepper, Step, StepButton } from '@mui/material';
4 | import Page1 from './page1';
5 | import Page2 from './page2';
6 | import Page3 from './page3';
7 | import { createExam } from 'api/exam';
8 | import { uploadImages } from 'api/utils';
9 | import { useSnackbar } from 'notistack';
10 | // import { makeStyles, useTheme } from '@mui/styles';
11 |
12 | const steps = ['Exam Details', 'Questions', 'Preview'];
13 |
14 | const combineDateAndTime = (date, time) => {
15 | date = new Date(date);
16 | time = new Date(time);
17 | let timeString = time.getHours() + ':' + time.getMinutes() + ':00';
18 | let year = date.getFullYear();
19 | let month = date.getMonth() + 1; // Jan is 0, dec is 11
20 | let day = date.getDate();
21 | let dateString = '' + year + '-' + month + '-' + day;
22 | let combined = new Date(dateString + ' ' + timeString);
23 | return combined;
24 | };
25 |
26 | const ExamCreation = () => {
27 | // const classes = useStyles();
28 | const history = useHistory();
29 | const { enqueueSnackbar } = useSnackbar();
30 | const [activeStep, setActiveStep] = useState(0);
31 | const [examDetails, setExamDetails] = useState({
32 | name: '',
33 | description: '',
34 | isPrivate: false,
35 | startTime: null,
36 | endTime: null,
37 | image: '',
38 | type: '',
39 | allowedUsers: [],
40 | tags: [],
41 | });
42 | const [questions, setQuestions] = useState([]);
43 |
44 | const handleStep = (step) => () => {
45 | setActiveStep(step);
46 | };
47 |
48 | const handleDetailsChange = (name, value) => {
49 | setExamDetails((d) => ({ ...d, [name]: value }));
50 | };
51 |
52 | const handleQuestionsChange = (q) => {
53 | if (questions.find((qs) => qs.id === q.id)) {
54 | setQuestions((x) => x.map((qs) => (qs.id === q.id ? q : qs)));
55 | } else {
56 | setQuestions((qs) => [...qs, q]);
57 | }
58 | };
59 |
60 | const handleSubmitExamData = async () => {
61 | try {
62 | const examData = JSON.parse(JSON.stringify(examDetails));
63 |
64 | examData.questions = questions;
65 | examData.duration = +new Date(examData.endTime) - +new Date(examData.startTime);
66 | examData.startTime = +new Date(examData.startTime);
67 | delete examData.endTime;
68 |
69 | examData.image = await handleUploadBannerImage();
70 | console.log(examData.image);
71 |
72 | // Final Creation of Question Set with
73 | const res = await createExam(examData);
74 | console.log('Exam Data : ', examData, ' Response : ', res);
75 | // history.push('/');
76 | } catch (err) {
77 | enqueueSnackbar(err.message, { variant: 'error' });
78 | }
79 | };
80 |
81 | const handleUploadBannerImage = async () => {
82 | try {
83 | const formData = new FormData();
84 | formData.append('image', examDetails.image);
85 | const res = await uploadImages(formData);
86 | console.log(res);
87 | return res.data.data.urls[0].url;
88 | } catch (err) {
89 | // enqueueSnackbar(err.message, { variant: 'error' });
90 | console.error(err);
91 | return ' ';
92 | }
93 | };
94 |
95 | return (
96 |
97 |
98 | {steps.map((label, index) => (
99 |
100 |
101 | {label}
102 |
103 |
104 | ))}
105 |
106 |
107 | {activeStep === 0 && }
108 | {activeStep === 1 && }
109 | {activeStep === 2 && }
110 |
111 | );
112 | };
113 |
114 | export default ExamCreation;
115 |
--------------------------------------------------------------------------------
/src/pages/exam-creation/page1.jsx:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react';
2 | import { Grid, Typography } from '@mui/material';
3 | import TextInputField from 'components/text-input-field';
4 | import DateTimePicker from 'components/date-time-picker/date-time';
5 | import MultiSelect from 'components/multi-select-dropdown';
6 | import DropdownField from 'components/dropdown-field';
7 | import FileUploadInput from 'components/file-upload-input';
8 | import TextEditor from 'components/text-editor';
9 | import UploadFileIcon from '@mui/icons-material/UploadFile';
10 | import { getExamTags, getExamTypes } from 'api/exam';
11 | import { csvToJSON } from 'utilities/functions';
12 |
13 | const Page1 = (props) => {
14 |
15 | const { examDetails, handleDetailsChange } = props;
16 | const [selectedTags, setSelectedTags] = useState([]);
17 | const [tags, setTags] = useState([]);
18 | const [examTypes, setExamTypes] = useState([]);
19 | const [examType, setExamType] = useState('public');
20 |
21 | useEffect(() => {
22 | fetchTags();
23 | fetchExamTypes();
24 | }, []);
25 |
26 | const fetchTags = async () => {
27 | const res = await getExamTags();
28 | setTags(res.data.data);
29 | };
30 |
31 | const fetchExamTypes = async () => {
32 | const res = await getExamTypes();
33 | setExamTypes(res.data);
34 | if (res.data.length > 0) setExamType(res.data[0].value);
35 | };
36 |
37 | const handleBannerUpload = (event) => {
38 | const file = event.target.files[0];
39 | console.log(file);
40 | handleDetailsChange('image', file);
41 | };
42 |
43 | const handleAllowedUsersUpload = (event) => {
44 | const file = event.target.files[0];
45 | console.log(file);
46 | const reader = new FileReader();
47 | reader.onload = (e) => {
48 | const str = reader.result;
49 | const json = csvToJSON(str);
50 | const emails = json.map((x) => x.email);
51 | console.log(emails);
52 | handleDetailsChange('allowedUsers', emails);
53 | reader.abort();
54 | };
55 | reader.readAsText(file);
56 | };
57 |
58 | return (
59 | <>
60 |
61 |
62 |
63 |
64 | handleDetailsChange(e.target.name, e.target.value)}
72 | />
73 |
74 |
75 | handleBannerUpload(e)}
82 | />
83 |
84 |
85 | handleDetailsChange('startTime', newTime)}
92 | />
93 |
94 |
95 | handleDetailsChange('endTime', newTime)}
102 | />
103 |
104 |
105 | setExamType(e.target.value)}
111 | name='isPrivate'
112 | value={examDetails.isPrivate ? 'private' : 'public'}
113 | onChange={(e) => handleDetailsChange(e.target.name, e.target.value === 'private')}
114 | />
115 |
116 |
117 | }
124 | name='allowedUsers'
125 | value={examDetails.allowedUsers}
126 | onChange={(e) => handleAllowedUsersUpload(e)}
127 | />
128 |
129 |
130 | handleDetailsChange(e.target.name, e.target.value)}
137 | />
138 |
139 |
140 |
141 |
142 |
143 |
144 | Description
145 |
146 | handleDetailsChange('description', value)}
151 | />
152 |
153 |
154 | >
155 | );
156 | };
157 |
158 | export default Page1;
159 |
--------------------------------------------------------------------------------
/src/pages/exam-creation/page2.jsx:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react';
2 | import { useHistory, useParams } from 'react-router-dom';
3 | import { makeStyles, useTheme } from '@mui/styles';
4 | import { Grid, Button } from '@mui/material';
5 | import TextEditor from 'components/text-editor';
6 | import TextInputField from 'components/text-input-field';
7 | import DropdownField from 'components/dropdown-field';
8 | import FileCopyIcon from '@mui/icons-material/FileCopyOutlined';
9 | import SaveIcon from '@mui/icons-material/Save';
10 | import PrintIcon from '@mui/icons-material/Print';
11 | import ShareIcon from '@mui/icons-material/Share';
12 | import AddIcon from '@mui/icons-material/Add';
13 | import { getQuestionTypes } from 'api/exam';
14 | import MCQ from './question-types/mcq';
15 | import FillBlanks from './question-types/fill-blanks';
16 |
17 | const actions = [
18 | {
19 | icon: ,
20 | name: 'Copy',
21 | onClick: () => {
22 | alert(1);
23 | },
24 | },
25 | { icon: , name: 'Save', onClick: () => {} },
26 | { icon: , name: 'Print', onClick: () => {} },
27 | { icon: , name: 'Share', onClick: () => {} },
28 | ];
29 |
30 | const useStyles = makeStyles((theme) => ({
31 | heading: {
32 | fontSize: '3.2rem',
33 | textAlign: 'center',
34 | marginBottom: '2rem',
35 | },
36 | bubble: {
37 | display: 'flex',
38 | justifyContent: 'center',
39 | alignItems: 'center',
40 | width: 50,
41 | minWidth: 0,
42 | height: 50,
43 | margin: '1rem',
44 | borderRadius: '50%',
45 | background: theme.palette.mode === 'dark' ? theme.palette.grey[600] : theme.palette.grey[400],
46 | color: theme.palette.common.white,
47 | },
48 | }));
49 |
50 | const Page2 = (props) => {
51 | const { questions, handleQuestionsChange } = props;
52 |
53 | const classes = useStyles();
54 | const theme = useTheme();
55 |
56 | console.log(questions);
57 | const [questionTypes, setQuestionTypes] = useState([]);
58 | const [currentQuestion, setCurrentQuestion] = useState(null);
59 |
60 | useEffect(() => {
61 | fetchQuestionTypes();
62 | if (questions.length === 0) {
63 | addNewQuestion();
64 | } else {
65 | setCurrentQuestion(questions[0]);
66 | }
67 | }, []);
68 |
69 | useEffect(() => {
70 | if (currentQuestion && !isNaN(currentQuestion.id)) {
71 | handleQuestionsChange(currentQuestion);
72 | }
73 | }, [currentQuestion]);
74 |
75 | const fetchQuestionTypes = async () => {
76 | const res = await getQuestionTypes();
77 | setQuestionTypes(res.data.data);
78 | };
79 |
80 | const addNewQuestion = () => {
81 | setCurrentQuestion({
82 | id: questions.length,
83 | type: 'mcq',
84 | question: '',
85 | options: [],
86 | correctOption: [],
87 | marks: 2,
88 | negMarks: 1,
89 | });
90 | };
91 |
92 | return (
93 |
94 | {/* EDITOR SECTION */}
95 | {currentQuestion && (
96 |
97 |
98 |
99 | setCurrentQuestion((q) => ({ ...q, type: e.target.value }))}
107 | />
108 |
109 |
110 |
111 |
119 | setCurrentQuestion((q) => ({
120 | ...q,
121 | marks: +e.target.value < 0 ? 0 : +e.target.value,
122 | }))
123 | }
124 | />
125 |
126 |
127 |
128 |
136 | setCurrentQuestion((q) => ({
137 | ...q,
138 | negMarks: +e.target.value < 0 ? 0 : +e.target.value,
139 | }))
140 | }
141 | />
142 |
143 |
144 |
145 | setCurrentQuestion((q) => ({ ...q, question: value }))} />
146 |
147 | )}
148 |
149 | {/* OPTION SECTION */}
150 | {currentQuestion && (
151 |
152 | {(currentQuestion.type === 'mcq' || currentQuestion.type === 'multipleOptions') && (
153 |
154 | )}
155 |
156 | {currentQuestion.type === 'fillInTheBlanks' && }
157 |
158 | )}
159 |
160 | {/* BUTTONS -- FOR QUESTION NUMBER */}
161 |
162 |
163 | {questions.map((ques, i) => (
164 |
173 | ))}
174 |
175 |
178 |
179 |
180 |
181 | );
182 | };
183 |
184 | export default Page2;
185 |
--------------------------------------------------------------------------------
/src/pages/exam-creation/page3.jsx:
--------------------------------------------------------------------------------
1 | import { useState, useEffect, useDispatch } from 'react-redux';
2 | import { makeStyles, useTheme } from '@mui/styles';
3 | import { Typography, Button, Paper, Grid } from '@mui/material';
4 | import { showConfirmation } from 'redux/slices/confirmation-dialog';
5 | import MCQSingle from 'layouts/questionType-result/mcq-single';
6 | import MCQMultiple from 'layouts/questionType-result/mcq-multiple';
7 | import FillBlanks from 'layouts/questionType-result/fill-blanks';
8 | import DomPurify from 'dompurify';
9 | import '../../index.css';
10 | import commonStyle from '../../common-style.js';
11 |
12 | const useStyles = makeStyles((theme) => ({}));
13 |
14 | const Page3 = (props) => {
15 | const { questions, handleSubmitExamData } = props;
16 |
17 | const dispatch = useDispatch();
18 | const classes = useStyles();
19 | const theme = useTheme();
20 | const classes2 = commonStyle();
21 |
22 | const handleSubmit = () => {
23 | dispatch(
24 | showConfirmation({
25 | title: 'Create Exam',
26 | content: 'Are you sure you want to create this exam?',
27 | primaryBtnText: 'Yes',
28 | secondaryBtnText: 'No',
29 | onPrimaryBtnClick: handleSubmitExamData,
30 | // onSecondaryBtnClick: () => console.log('do nothing'),
31 | })
32 | );
33 | };
34 |
35 | console.log('All_Questions ==> ', questions);
36 |
37 | return (
38 |
39 |
PREVIEW
40 |
41 | {questions && questions.length > 0 && (
42 |
43 | {questions.map((eachQues, idx) => {
44 | return (
45 | -
46 | {/* ==================== MAIN QUESTION BODY ==================== */}
47 |
48 |
54 |
55 |
{`Marks: ${eachQues.marks}`}
56 |
{`Negative Marks: ${eachQues.negMarks}`}
57 |
58 |
59 | {eachQues.type === 'multipleOptions' && (
60 |
61 | )}
62 |
63 | {eachQues.type === 'mcq' && (
64 |
65 | )}
66 |
67 | {eachQues.type === 'fillInTheBlanks' && }
68 |
69 |
70 |
71 | );
72 | })}
73 |
74 | )}
75 |
76 |
77 |
80 |
83 |
84 |
85 | );
86 | };
87 |
88 | export default Page3;
89 |
--------------------------------------------------------------------------------
/src/pages/exam-creation/question-types/fill-blanks.jsx:
--------------------------------------------------------------------------------
1 | import TextInputField from 'components/text-input-field';
2 |
3 | const FillBlanks = (props) => {
4 | const { currentQuestion, setCurrentQuestion } = props;
5 |
6 | return (
7 | <>
8 | setCurrentQuestion((q) => ({ ...q, correctOption: e.target.value }))}
15 | />
16 | >
17 | );
18 | };
19 |
20 | export default FillBlanks;
21 |
--------------------------------------------------------------------------------
/src/pages/exam-creation/question-types/mcq.jsx:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react';
2 | import { Grid, Typography, Box, Button } from '@mui/material';
3 | import { makeStyles } from '@mui/styles';
4 | import Bullet from '@mui/icons-material/FiberManualRecord';
5 | import TextInputField from 'components/text-input-field';
6 |
7 | const useStyles = makeStyles((theme) => ({
8 | labelSmall: {
9 | fontSize: '1.4rem',
10 | },
11 | }));
12 |
13 | const MCQ = (props) => {
14 | const classes = useStyles();
15 | const { currentQuestion, setCurrentQuestion } = props;
16 |
17 | const [newOption, setNewOption] = useState('');
18 | const [newCorrectOption, setNewCorrectOption] = useState('');
19 |
20 | const addNewOption = () => {
21 | if (!newOption || !newOption.trim()) return;
22 | setCurrentQuestion((q) => ({ ...q, options: [...q.options, { id: q.options.length + 1 || 1, data: newOption }] }));
23 | setNewOption('');
24 | };
25 |
26 | const addCorrectOption = () => {
27 | if (!newCorrectOption || !newCorrectOption.trim()) return;
28 | if (currentQuestion.type === 'mcq' && currentQuestion.correctOption?.length >= 1) return;
29 | setCurrentQuestion((q) => ({ ...q, correctOption: [...q.correctOption, +newCorrectOption] }));
30 | setNewCorrectOption('');
31 | };
32 |
33 | return (
34 |
35 |
36 |
37 | Available Options :
38 |
39 | setNewOption(e.target.value)}
45 | showActionBtn
46 | fullWidth
47 | actionBtnText='Add'
48 | actionOnClick={(e) => addNewOption()}
49 | />
50 |
51 | {currentQuestion.options &&
52 | currentQuestion.options.length != 0 &&
53 | currentQuestion.options.map((item, index) => (
54 |
55 | {index + 1}) {item.data}
56 |
57 | ))}
58 |
59 |
60 |
61 |
62 |
63 | {`Correct Option${currentQuestion.type === 'multipleOptions' ? 's' : ''}:`}
64 |
65 | setNewCorrectOption(e.target.value)}
73 | fullWidth
74 | showActionBtn
75 | actionBtnText='Add'
76 | actionOnClick={(e) => addCorrectOption()}
77 | />
78 |
79 | {currentQuestion.correctOption &&
80 | currentQuestion.correctOption.length != 0 &&
81 | currentQuestion.correctOption.map((item, index) => (
82 |
83 | {item}
84 |
85 | ))}
86 |
87 |
88 |
89 | );
90 | };
91 |
92 | export default MCQ;
93 |
--------------------------------------------------------------------------------
/src/pages/exam-details/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import {
3 | Container,
4 | Grid,
5 | Typography,
6 | Box,
7 | Tab,
8 | Tabs,
9 | Button,
10 | Paper,
11 | } from "@mui/material";
12 | import { useTheme } from "@mui/styles";
13 | import { useHistory, useLocation } from "react-router-dom";
14 | import { useSelector, useDispatch } from "react-redux";
15 | import useStyles from "./styles";
16 | import {
17 | getExamDetails,
18 | registerInExam,
19 | getUserExamRegisterStatus,
20 | getExamResult,
21 | getExamScores,
22 | } from "api/exam";
23 | import { showConfirmation } from "redux/slices/confirmation-dialog";
24 | import DomPurify from "dompurify";
25 | import { useSnackbar } from "notistack";
26 | import { getUnitsFromDuration } from "utilities/functions";
27 | import About from "./tab-content/about";
28 | import Scores from "./tab-content/scores";
29 | import Result from "./tab-content/result";
30 |
31 | let timerInterval;
32 |
33 | const TabPanel = (props) => {
34 | const { children, value, index, ...other } = props;
35 | return (
36 |
37 | {value === index && (
38 |
39 | {children}
40 |
41 | )}
42 |
43 | );
44 | };
45 |
46 | const a11yProps = (index) => {
47 | return {
48 | id: `vertical-tab-${index}`,
49 | fontWeight: "bold",
50 | };
51 | };
52 |
53 | const Exam_Details = () => {
54 | const history = useHistory();
55 | const location = useLocation();
56 | const classes = useStyles();
57 | const { enqueueSnackbar } = useSnackbar();
58 | const dispatch = useDispatch();
59 | const theme = useTheme();
60 | const examId = location.pathname.split("/")[2];
61 |
62 | const { user } = useSelector((state) => state.auth);
63 | const [value, setValue] = useState(0);
64 | const [examData, setExamData] = useState({});
65 | const [registerStatus, setRegisterStatus] = useState(false);
66 | const [resultDetails, setResultDetails] = useState(null);
67 | const [examScores, setExamScores] = useState(null);
68 | const cleanDescription = DomPurify.sanitize(examData.description);
69 |
70 | // Timer states
71 | const [remainingTime, setRemainingTime] = useState({
72 | days: null,
73 | hours: null,
74 | minutes: null,
75 | seconds: null,
76 | });
77 |
78 | useEffect(() => {
79 | if (!examData?.startTime) return;
80 | const duration = new Date(examData.startTime) - Date.now();
81 | if (duration < 0) {
82 | return setRemainingTime({ days: 0, hours: 0, minutes: 0, seconds: 0 });
83 | }
84 | const dd = getUnitsFromDuration(duration);
85 | console.log(duration, dd);
86 | setRemainingTime({
87 | days: dd.days,
88 | hours: dd.hours,
89 | minutes: dd.minutes,
90 | seconds: dd.seconds,
91 | });
92 | }, [examData.startTime]);
93 |
94 | useEffect(() => {
95 | if (
96 | remainingTime.days === null ||
97 | remainingTime.hours === null ||
98 | remainingTime.minutes === null ||
99 | remainingTime.seconds === null
100 | ) {
101 | return;
102 | }
103 |
104 | clearInterval(timerInterval);
105 | timerInterval = setInterval(() => {
106 | if (remainingTime.seconds > 0) {
107 | setRemainingTime((t) => ({ ...t, seconds: t.seconds - 1 }));
108 | }
109 | if (remainingTime.seconds === 0) {
110 | if (remainingTime.minutes === 0) {
111 | if (remainingTime.hours === 0) {
112 | if (remainingTime.days === 0) {
113 | // Timer has finished
114 | clearInterval(timerInterval);
115 | } else {
116 | setRemainingTime((t) => ({
117 | ...t,
118 | days: t.days - 1,
119 | hours: 23,
120 | minutes: 59,
121 | seconds: 59,
122 | }));
123 | }
124 | } else {
125 | setRemainingTime((t) => ({
126 | ...t,
127 | hours: t.hours - 1,
128 | minutes: 59,
129 | seconds: 59,
130 | }));
131 | }
132 | } else {
133 | setRemainingTime((t) => ({
134 | ...t,
135 | minutes: t.minutes - 1,
136 | seconds: 59,
137 | }));
138 | }
139 | }
140 | }, 1000);
141 |
142 | return () => {
143 | clearInterval(timerInterval);
144 | };
145 | }, [remainingTime]);
146 |
147 | // -------------------------------------
148 | // FETCH ALL EXAMS DATA
149 | // -------------------------------------
150 | useEffect(async () => {
151 | fetchExamDetails();
152 | checkRegisterStatus();
153 | fetchResult();
154 | fetchScores();
155 | window.scrollTo(0, 0);
156 | }, []);
157 |
158 | const fetchResult = async () => {
159 | try {
160 | const res = await getExamResult(examId);
161 | console.log(res);
162 | if (res.data.data) setResultDetails(res.data.data);
163 | } catch (err) {
164 | // enqueueSnackbar(err.message, { variant: 'error' });
165 | console.error(err);
166 | }
167 | };
168 |
169 | const fetchScores = async () => {
170 | try {
171 | const res = await getExamScores(examId);
172 | setExamScores(res.data.data);
173 | } catch (err) {
174 | // enqueueSnackbar(err.message, { variant: 'error' });
175 | console.error(err);
176 | }
177 | };
178 |
179 | const checkRegisterStatus = async () => {
180 | try {
181 | const res = await getUserExamRegisterStatus(examId);
182 | setRegisterStatus(!!res.data.data.registered);
183 | } catch (err) {
184 | enqueueSnackbar(err.message, { variant: "error" });
185 | }
186 | };
187 |
188 | const fetchExamDetails = async () => {
189 | try {
190 | const res = await getExamDetails(examId);
191 | setExamData(res.data.data);
192 | } catch (err) {
193 | enqueueSnackbar(err.message, { variant: "error" });
194 | }
195 | };
196 |
197 | const handleChange = (event, newValue) => {
198 | setValue(newValue);
199 | };
200 |
201 | const registerUserInExam = async () => {
202 | try {
203 | const res = await registerInExam({ examId });
204 | if (res.status === 200) {
205 | setRegisterStatus(true);
206 | }
207 | } catch (err) {
208 | enqueueSnackbar(err.message, { variant: "error" });
209 | }
210 | };
211 |
212 | const redirectToGiveExam = () => {
213 | history.push(`/exam/${examId}/give`);
214 | };
215 |
216 | const TextPair = ({ heading, value }) => {
217 | return (
218 |
219 |
223 | {heading}
224 |
225 | {value}
226 |
227 | );
228 | };
229 |
230 | const JoinCardText = ({ heading, value }) => {
231 | return (
232 |
233 |
234 |
237 | {heading}:
238 |
239 |
240 |
241 |
242 | {value}
243 |
244 |
245 |
246 | );
247 | };
248 |
249 | const getTypography = (text = "", style = {}) => {
250 | return (
251 |
256 | {text}
257 |
258 | );
259 | };
260 |
261 | const getButton = (text = "", type = "") => {
262 | return (
263 |
293 | );
294 | };
295 |
296 | const JoinRegisterSection = () => {
297 | if (!examData.startTime) return null;
298 | if (
299 | remainingTime.days === null ||
300 | remainingTime.hours === null ||
301 | remainingTime.minutes === null ||
302 | remainingTime.seconds === null
303 | )
304 | return null;
305 | const startTime = new Date(examData.startTime);
306 | const endTime = new Date(+startTime + examData.duration);
307 | const remain = Math.abs(
308 | remainingTime.days * 86400000 -
309 | remainingTime.hours * 3600000 -
310 | remainingTime.minutes * 60000 -
311 | remainingTime.seconds * 1000
312 | );
313 | // If days,hours,minutes,seconds are zeros, then remain = 0, and startTime - remain = 0, so currentTime should be Date.now()
314 | const currentTime = remain === 0 ? Date.now() : +startTime - remain;
315 | if (currentTime < startTime) {
316 | // before exam
317 | if (!user) {
318 | return (
319 | <>
320 | {getTypography("Login to register for exam")}
321 |
322 | >
323 | );
324 | } else {
325 | if (!registerStatus) {
326 | return (
327 | <>
328 | {getButton("Register", "register")}
329 |
330 | >
331 | );
332 | } else {
333 | return (
334 | <>
335 | {getTypography("You have registered")}
336 |
337 | >
338 | );
339 | }
340 | }
341 | } else if (currentTime >= startTime && currentTime < endTime) {
342 | if (!user) {
343 | return getTypography("Login to enter exam");
344 | }
345 | // user has not registered
346 | else if (!registerStatus) {
347 | return getTypography("You have not registered for this exam");
348 | } else {
349 | return getButton("Enter Exam", "start");
350 | }
351 | } else if (currentTime >= endTime) {
352 | // after exam
353 | return getTypography("Exam has ended", {
354 | color: "crimson",
355 | fontWeight: "900",
356 | });
357 | }
358 | };
359 |
360 | const CountdownTimer = () => {
361 | return (
362 |
363 | {`${remainingTime.days} days ${remainingTime.hours} hours ${remainingTime.minutes} minutes ${remainingTime.seconds} seconds`}
364 |
365 | );
366 | };
367 |
368 | const getExamDurationFormatted = () => {
369 | if (!examData.duration) return null;
370 | const { days, hours, minutes, seconds } = getUnitsFromDuration(
371 | examData.duration
372 | );
373 | let duration = "";
374 | if (days > 0) {
375 | duration += days === 1 ? `${days} day ` : `${days} days `;
376 | }
377 | if (hours > 0) {
378 | duration += hours === 1 ? `${hours} hour ` : `${hours} hours `;
379 | }
380 | if (minutes > 0) {
381 | duration += minutes === 1 ? `${minutes} minute ` : `${minutes} minutes `;
382 | }
383 | if (seconds > 0) {
384 | duration += seconds === 1 ? `${seconds} second ` : `${seconds} seconds `;
385 | }
386 | return duration;
387 | };
388 |
389 | return (
390 | <>
391 | {/* ----------- Banner ----------- */}
392 |
401 |
402 |
403 |
404 |

405 |
406 |
407 |
408 |
409 | {examData.name !== null ? examData.name : "-"}
410 |
411 |
412 | {" "}
413 | Author : {examData.user?.name}
414 |
415 |
416 |
417 |
418 | {/*-------------------------------------------------- */}
419 | {/* INFORMATION */}
420 | {/*-------------------------------------------------- */}
421 |
422 |
426 |
430 |
434 |
442 |
443 |
444 | {/*-------------------------------------------------- */}
445 | {/* EXAM TIME DISPLAY CARD */}
446 | {/*-------------------------------------------------- */}
447 |
448 |
452 |
453 |
461 |
462 |
466 |
467 |
468 | {/* Timer Part */}
469 |
470 |
471 |
472 |
473 |
474 |
475 | {/* SECTION - 2 */}
476 | {/* Side Vertical Tabs */}
477 |
478 |
484 |
489 |
495 |
501 |
506 |
511 |
512 |
513 |
514 |
515 |
516 |
517 |
518 |
519 |
520 |
521 |
522 |
523 |
524 |
525 |
526 | FAQ's
527 |
528 |
529 |
530 | Discussions
531 |
532 |
533 |
534 | >
535 | );
536 | };
537 |
538 | export default Exam_Details;
539 |
--------------------------------------------------------------------------------
/src/pages/exam-details/styles.js:
--------------------------------------------------------------------------------
1 | import { makeStyles } from '@mui/styles';
2 |
3 | export default makeStyles((theme) => ({
4 | banner: {
5 | width: '100%',
6 | objectFit: 'cover',
7 | objectPosition: 'center',
8 | },
9 | dp: {
10 | position: 'relative',
11 | transform: 'translateY(-40%)',
12 | borderRadius: '8px',
13 | width: 180,
14 | height: 180,
15 | objectFit: 'cover',
16 | },
17 | root: {
18 | flexGrow: 1,
19 | backgroundColor: theme.palette.background.paper,
20 | display: 'flex',
21 | minHeight: '50rem',
22 | paddingBottom: '10rem',
23 | fontWeight: '600',
24 | },
25 | tabVerticalLine: {
26 | borderRight: `2px solid ${theme.palette.divider}`,
27 | overflow: 'visible !important',
28 | },
29 | sliderTitle: {
30 | fontWeight: 'bold',
31 | },
32 | examInfo: {
33 | // padding: '1.6rem',
34 | width: '75%',
35 | display: 'grid',
36 | gridTemplateColumns: '1fr 1fr 1fr',
37 | },
38 | joinCard: {
39 | position: 'relative',
40 | width: '25%',
41 | backgroundColor: theme.palette.background.default,
42 | padding: '1.5rem',
43 | transform: 'translateY(-20%)', // Shifts Vertically
44 | },
45 | joinButton: {
46 | width: '100%',
47 | margin: '1rem auto',
48 | fontWeight: '600',
49 | textTransform: 'uppercase',
50 | },
51 | joinText: {
52 | margin: '0 auto',
53 | fontWeight: '600',
54 | textTransform: 'uppercase',
55 | textAlign: 'center',
56 | },
57 | }));
58 |
--------------------------------------------------------------------------------
/src/pages/exam-details/tab-content/about.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react';
2 | import { Grid, Typography } from '@mui/material';
3 | import { makeStyles, useTheme } from '@mui/styles';
4 | import commonStyle from '../../../common-style';
5 |
6 | const useStyles = makeStyles((theme) => ({}));
7 |
8 | const About = (props) => {
9 | const { content = '' } = props;
10 | const classes = useStyles();
11 | const classes2 = commonStyle();
12 | const theme = useTheme();
13 |
14 | return (
15 | <>
16 | DESCRIPTION
17 |
18 |
19 | >
20 | );
21 | };
22 |
23 | export default About;
24 |
--------------------------------------------------------------------------------
/src/pages/exam-details/tab-content/result.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react';
2 | import { Grid, Typography } from '@mui/material';
3 | import { makeStyles, useTheme } from '@mui/styles';
4 | import MCQSingle from 'layouts/questionType-result/mcq-single';
5 | import MCQMultiple from 'layouts/questionType-result/mcq-multiple';
6 | import FillBlanks from 'layouts/questionType-result/fill-blanks';
7 | import DomPurify from 'dompurify';
8 | import commonStyle from '../../../common-style';
9 |
10 | const useStyles = makeStyles((theme) => ({}));
11 |
12 | const Result = (props) => {
13 | const { ['result']: resultDetails } = props;
14 | const classes = useStyles();
15 | const classes2 = commonStyle();
16 | const theme = useTheme();
17 |
18 | if (!resultDetails) return null;
19 |
20 | return (
21 | <>
22 | RESULT
23 |
24 | {resultDetails && resultDetails.length > 0 && (
25 |
26 | {resultDetails.map((ques, idx) => {
27 | return (
28 | -
29 | {/* ==================== MAIN QUESTION BODY ==================== */}
30 |
31 |
37 |
38 |
39 | {ques.type === 'multipleOptions' && (
40 |
41 | )}
42 |
43 | {ques.type === 'mcq' && (
44 |
45 | )}
46 |
47 | {ques.type === 'fillInTheBlanks' && (
48 |
49 | )}
50 |
51 |
52 |
53 | );
54 | })}
55 |
56 | )}
57 | >
58 | );
59 | };
60 |
61 | export default Result;
62 |
--------------------------------------------------------------------------------
/src/pages/exam-details/tab-content/scores.jsx:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react';
2 | import { useDispatch } from 'react-redux';
3 | import { showConfirmation } from 'redux/slices/confirmation-dialog';
4 | import { useHistory } from 'react-router-dom';
5 | import { useSnackbar } from 'notistack';
6 | import { Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Paper, Card, CardContent, Box, Typography } from '@mui/material';
7 | import { makeStyles, useTheme } from '@mui/styles';
8 | import commonStyle from '../../../common-style';
9 | import crown from '../../../assets/icons/crown.png';
10 |
11 | const useStyles = makeStyles((theme) => ({
12 | card: {
13 | marginBottom: '2rem',
14 | paddingBottom: '2rem',
15 | marginTop: '2.6rem',
16 | textAlign: 'center',
17 | },
18 | }));
19 |
20 | const Scores = (props) => {
21 | const { examScoresDetails } = props;
22 | const dispatch = useDispatch();
23 | const classes = useStyles();
24 | const classes2 = commonStyle();
25 | const history = useHistory();
26 | const theme = useTheme();
27 | const { enqueueSnackbar, closeSnackbar } = useSnackbar();
28 |
29 | console.log(examScoresDetails);
30 |
31 | if (!examScoresDetails) return null;
32 |
33 | return (
34 | <>
35 | SCORES
36 |
37 | {/*
38 |
54 |
55 |
56 |
57 |
*/}
58 |
59 |
60 |
61 |
62 |
63 |
64 | Your Score
65 |
66 |
67 |
68 |
69 | COMPLETED AT
70 |
71 | {new Date(examScoresDetails.finishTime).toDateString()} , {new Date(examScoresDetails.finishTime).toLocaleTimeString()}
72 |
73 |
74 |
75 |
76 | RANK
77 |
78 | {examScoresDetails.rank !== null ? ` Position ${examScoresDetails.rank}` : '-'}
79 |
80 |
81 |
82 |
83 | SCORE ACHIEVED
84 |
85 | {examScoresDetails.totalScore !== null ? examScoresDetails.totalScore : '-'}
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 | Top 5 Performers
97 |
98 |
99 |
100 |
101 |
102 |
103 | Rank
104 | Name
105 | Achieved Score
106 | Completed At
107 |
108 |
109 |
110 |
111 | {examScoresDetails.topPerformers &&
112 | examScoresDetails.topPerformers.map((user) => (
113 |
117 | {user.rank}
118 | {user.name}
119 | {user.totalScore}
120 |
121 | {' '}
122 | {new Date(user.finishTime).toDateString()} , {new Date(user.finishTime).toLocaleTimeString()}{' '}
123 |
124 |
125 | ))}
126 |
127 |
128 |
129 | >
130 | );
131 | };
132 |
133 | export default Scores;
134 |
--------------------------------------------------------------------------------
/src/pages/give-exam/index.jsx:
--------------------------------------------------------------------------------
1 | import { useState, useEffect, useRef } from 'react';
2 | import { Container, Typography, Button, Paper, Grid } from '@mui/material';
3 | import { useLocation, useHistory } from 'react-router-dom';
4 | import { submitExamAnswers, startExam } from 'api/exam';
5 | import Page1 from './page1';
6 | import Page2 from './page2';
7 | import RequestFullScreen from 'layouts/request-full-screen';
8 | import { getUnitsFromDuration } from 'utilities/functions';
9 | import { useSnackbar } from 'notistack';
10 |
11 | let timerInterval;
12 |
13 | const GiveExam = () => {
14 | const location = useLocation();
15 | const history = useHistory();
16 | const { enqueueSnackbar } = useSnackbar();
17 | const examId = location.pathname.split('/')[2];
18 | const [examData, setExamData] = useState({});
19 | const [answer, setAnswer] = useState({});
20 | const [questionsStatus, setQuestionsStatus] = useState({});
21 | const [page, setPage] = useState(1);
22 | const [isFullScreen, setIsFullScreen] = useState(false);
23 | const documentElement = document.documentElement;
24 | const [remainingTime, setRemainingTime] = useState({
25 | days: null,
26 | hours: null,
27 | minutes: null,
28 | seconds: null,
29 | });
30 |
31 | const questions = examData.questions || [];
32 |
33 | useEffect(() => {
34 | EnterFullScreen();
35 | documentElement.addEventListener('fullscreenchange', () => {
36 | if (document.fullscreenElement) setIsFullScreen(true);
37 | else setIsFullScreen(false);
38 | });
39 | getExamData();
40 | }, []);
41 |
42 | useEffect(() => {
43 | if (remainingTime.days === null || remainingTime.hours === null || remainingTime.minutes === null || remainingTime.seconds === null) {
44 | return;
45 | }
46 | clearInterval(timerInterval);
47 | timerInterval = setInterval(() => {
48 | if (remainingTime.seconds > 0) {
49 | setRemainingTime((t) => ({ ...t, seconds: t.seconds - 1 }));
50 | }
51 | if (remainingTime.seconds === 0) {
52 | if (remainingTime.minutes === 0) {
53 | if (remainingTime.hours === 0) {
54 | if (remainingTime.days === 0) {
55 | // Timer has finished
56 | clearInterval(timerInterval);
57 | handleEndExam();
58 | } else {
59 | setRemainingTime((t) => ({ ...t, days: t.days - 1, hours: 23, minutes: 59, seconds: 59 }));
60 | }
61 | } else {
62 | setRemainingTime((t) => ({ ...t, hours: t.hours - 1, minutes: 59, seconds: 59 }));
63 | }
64 | } else {
65 | setRemainingTime((t) => ({ ...t, minutes: t.minutes - 1, seconds: 59 }));
66 | }
67 | }
68 | }, 1000);
69 |
70 | return () => {
71 | clearInterval(timerInterval);
72 | };
73 | }, [remainingTime]);
74 |
75 | const EnterFullScreen = () => {
76 | documentElement
77 | .requestFullscreen()
78 | .then(() => console.log('entered full screen'))
79 | .catch((err) => enqueueSnackbar(err.message, { variant: 'error' }));
80 | };
81 |
82 | const ExitFullScreen = () => {
83 | document
84 | .exitFullscreen()
85 | .then(() => console.log('exited full screen'))
86 | .catch((err) => enqueueSnackbar(err.message, { variant: 'error' }));
87 | };
88 |
89 | const getExamData = async () => {
90 | try {
91 | const res = await startExam(examId);
92 | if (res) setExamData(res.data.data);
93 | else console.log('Error occured !', res);
94 | } catch (err) {
95 | enqueueSnackbar(err.message, { variant: 'error' });
96 | history.push(`/exam/${examId}`);
97 | }
98 | };
99 |
100 | const getQuestionFromId = (id) => {
101 | return questions.find((q) => q.id === id);
102 | };
103 |
104 | const getQuestionType = (q) => {
105 | if (q) return q.type;
106 | return '';
107 | };
108 |
109 | const handleQAnswer = (qId, ans) => {
110 | setAnswer((a) => ({ ...a, [qId]: { type: getQuestionType(getQuestionFromId(qId)), answer: ans } }));
111 | };
112 |
113 | const handleQStatus = (qId, status) => {
114 | setQuestionsStatus((qs) => ({ ...qs, [qId]: status }));
115 | };
116 |
117 | const handleEndExam = async () => {
118 | const body = {
119 | examId,
120 | answers: answer,
121 | finishTime: Date.now(),
122 | };
123 | const res = await submitExamAnswers(body);
124 | console.log(res);
125 | ExitFullScreen();
126 | history.push(`/exam/${examId}`);
127 | };
128 |
129 | const handleTimerCountdown = () => {
130 | if (!examData) return;
131 | const endTime = new Date(+examData.startTime + examData.duration);
132 | const dd = getUnitsFromDuration(+endTime - Date.now());
133 | setRemainingTime({ days: dd.days, hours: dd.hours, minutes: dd.minutes, seconds: dd.seconds });
134 | };
135 |
136 | const handleViewQuestions = () => {
137 | setPage((p) => p + 1);
138 | handleTimerCountdown();
139 | };
140 |
141 | return (
142 |
143 | {false && !isFullScreen ? (
144 |
145 | ) : (
146 |
147 | {page === 1 && }
148 | {page === 2 && (
149 |
158 | )}
159 |
160 | )}
161 |
162 | );
163 | };
164 |
165 | export default GiveExam;
166 |
--------------------------------------------------------------------------------
/src/pages/give-exam/page1.jsx:
--------------------------------------------------------------------------------
1 | import { Typography, Button, Paper, Grid } from '@mui/material';
2 |
3 | const Page1 = (props) => {
4 | const { handlePageNext = () => {} } = props;
5 |
6 | return (
7 | <>
8 |
9 | Lorem ipsum dolor sit amet consectetur adipisicing elit. Iure mollitia officia incidunt recusandae maxime facilis deleniti cum vel
10 | enim, quod sequi repellat porro quas molestiae quisquam dolores, voluptas in quos quo magnam et! Eaque corporis et aut quo officiis
11 | ullam eveniet, ipsa eius dolorum inventore velit beatae iusto fugit labore, excepturi similique deleniti ut quibusdam totam! Vitae
12 | quisquam numquam doloremque magni esse temporibus suscipit. Odit, odio quae consequuntur vero voluptas aperiam quaerat? Veniam natus
13 | molestias minima exercitationem, repellendus quam! Officia, nisi ad inventore a qui illum reprehenderit eligendi tempora fuga ea
14 | quasi quia quibusdam repudiandae neque dicta, voluptate dolorem ducimus?
15 |
16 |
19 | >
20 | );
21 | };
22 |
23 | export default Page1;
24 |
--------------------------------------------------------------------------------
/src/pages/give-exam/page2.jsx:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react';
2 | import { Typography, Button, Paper, Grid } from '@mui/material';
3 | import { makeStyles, useTheme } from '@mui/styles';
4 | import MCQSingle from './question-options/mcq-single';
5 | import MCQMultiple from './question-options/mcq-multiple';
6 | import FillBlanks from './question-options/fill-blanks';
7 | import DomPurify from 'dompurify';
8 | import { useDispatch } from 'react-redux';
9 | import { showConfirmation } from 'redux/slices/confirmation-dialog';
10 |
11 | const qStatus = {
12 | notAttempted: 'not_attempted',
13 | marked: 'marked',
14 | review: 'review',
15 | };
16 |
17 | const useStyles = makeStyles((theme) => ({
18 | bubble: {
19 | display: 'flex',
20 | justifyContent: 'center',
21 | alignItems: 'center',
22 | width: 50,
23 | minWidth: 0,
24 | height: 50,
25 | margin: '1rem',
26 | borderRadius: '50%',
27 | background: theme.palette.mode === 'dark' ? theme.palette.grey[600] : theme.palette.grey[400],
28 | color: theme.palette.common.white,
29 | },
30 | }));
31 |
32 | const Page2 = (props) => {
33 | const {
34 | questions = [],
35 | answerObj = {},
36 | handleQAnswer = () => {},
37 | questionsStatus = {},
38 | handleQStatus = () => {},
39 | handleEndExam = () => {},
40 | remainingTime = {},
41 | } = props;
42 |
43 | const classes = useStyles();
44 | const theme = useTheme();
45 | const dispatch = useDispatch();
46 | const [currentQuestion, setCurrentQuestion] = useState({});
47 | const paperHeight = 'calc(100vh - 20rem)';
48 | const questionHeight = 'calc(100vh - 35rem)';
49 | const QuestionStatement = DomPurify.sanitize(currentQuestion.question);
50 |
51 | useEffect(() => {
52 | if (questions.length > 0) {
53 | console.log('questions :: ', questions);
54 | setCurrentQuestion(questions[0]);
55 | }
56 | }, [questions]);
57 |
58 | // --------------------------------------------
59 | // Function for the Question Number Bubble (returns the color)
60 | // --------------------------------------------
61 | const getBubbleBackground = (qId) => {
62 | const status = questionsStatus[qId];
63 | if (!status || status === qStatus.notAttempted) return theme.palette.mode === 'dark' ? theme.palette.grey[600] : theme.palette.grey[400];
64 | if (status === qStatus.marked) return theme.palette.mode === 'dark' ? theme.palette.primary.dark : theme.palette.primary.main;
65 | if (status === qStatus.review) return theme.palette.mode === 'dark' ? theme.palette.secondary.dark : theme.palette.secondary.main;
66 | };
67 |
68 | return (
69 | <>
70 | {/* ==================== TIMER COMPONENT ==================== */}
71 |
79 |
80 | Time Remaining:{' '}
81 | {`${remainingTime.days} days ${remainingTime.hours} hours ${remainingTime.minutes} minutes ${remainingTime.seconds} seconds`}
82 |
83 |
99 |
100 |
101 |
102 |
103 |
104 |
110 | {`Marks: ${3}`}
111 | {`Negative Marks: ${1}`}
112 |
113 |
114 | {/* ==================== MAIN QUESTION BODY ==================== */}
115 |
116 |
117 |
118 | {currentQuestion.type === 'multipleOptions' && (
119 |
125 | )}
126 |
127 | {currentQuestion.type === 'mcq' && (
128 |
134 | )}
135 |
136 | {currentQuestion.type === 'fillInTheBlanks' && (
137 |
138 | )}
139 |
140 |
141 |
142 | {/* ==================== SEVERAL BUTTONS ==================== */}
143 |
144 |
151 |
152 |
159 |
160 |
167 |
168 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 | {questions.map((q, i) => (
186 |
199 | ))}
200 |
201 |
202 |
203 |
204 | >
205 | );
206 | };
207 |
208 | export default Page2;
209 |
--------------------------------------------------------------------------------
/src/pages/give-exam/question-options/fill-blanks.jsx:
--------------------------------------------------------------------------------
1 | import TextInputField from 'components/text-input-field';
2 |
3 | const FillBlanks = (props) => {
4 | const { questionId, answer, handleQAnswer } = props;
5 |
6 | return (
7 | handleQAnswer(questionId, e.target.value)}
12 | />
13 | );
14 | };
15 |
16 | export default FillBlanks;
17 |
--------------------------------------------------------------------------------
/src/pages/give-exam/question-options/mcq-multiple.jsx:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react';
2 | import FormGroup from '@mui/material/FormGroup';
3 | import FormControlLabel from '@mui/material/FormControlLabel';
4 | import Checkbox from '@mui/material/Checkbox';
5 |
6 | const MCQMultiple = (props) => {
7 | const { questionId, options, answer, handleQAnswer } = props;
8 |
9 | const handleOptionChange = (e, optionId) => {
10 | let tmpAns = !answer ? [] : [...answer];
11 | optionId = +optionId;
12 | if (tmpAns.includes(optionId)) tmpAns = tmpAns.filter((o) => o !== optionId);
13 | else tmpAns.push(optionId);
14 | handleQAnswer(questionId, tmpAns);
15 | };
16 |
17 | return (
18 |
19 | {options.map((o) => (
20 | handleOptionChange(e, o.id)}
25 | />
26 | }
27 | label={o.data}
28 | />
29 | ))}
30 |
31 | );
32 | };
33 |
34 | export default MCQMultiple;
35 |
--------------------------------------------------------------------------------
/src/pages/give-exam/question-options/mcq-single.jsx:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react';
2 | import Radio from '@mui/material/Radio';
3 | import RadioGroup from '@mui/material/RadioGroup';
4 | import FormControlLabel from '@mui/material/FormControlLabel';
5 | import FormControl from '@mui/material/FormControl';
6 |
7 | const MCQSingle = (props) => {
8 | const { questionId, options, answer, handleQAnswer } = props;
9 |
10 | const handleOptionChange = (e) => {
11 | handleQAnswer(questionId, [+e.target.value]);
12 | };
13 |
14 | return (
15 |
16 | handleOptionChange(e)}>
17 | {options.map((o) => (
18 | } label={o.data} />
19 | ))}
20 |
21 |
22 | );
23 | };
24 |
25 | export default MCQSingle;
26 |
--------------------------------------------------------------------------------
/src/pages/home/arrows.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useTheme } from '@emotion/react';
3 |
4 | import { VisibilityContext } from 'react-horizontal-scrolling-menu';
5 | import KeyboardArrowLeftIcon from '@mui/icons-material/KeyboardArrowLeft';
6 | import KeyboardArrowRightIcon from '@mui/icons-material/KeyboardArrowRight';
7 | import { Box, Card, CardContent, Button, Typography } from '@mui/material';
8 |
9 | function Arrow({ children, disabled, onClick }) {
10 | const theme = useTheme();
11 | return (
12 |
27 | {children}
28 |
29 | );
30 | }
31 |
32 | export function LeftArrow() {
33 | const {
34 | isFirstItemVisible,
35 | scrollPrev,
36 | visibleItemsWithoutSeparators,
37 | initComplete,
38 | } = React.useContext(VisibilityContext);
39 |
40 | const [disabled, setDisabled] = React.useState(
41 | !initComplete || (initComplete && isFirstItemVisible)
42 | );
43 | React.useEffect(() => {
44 | // NOTE: detect if whole component visible
45 | if (visibleItemsWithoutSeparators.length) {
46 | setDisabled(isFirstItemVisible);
47 | }
48 | }, [isFirstItemVisible, visibleItemsWithoutSeparators]);
49 |
50 | return (
51 | scrollPrev()}>
52 |
65 |
66 |
67 |
68 | );
69 | }
70 |
71 | export function RightArrow() {
72 | const { isLastItemVisible, scrollNext, visibleItemsWithoutSeparators } =
73 | React.useContext(VisibilityContext);
74 |
75 | // console.log({ isLastItemVisible });
76 | const [disabled, setDisabled] = React.useState(
77 | !visibleItemsWithoutSeparators.length && isLastItemVisible
78 | );
79 | React.useEffect(() => {
80 | if (visibleItemsWithoutSeparators.length) {
81 | setDisabled(isLastItemVisible);
82 | }
83 | }, [isLastItemVisible, visibleItemsWithoutSeparators]);
84 |
85 | return (
86 | scrollNext()}>
87 |
100 |
101 |
102 |
103 | );
104 | }
105 |
--------------------------------------------------------------------------------
/src/pages/home/exams-horizontal-scroll-section.jsx:
--------------------------------------------------------------------------------
1 | import { LeftArrow, RightArrow } from './arrows';
2 | import { useState } from 'react';
3 | import ExamDetailsCard2 from 'components/exam-details-card2';
4 | import { ScrollMenu, VisibilityContext } from 'react-horizontal-scrolling-menu';
5 | import { Box, Card, CardContent, Button, Typography } from '@mui/material';
6 |
7 | const ExamsHorizontalScrollSection = ({ allData }) => {
8 | //swipable states===============================
9 |
10 | const elemPrefix = 'test';
11 | const getId = (index) => `${elemPrefix}${index}`;
12 |
13 | const getItems = () => {
14 | return allData.map((data, id) => {
15 | data[id] = id;
16 | return data;
17 | });
18 | };
19 |
20 | const [items, setItems] = useState(getItems);
21 | const [selected, setSelected] = useState([]);
22 | const [position, setPosition] = useState(0);
23 |
24 | const isItemSelected = (id) => !!selected.find((el) => el === id);
25 |
26 | const handleClick =
27 | (id) =>
28 | ({ getItemById, scrollToItem }) => {
29 | const itemSelected = isItemSelected(id);
30 |
31 | setSelected((currentSelected) =>
32 | itemSelected
33 | ? currentSelected.filter((el) => el !== id)
34 | : currentSelected.concat(id)
35 | );
36 | };
37 | //==============================================
38 |
39 | const [showFilters, setShowFilters] = useState(false);
40 | const [selectedFilters, setSelectedFilters] = useState({
41 | startDate: null,
42 | examVisibility: null,
43 | examStatus: null,
44 | });
45 |
46 | const handleFilterChange = (e) =>
47 | setSelectedFilters((f) => ({ ...f, [e.target.name]: e.target.value }));
48 | const hideFilters = () => setShowFilters(false);
49 |
50 | return (
51 | }
63 | RightArrow={}
64 | onWheel={onWheel}
65 | >
66 | {/* array.map(() => {}) */}
67 | {allData &&
68 | allData.map((Obj, id) => (
69 |
86 | ))}
87 |
88 | );
89 | };
90 |
91 | function onWheel(apiObj, ev) {
92 | console.log(apiObj, 'sss', ev);
93 | const isThouchpad = Math.abs(ev.deltaX) !== 0 || Math.abs(ev.deltaY) < 15;
94 |
95 | if (isThouchpad) {
96 | ev.stopPropagation();
97 | return;
98 | }
99 |
100 | if (ev.deltaY < 0) {
101 | apiObj.scrollNext();
102 | } else if (ev.deltaY > 0) {
103 | apiObj.scrollPrev();
104 | }
105 | }
106 |
107 | export default ExamsHorizontalScrollSection;
108 |
--------------------------------------------------------------------------------
/src/pages/home/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react';
2 | import { useTheme } from '@emotion/react';
3 | import { Container } from '@mui/material';
4 | import { Box, Button } from '@mui/material';
5 | import useStyles from './styles';
6 | import { getExams } from 'api/exam';
7 | import Carousel from 'components/carousel';
8 | import announcementWhite from 'assets/icons/announcementWhite.png';
9 | import announcementBlack from 'assets/icons/announcementBlack.png';
10 | import { useSnackbar } from 'notistack';
11 | import ExamsHorizontalScroll from './exams-horizontal-scroll-section';
12 |
13 | const examVisibilities = [
14 | { label: 'Public', value: 'public' },
15 | { label: 'Private', value: 'private' },
16 | ];
17 |
18 | const examStatuses = ['Scheduled', 'Ongoing', 'Finished'];
19 |
20 | const Home = () => {
21 | const [fetchAllExam, setFetchAllExam] = useState([]);
22 | const theme = useTheme();
23 | const classes = useStyles();
24 | const { enqueueSnackbar } = useSnackbar();
25 |
26 | // -------------------------------------
27 | // FETCH ALL EXAMS DATA
28 | // -------------------------------------
29 | useEffect(async () => {
30 | try {
31 | const res = await getExams({});
32 | console.log('ALL EXAMS : ', res);
33 | setFetchAllExam(res.data.data);
34 | } catch (err) {
35 | enqueueSnackbar(err.message, { variant: 'error' });
36 | }
37 | }, []);
38 |
39 | return (
40 |
41 | {/* ------------- CAROUSAL PART -------------------- */}
42 |
45 |
46 | {/* ------------- SECOND PART -------------------- */}
47 |
48 | Events and Contests
49 |
50 |
51 |
52 | {/* ------------- THIRD PART -------------------- */}
53 |
54 | More Exams
55 |
56 |
57 |
58 |
59 | {/* ------------------- LAST ANNOUNCEMENT SECTION ------------------- */}
60 |
61 |
62 | Announcements
63 | {new Array(3).fill(0).map((announcement) => (
64 |
65 |
71 |

{' '}
79 |
New Exam
80 |
{new Date().toDateString()}
81 |
82 |
83 | Registrations for the 1st stages of the Indian Computing
84 | Olympiad 2022 - ZIO (Zonal Informatics Olympiad) & ZCO (Zonal
85 | Computing Olympiad) are ongoing.
86 |
87 |
88 |
89 |
90 |
91 | ))}
92 |
93 |
94 |
95 | );
96 | };
97 | export default Home;
98 |
--------------------------------------------------------------------------------
/src/pages/home/styles.js:
--------------------------------------------------------------------------------
1 | import { makeStyles } from '@mui/styles';
2 |
3 | export default makeStyles((theme) => ({
4 | imgSize: {
5 | width: '4rem',
6 | height: '4rem',
7 | marginRight: '1rem',
8 | },
9 | homePage: {
10 | backgroundColor:
11 | theme.palette.mode === 'dark'
12 | ? theme.palette.common.black
13 | : theme.palette.grey[200],
14 | },
15 | heading: {
16 | fontSize: '3.2rem',
17 | textAlign: 'center',
18 | marginBottom: '2rem',
19 | },
20 | sectionHeading: {
21 | fontSize: '2.6rem',
22 | width: '85%',
23 | border: '0px solid red',
24 | margin: '1rem auto',
25 | fontWeight: '600',
26 | // color: theme.palette.grey[600],
27 | color: theme.palette.primary.main,
28 | border: '0px solid red',
29 | },
30 | carouselSection: {
31 | padding: '4rem',
32 | backgroundColor:
33 | theme.palette.mode === 'dark'
34 | ? theme.palette.grey[900]
35 | : theme.palette.grey[100],
36 | marginBottom: '3rem',
37 | },
38 | examListSection: {
39 | paddingTop: '4rem',
40 | paddingBottom: '4rem',
41 | border: '0px solid red',
42 | padding: '4rem',
43 | backgroundColor:
44 | theme.palette.mode === 'dark'
45 | ? theme.palette.grey[900]
46 | : theme.palette.grey[100],
47 | },
48 | announcementSection: {
49 | backgroundColor:
50 | theme.palette.mode === 'dark'
51 | ? theme.palette.grey[900]
52 | : 'rgb(26, 178, 115,.09)',
53 | },
54 | announcementCard: {
55 | padding: '1rem',
56 | borderTop: `0px solid ${
57 | theme.palette.mode === 'dark'
58 | ? theme.palette.common.black
59 | : theme.palette.grey[300]
60 | }`,
61 | borderBottom: `2px solid ${
62 | theme.palette.mode === 'dark'
63 | ? theme.palette.common.black
64 | : theme.palette.grey[300]
65 | }`,
66 | margin: '1rem',
67 | },
68 | announcementContainer: {
69 | margin: '0 auto',
70 | paddingTop: '2rem',
71 | '& > div': { width: '85%', margin: '0 auto', marginTop: '3rem' },
72 | '& > :nth-child(2)': {
73 | borderTop: `0px solid ${
74 | theme.palette.mode === 'dark'
75 | ? theme.palette.common.black
76 | : theme.palette.grey[300]
77 | } !important`,
78 | },
79 | '& > :last-child': {
80 | borderBottom: `0px solid ${
81 | theme.palette.mode === 'dark'
82 | ? theme.palette.common.black
83 | : theme.palette.grey[300]
84 | } !important`,
85 | },
86 | },
87 | announcementCardDesc: {
88 | width: '87%',
89 | margin: '0 auto',
90 | },
91 | announcementCardButtonDiv: {
92 | marginTop: '2rem',
93 | marginBottom: '2.6rem',
94 | width: '87%',
95 | margin: '0 auto',
96 | },
97 | }));
98 |
--------------------------------------------------------------------------------
/src/pages/profile/User.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import { useHistory, useParams } from 'react-router-dom';
3 | import { Grid, Typography, Button, Box, TextField, Modal, Container, Paper, Tabs, Tab } from '@mui/material';
4 | import { makeStyles, useTheme } from '@mui/styles';
5 | import ProfileSection from './profile-section';
6 | import DetailSection from './details-section';
7 |
8 | const User = () => {
9 | const history = useHistory();
10 | const theme = useTheme();
11 |
12 | return (
13 | <>
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | >
25 | );
26 | };
27 |
28 | export default User;
29 |
--------------------------------------------------------------------------------
/src/pages/profile/crop-image.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useCallback } from 'react';
2 | import Modal from '@mui/material/Modal';
3 | import { Dialog, DialogContent } from 'components/dialog';
4 | import Cropper from 'react-easy-crop';
5 | import Slider from '@mui/material/Slider';
6 | import getCroppedImg from './crop-img-utils';
7 | import Button from '@mui/material/Button';
8 | import Stack from '@mui/material/Stack';
9 | import Box from '@mui/material/Box';
10 | import { styled } from '@mui/material/styles';
11 | import Paper from '@mui/material/Paper';
12 | const Item = styled(Paper)(({ theme }) => ({
13 | ...theme.typography.body2,
14 | padding: theme.spacing(1),
15 | textAlign: 'center',
16 | color: theme.palette.text.secondary,
17 | }));
18 |
19 | //import 'react-image-crop-component/style.css';
20 | const style = {
21 | position: 'absolute',
22 | top: '50%',
23 | left: '50%',
24 | transform: 'translate(-50%, -50%)',
25 | width: 1000,
26 | height: 600,
27 | bgcolor: 'background.paper',
28 | boxShadow: 24,
29 | p: 4,
30 | };
31 | export default function CropImageDialog(props) {
32 | const { open, setOpen } = props;
33 | const [croppedArea, setcroppedArea] = useState(null);
34 | const [crop, setCrop] = useState({ x: 0, y: 0 });
35 | const [zoom, setZoom] = useState(1);
36 | const [rotation, setRotation] = useState(0);
37 |
38 | const handleClose = () => {
39 | props.setfileselect(null);
40 | setOpen(false);
41 | };
42 |
43 | const onCropComplete = useCallback((croppedArea, croppedAreaPixels) => {
44 | setcroppedArea(croppedAreaPixels);
45 | }, []);
46 |
47 | const cropSize = { height: props.height, width: props.width };
48 |
49 | const reset = () => {
50 | setZoom(1);
51 | setRotation(0);
52 | setCrop({ x: 0, y: 0 });
53 | };
54 |
55 | const save = useCallback(async () => {
56 | try {
57 | const croppedImage = await getCroppedImg(props.fileselect, croppedArea, rotation);
58 | console.log('donee', croppedImage);
59 | props.setfileselect(null);
60 | props.setimageCropped(croppedImage);
61 | setOpen(false);
62 | } catch (e) {
63 | console.error(e);
64 | }
65 | }, [croppedArea, rotation]);
66 |
67 | return (
68 |
128 | );
129 | }
130 |
--------------------------------------------------------------------------------
/src/pages/profile/crop-img-utils.jsx:
--------------------------------------------------------------------------------
1 | const createImage = (url) =>
2 | new Promise((resolve, reject) => {
3 | const image = new Image();
4 | image.addEventListener('load', () => resolve(image));
5 | image.addEventListener('error', (error) => reject(error));
6 | image.setAttribute('crossOrigin', 'anonymous'); // needed to avoid cross-origin issues on CodeSandbox
7 | image.src = url;
8 | });
9 |
10 | function getRadianAngle(degreeValue) {
11 | return (degreeValue * Math.PI) / 180;
12 | }
13 |
14 | /**
15 | * This function was adapted from the one in the ReadMe of https://github.com/DominicTobias/react-image-crop
16 | * @param {File} image - Image File url
17 | * @param {Object} pixelCrop - pixelCrop Object provided by react-easy-crop
18 | * @param {number} rotation - optional rotation parameter
19 | */
20 | export default async function getCroppedImg(imageSrc, pixelCrop, rotation = 0) {
21 | const image = await createImage(imageSrc);
22 | const canvas = document.createElement('canvas');
23 | const ctx = canvas.getContext('2d');
24 |
25 | const maxSize = Math.max(image.width, image.height);
26 | const safeArea = 2 * ((maxSize / 2) * Math.sqrt(2));
27 |
28 | // set each dimensions to double largest dimension to allow for a safe area for the
29 | // image to rotate in without being clipped by canvas context
30 | canvas.width = safeArea;
31 | canvas.height = safeArea;
32 |
33 | // translate canvas context to a central location on image to allow rotating around the center.
34 | ctx.translate(safeArea / 2, safeArea / 2);
35 | ctx.rotate(getRadianAngle(rotation));
36 | ctx.translate(-safeArea / 2, -safeArea / 2);
37 |
38 | // draw rotated image and store data.
39 | ctx.drawImage(image, safeArea / 2 - image.width * 0.5, safeArea / 2 - image.height * 0.5);
40 | const data = ctx.getImageData(0, 0, safeArea, safeArea);
41 |
42 | // set canvas width to final desired crop size - this will clear existing context
43 | canvas.width = pixelCrop.width;
44 | canvas.height = pixelCrop.height;
45 |
46 | // paste generated rotate image with correct offsets for x,y crop values.
47 | ctx.putImageData(
48 | data,
49 | Math.round(0 - safeArea / 2 + image.width * 0.5 - pixelCrop.x),
50 | Math.round(0 - safeArea / 2 + image.height * 0.5 - pixelCrop.y)
51 | );
52 |
53 | // As Base64 string
54 | // return canvas.toDataURL('image/jpeg');
55 |
56 | // As a blob
57 | return new Promise((resolve) => {
58 | canvas.toBlob((file) => {
59 | resolve(URL.createObjectURL(file));
60 | }, 'image/jpeg');
61 | });
62 | }
63 |
--------------------------------------------------------------------------------
/src/pages/profile/details-section.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import { Grid, Typography, Button, Box, TextField, Modal, Container, Paper, Tabs, Tab } from '@mui/material';
3 | import ExamsList from './exams-list';
4 |
5 | const TabPanel = (props) => {
6 | const { children, value, index, ...other } = props;
7 | return (
8 |
9 | {value === index && (
10 |
11 | {children}
12 |
13 | )}
14 |
15 | );
16 | };
17 |
18 | const a11yProps = (index) => {
19 | return {
20 | id: `profile-tab-${index}`,
21 | };
22 | };
23 |
24 | // const tabs = ['Performance', 'Exams Hosted', 'Exams Given'];
25 | const tabs = ['Exams Hosted', 'Exams Given'];
26 |
27 | const Details = () => {
28 | const [currentTab, setCurrentTab] = useState(0);
29 |
30 | const handleTabChange = (event, newValue) => setCurrentTab(newValue);
31 |
32 | return (
33 | <>
34 |
35 |
36 |
37 | {tabs.map((x, i) => (
38 |
39 | ))}
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 | >
50 | );
51 | };
52 |
53 | export default Details;
54 |
--------------------------------------------------------------------------------
/src/pages/profile/exams-list.jsx:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react';
2 | import {
3 | Box,
4 | Card,
5 | CardActions,
6 | CardContent,
7 | Button,
8 | Typography,
9 | Paper,
10 | Grid,
11 | } from '@mui/material';
12 | import { useSnackbar } from 'notistack';
13 | import TextInputField from 'components/text-input-field';
14 | import DatePicker from 'components/date-time-picker/date';
15 | import TimePicker from 'components/date-time-picker/time';
16 | import MultiSelect from 'components/multi-select-dropdown';
17 | import DropdownField from 'components/dropdown-field';
18 | import FilterListIcon from '@mui/icons-material/FilterList';
19 | import {
20 | DialogActions,
21 | DialogContent,
22 | DialogTitle,
23 | Dialog,
24 | } from 'components/dialog';
25 | import ExamDetailsCard from 'components/exam-details-card';
26 | import ExamDetailsCard2 from 'components/exam-details-card2';
27 | import { getHostedExams, getGivenExams } from 'api/user';
28 |
29 | const examVisibilities = [
30 | { label: 'Public', value: 'public' },
31 | { label: 'Private', value: 'private' },
32 | ];
33 |
34 | const examStatuses = ['Scheduled', 'Ongoing', 'Finished'];
35 |
36 | const ExamsList = (props) => {
37 | const { type } = props;
38 | const { enqueueSnackbar } = useSnackbar();
39 | const [showFilters, setShowFilters] = useState(false);
40 | const [selectedFilters, setSelectedFilters] = useState({
41 | startDate: null,
42 | examVisibility: null,
43 | examStatus: null,
44 | });
45 | const [exams, setExams] = useState([]);
46 |
47 | useEffect(() => {
48 | fetchExams();
49 | }, []);
50 |
51 | const fetchExams = async () => {
52 | try {
53 | let res;
54 | if (type === 'hosted') res = await getHostedExams();
55 | if (type === 'given') res = await getGivenExams();
56 | setExams(res.data.data);
57 | } catch (err) {
58 | enqueueSnackbar(err.message, { variant: 'error' });
59 | }
60 | };
61 |
62 | const handleFilterChange = (e) =>
63 | setSelectedFilters((f) => ({ ...f, [e.target.name]: e.target.value }));
64 |
65 | const hideFilters = () => setShowFilters(false);
66 | return (
67 | <>
68 |
121 |
122 | }
126 | endIconOnClick={() => setShowFilters(true)}
127 | />
128 |
135 | {/* {exams.map((exam, i) => (
136 |
144 | ))} */}
145 | {exams.map((exam, i) => (
146 |
152 | ))}
153 |
154 |
155 | >
156 | );
157 | };
158 |
159 | export default ExamsList;
160 |
--------------------------------------------------------------------------------
/src/pages/profile/profile-section.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import { useHistory, useParams } from 'react-router-dom';
3 | import { useSelector } from 'react-redux';
4 | import { Grid, Typography, Button, Box, TextField, Modal, Container, Paper, Tabs, Tab } from '@mui/material';
5 | import { makeStyles, useTheme } from '@mui/styles';
6 | import { DialogActions, DialogContent, DialogTitle, Dialog } from 'components/dialog';
7 | import TextInputField from 'components/text-input-field';
8 | import CropImageDialog from './crop-image';
9 | import EditIcon from '@mui/icons-material/Edit';
10 | import PhoneEnabledIcon from '@mui/icons-material/PhoneEnabled';
11 | import UserIcon from 'assets/icons/user.png';
12 | import Visibility from '@mui/icons-material/Visibility';
13 | import VisibilityOff from '@mui/icons-material/VisibilityOff';
14 |
15 | const useStyles = makeStyles((theme) => ({
16 | name: {
17 | fontSize: '2.5rem',
18 | fontWeight: 600,
19 | color: theme.palette.primary.main,
20 | marginTop: '1rem',
21 | },
22 | title: {
23 | fontWeight: '700',
24 | fontSize: '1.4rem',
25 | },
26 | subTitleKey: {
27 | fontSize: '1.5rem',
28 | fontWeight: 500,
29 | color: theme.palette.primary.main,
30 | },
31 | subTitleValue: {
32 | fontSize: '1.5rem',
33 | fontWeight: 500,
34 | width: '100%',
35 | },
36 | labelSmall: {
37 | fontSize: '1.5rem',
38 | },
39 | profileImage: {
40 | borderRadius: '50%',
41 | width: 150,
42 | height: 150,
43 | },
44 | }));
45 |
46 | const types = ['image/png', 'image/jpeg', 'image/jpg'];
47 |
48 | const Profile = () => {
49 | const classes = useStyles();
50 |
51 | const { user } = useSelector((state) => state.auth);
52 |
53 | const [imageCropped, setimageCropped] = useState(null);
54 | const [fileselect, setfileselect] = useState(null);
55 | const [editDialogOpen, setEditDialogOpen] = useState(false);
56 | const [imageDialogOpen, setImageDialogOpen] = useState(false);
57 | const [showPassword, setShowPassword] = useState(false);
58 |
59 | useEffect(() => {
60 | if (fileselect) setImageDialogOpen(true);
61 | }, [fileselect]);
62 |
63 | const handleClickShowPassword = () => {
64 | setShowPassword((s) => !s);
65 | };
66 |
67 | const handleEditDialogOpen = () => setEditDialogOpen(true);
68 | const handleEditDialogClose = () => setEditDialogOpen(false);
69 |
70 | const handleChange = (e) => {
71 | let selectedFile = e.target.files[0];
72 | setfileselect(URL.createObjectURL(selectedFile));
73 | };
74 |
75 | return (
76 | <>
77 | {/* ----------------------------- EDIT PROFILE MODAL PART ---------------------------------------------- */}
78 |
142 | {/* -------------------------------------------------------------------------------------- */}
143 |
144 |
145 |
154 |
163 |
164 |
165 |
173 |
176 |
177 |
178 |
179 | {user.name}
180 |
181 |
182 | {user.email}
183 |
184 |
185 |
186 |
187 | Bio:
188 |
189 |
190 | {user.bio}
191 |
192 |
193 | Gender:
194 |
195 |
196 | {user.gender}
197 |
198 |
199 | School/College:
200 |
201 |
202 | {user.institution}
203 |
204 |
205 | Phone:
206 |
207 |
208 | {user.phoneNumber}
209 |
210 |
211 | Date of Birth:
212 |
213 |
214 | {new Date(user.dob)?.toDateString()}
215 |
216 |
217 | Address:
218 |
219 |
220 | {user.address}
221 |
222 |
223 |
224 |
225 |
228 |
229 |
230 | >
231 | );
232 | };
233 |
234 | export default Profile;
235 |
--------------------------------------------------------------------------------
/src/redux/slices/auth.js:
--------------------------------------------------------------------------------
1 | import { createSlice } from '@reduxjs/toolkit';
2 |
3 | const initialState = {
4 | user: null,
5 | };
6 |
7 | export const authSlice = createSlice({
8 | name: 'auth',
9 | initialState,
10 | reducers: {
11 | setUserAndToken: (state, action) => {
12 | // Redux Toolkit allows us to write "mutating" logic in reducers. It
13 | // doesn't actually mutate the state because it uses the Immer library,
14 | // which detects changes to a "draft state" and produces a brand new
15 | // immutable state based off those changes
16 | if (action.payload.user) state.user = action.payload.user;
17 | if (action.payload.token) localStorage.setItem('AUTH_TOKEN', action.payload.token);
18 | },
19 | clearUserAndToken: (state) => {
20 | state.user = null;
21 | localStorage.removeItem('AUTH_TOKEN');
22 | },
23 | },
24 | });
25 |
26 | // Action creators are generated for each case reducer function
27 | export const { setUserAndToken, clearUserAndToken } = authSlice.actions;
28 |
29 | export default authSlice.reducer;
30 |
--------------------------------------------------------------------------------
/src/redux/slices/confirmation-dialog.js:
--------------------------------------------------------------------------------
1 | import { createSlice } from '@reduxjs/toolkit';
2 |
3 | const initialState = {
4 | open: false,
5 | title: '',
6 | content: '',
7 | primaryBtnText: '',
8 | secondaryBtnText: '',
9 | onPrimaryBtnClick: () => {},
10 | onSecondaryBtnClick: () => {},
11 | };
12 |
13 | export const confirmationSlice = createSlice({
14 | name: 'confirmation-dialog',
15 | initialState,
16 | reducers: {
17 | showConfirmation: (state, action) => {
18 | Object.keys(initialState).forEach((key) => (state[key] = action.payload[key] || initialState[key]));
19 | state.open = true;
20 | },
21 | hideConfirmation: (state) => {
22 | state.open = false;
23 | // Object.keys(initialState).forEach((key) => (state[key] = initialState[key])); // Dialog close animation lags
24 | },
25 | },
26 | });
27 |
28 | export const { showConfirmation, hideConfirmation } = confirmationSlice.actions;
29 |
30 | export default confirmationSlice.reducer;
31 |
--------------------------------------------------------------------------------
/src/redux/slices/dialog-visibility.js:
--------------------------------------------------------------------------------
1 | import { createSlice } from '@reduxjs/toolkit';
2 |
3 | export const dialogNames = {
4 | login: 'login',
5 | register: 'register',
6 | forgotPassword: 'forgotPassword',
7 | registerReminder: 'registerReminder',
8 | };
9 |
10 | const createInitialState = (namesObj) => {
11 | const obj = {};
12 | Object.values(namesObj).map((val) => {
13 | obj[val] = false;
14 | });
15 | return obj;
16 | };
17 |
18 | export const dialogVisibilitySlice = createSlice({
19 | name: 'dialogVisibility',
20 | initialState: createInitialState(dialogNames),
21 | reducers: {
22 | enableVisibility: (state, action) => {
23 | const key = action.payload;
24 | state[key] = true;
25 | },
26 | hideVisibility: (state, action) => {
27 | const key = action.payload;
28 | state[key] = false;
29 | },
30 | },
31 | });
32 |
33 | // Action creators are generated for each case reducer function
34 | export const { enableVisibility, hideVisibility } = dialogVisibilitySlice.actions;
35 |
36 | export default dialogVisibilitySlice.reducer;
37 |
--------------------------------------------------------------------------------
/src/redux/store.js:
--------------------------------------------------------------------------------
1 | import { configureStore } from '@reduxjs/toolkit';
2 | import auth from './slices/auth';
3 | import dialogVisibility from './slices/dialog-visibility';
4 | import confirmationDialog from './slices/confirmation-dialog';
5 | export default configureStore({
6 | reducer: {
7 | auth,
8 | dialogVisibility,
9 | confirmationDialog,
10 | },
11 | middleware: (getDefaultMiddleware) =>
12 | getDefaultMiddleware({
13 | serializableCheck: false,
14 | }),
15 | });
16 |
--------------------------------------------------------------------------------
/src/utilities/functions.js:
--------------------------------------------------------------------------------
1 |
2 | export const csvToJSON = (csv) => {
3 | let lines = csv.split('\n');
4 | let result = [];
5 | let headers = lines[0].split(',');
6 | for (let i = 1; i < lines.length; i++) {
7 | if (!lines[i]) continue;
8 | let obj = {};
9 | let currentline = lines[i].split(',');
10 | for (let j = 0; j < headers.length; j++) {
11 | obj[headers[j].trim()] = currentline[j].trim();
12 | }
13 | result.push(obj);
14 | }
15 | return result;
16 | };
17 |
18 | export const getUnitsFromDuration = (duration) => {
19 | duration = duration / 1000;
20 | const days = Math.floor(duration / 86400);
21 | duration -= days * 86400;
22 | const hours = Math.floor(duration / 3600) % 24;
23 | duration -= hours * 3600;
24 | const minutes = Math.floor(duration / 60) % 60;
25 | duration -= minutes * 60;
26 | const seconds = Math.floor(duration % 60);
27 | return { days, hours, minutes, seconds };
28 | };
29 |
--------------------------------------------------------------------------------
/src/utilities/theme.js:
--------------------------------------------------------------------------------
1 |
2 |
3 | const PRIMARY_COLOR = '#1ab273';
4 | const SECONDARY_COLOR = '#ffd670';
5 |
6 | const getDesignTokens = (mode) => ({
7 | breakpoints: {
8 | values: {
9 | xs: 0,
10 | sm: 480,
11 | md: 768,
12 | lg: 1024,
13 | xl: 1400,
14 | },
15 | },
16 | typography: {
17 | htmlFontSize: 10,
18 | fontFamily: "'Inter', sans-serif",
19 | },
20 | palette: {
21 | mode,
22 | common: {
23 | white: '#ffffff',
24 | black: '#000000',
25 | },
26 | ...(mode === 'light'
27 | ? {
28 | primary: {
29 | main: PRIMARY_COLOR,
30 | contrastText: '#fff',
31 | grey: '#bab5b5',
32 | },
33 | text: {
34 | primary: '#000',
35 | secondary: '#555',
36 | },
37 | divider: PRIMARY_COLOR,
38 | background: {
39 | default: '#fff',
40 | },
41 | }
42 |
43 | :
44 |
45 | {
46 | primary: {
47 | main: SECONDARY_COLOR,
48 | contrastText: '#000',
49 | grey: '#bab5b5',
50 | },
51 | text: {
52 | primary: '#fff',
53 | secondary: '#ddd',
54 | },
55 | divider: SECONDARY_COLOR,
56 | background: {
57 | default: '#111',
58 | paper: '#111',
59 | },
60 | }),
61 | },
62 | });
63 |
64 |
65 | export default getDesignTokens;
66 |
--------------------------------------------------------------------------------