├── src
├── css
│ ├── about.css
│ ├── normalize
│ │ └── normalize.css
│ └── index.css
├── hooks
│ ├── searchHook.js
│ ├── loadingImage.js
│ └── dimensionHook.js
├── helpers
│ ├── user
│ │ └── user.js
│ ├── form
│ │ └── formHelper.js
│ └── download
│ │ └── downloadHelper.js
├── components
│ ├── search
│ │ ├── academicStructure.json
│ │ └── SearchContainer.js
│ ├── profile
│ │ └── AutoGeneratedImage.js
│ ├── navbar
│ │ ├── notification
│ │ │ ├── components
│ │ │ │ └── NotificationItem.js
│ │ │ └── ShowNotificationContainer.js
│ │ ├── NavBar.js
│ │ └── components
│ │ │ └── Menu.js
│ ├── App.js
│ ├── assigments
│ │ ├── AssigmentElement.js
│ │ ├── PreviewAssigment.js
│ │ └── AssigmentContainer.js
│ └── contribute
│ │ ├── Contribute.js
│ │ └── form
│ │ └── Form.js
├── font
│ ├── Rubik-Black.ttf
│ ├── Rubik-Bold.ttf
│ ├── Rubik-Light.ttf
│ ├── Rubik-Italic.ttf
│ ├── Rubik-Medium.ttf
│ ├── Rubik-Regular.ttf
│ ├── Rubik-SemiBold.ttf
│ ├── Rubik-BoldItalic.ttf
│ ├── Rubik-ExtraBold.ttf
│ ├── Rubik-BlackItalic.ttf
│ ├── Rubik-LightItalic.ttf
│ ├── Rubik-MediumItalic.ttf
│ ├── Rubik-ExtraBoldItalic.ttf
│ └── Rubik-SemiBoldItalic.ttf
├── config
│ └── apiConfig.js
├── assets
│ └── images
│ │ ├── pexels-fauxels-3184325.jpg
│ │ ├── ant-design_user-outlined.png
│ │ ├── pexels-andrea-piacquadio-3765114.jpg
│ │ ├── pexels-nataliya-vaitkevich-6120397.jpg
│ │ └── pexels-tima-miroshnichenko-5685821.jpg
├── reducers
│ ├── RootReducer.js
│ ├── Store.js
│ └── search
│ │ └── SearchReducer.js
├── pages
│ ├── UserPage
│ │ ├── UserSubPages
│ │ │ ├── UserNotificationPage.js
│ │ │ ├── UserStatisticsPage.js
│ │ │ └── UserConfigurationPage.js
│ │ └── UserPage.js
│ ├── ContributorsPage
│ │ └── ContributorsPage.js
│ ├── Admin
│ │ ├── SubmissionsPages
│ │ │ ├── ApprovedSubmissions
│ │ │ │ └── ApprovedSubmission.js
│ │ │ ├── RejectedSubmissions
│ │ │ │ └── NotApprovedSubmission.js
│ │ │ ├── NotApprovedSubmissions
│ │ │ │ └── NotApprovedSubmission.js
│ │ │ ├── SubmissionPage.js
│ │ │ └── components
│ │ │ │ └── SubmissionForm.js
│ │ ├── Management
│ │ │ ├── Components
│ │ │ │ └── ContainerItem.js
│ │ │ ├── ManagementPage.js
│ │ │ ├── Course
│ │ │ │ └── ManagementCoursePage.js
│ │ │ ├── Subject
│ │ │ │ └── ManagementSubjectPage.js
│ │ │ └── CourseSubject
│ │ │ │ └── ManagementCourseSubjectPage.js
│ │ └── HomeAdminPage.js
│ ├── AboutPage
│ │ ├── components
│ │ │ └── ContributeWayCard.js
│ │ └── AboutPage.js
│ ├── NotificationPage
│ │ └── NotificationPage.js
│ ├── HomePage
│ │ └── HomePage.js
│ ├── Login
│ │ └── LoginPage.js
│ └── Signup
│ │ └── SignupPage.js
├── services
│ ├── remote
│ │ ├── auth
│ │ │ └── login.js
│ │ ├── user
│ │ │ └── user.js
│ │ ├── courseSubject
│ │ │ └── courseSubject.js
│ │ ├── subject
│ │ │ └── subjectRemote.js
│ │ ├── assigments
│ │ │ └── assigmentsRemote.js
│ │ ├── notifications
│ │ │ └── notificationRemote.js
│ │ ├── submissions
│ │ │ └── submissionsRemote.js
│ │ └── course
│ │ │ └── courseRemote.js
│ ├── security
│ │ └── guards
│ │ │ ├── userGuard.js
│ │ │ └── adminGuard.js
│ ├── auth
│ │ └── authService.js
│ └── http
│ │ └── httpService.js
├── index.js
├── routes
│ ├── admin
│ │ └── MainAdminRouter.js
│ ├── private
│ │ └── MainPrivateRouter.js
│ └── public
│ │ └── MainPublicRouter.js
└── JSON
│ └── academicStructure.json
├── .gitignore
├── index.css
├── public
├── img
│ ├── IMG_20191018_142643.jpg
│ ├── radix-icons_hamburger-menu.png
│ ├── Recurso-Correccao-SDP-I-2016-01-15-A.pdf
│ ├── codicon_close.svg
│ ├── akar-icons_download.svg
│ ├── fluent_eye-show-24-filled.svg
│ ├── radix-icons_hamburger-menu.svg
│ ├── eos-icons_loading.svg
│ └── undraw_Team_re_0bfe.svg
└── index.html
├── .expo
├── settings.json
└── README.md
├── target
└── npmlist.json
├── index.html
├── LICENSE
├── package.json
└── README.md
/src/css/about.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
--------------------------------------------------------------------------------
/src/hooks/searchHook.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/helpers/user/user.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | padding: 0;
3 | margin: 0;
4 | }
5 |
--------------------------------------------------------------------------------
/src/components/search/academicStructure.json:
--------------------------------------------------------------------------------
1 | {
2 | "curso": "dksmk"
3 | }
--------------------------------------------------------------------------------
/src/font/Rubik-Black.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ProvasUcan/biblioteca-de-provas-e-materiais-site/HEAD/src/font/Rubik-Black.ttf
--------------------------------------------------------------------------------
/src/font/Rubik-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ProvasUcan/biblioteca-de-provas-e-materiais-site/HEAD/src/font/Rubik-Bold.ttf
--------------------------------------------------------------------------------
/src/font/Rubik-Light.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ProvasUcan/biblioteca-de-provas-e-materiais-site/HEAD/src/font/Rubik-Light.ttf
--------------------------------------------------------------------------------
/src/font/Rubik-Italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ProvasUcan/biblioteca-de-provas-e-materiais-site/HEAD/src/font/Rubik-Italic.ttf
--------------------------------------------------------------------------------
/src/font/Rubik-Medium.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ProvasUcan/biblioteca-de-provas-e-materiais-site/HEAD/src/font/Rubik-Medium.ttf
--------------------------------------------------------------------------------
/src/font/Rubik-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ProvasUcan/biblioteca-de-provas-e-materiais-site/HEAD/src/font/Rubik-Regular.ttf
--------------------------------------------------------------------------------
/src/font/Rubik-SemiBold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ProvasUcan/biblioteca-de-provas-e-materiais-site/HEAD/src/font/Rubik-SemiBold.ttf
--------------------------------------------------------------------------------
/src/font/Rubik-BoldItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ProvasUcan/biblioteca-de-provas-e-materiais-site/HEAD/src/font/Rubik-BoldItalic.ttf
--------------------------------------------------------------------------------
/src/font/Rubik-ExtraBold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ProvasUcan/biblioteca-de-provas-e-materiais-site/HEAD/src/font/Rubik-ExtraBold.ttf
--------------------------------------------------------------------------------
/src/font/Rubik-BlackItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ProvasUcan/biblioteca-de-provas-e-materiais-site/HEAD/src/font/Rubik-BlackItalic.ttf
--------------------------------------------------------------------------------
/src/font/Rubik-LightItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ProvasUcan/biblioteca-de-provas-e-materiais-site/HEAD/src/font/Rubik-LightItalic.ttf
--------------------------------------------------------------------------------
/src/font/Rubik-MediumItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ProvasUcan/biblioteca-de-provas-e-materiais-site/HEAD/src/font/Rubik-MediumItalic.ttf
--------------------------------------------------------------------------------
/public/img/IMG_20191018_142643.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ProvasUcan/biblioteca-de-provas-e-materiais-site/HEAD/public/img/IMG_20191018_142643.jpg
--------------------------------------------------------------------------------
/src/font/Rubik-ExtraBoldItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ProvasUcan/biblioteca-de-provas-e-materiais-site/HEAD/src/font/Rubik-ExtraBoldItalic.ttf
--------------------------------------------------------------------------------
/src/font/Rubik-SemiBoldItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ProvasUcan/biblioteca-de-provas-e-materiais-site/HEAD/src/font/Rubik-SemiBoldItalic.ttf
--------------------------------------------------------------------------------
/src/config/apiConfig.js:
--------------------------------------------------------------------------------
1 | export const apiBaseUrl = 'https://biblioteca-de-provas-ucan-backend.onrender.com'
2 | //export const apiBaseUrl = 'http://localhost:3000'
--------------------------------------------------------------------------------
/public/img/radix-icons_hamburger-menu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ProvasUcan/biblioteca-de-provas-e-materiais-site/HEAD/public/img/radix-icons_hamburger-menu.png
--------------------------------------------------------------------------------
/.expo/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "hostType": "lan",
3 | "lanType": "ip",
4 | "dev": true,
5 | "minify": false,
6 | "urlRandomness": null,
7 | "https": false
8 | }
9 |
--------------------------------------------------------------------------------
/src/assets/images/pexels-fauxels-3184325.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ProvasUcan/biblioteca-de-provas-e-materiais-site/HEAD/src/assets/images/pexels-fauxels-3184325.jpg
--------------------------------------------------------------------------------
/src/assets/images/ant-design_user-outlined.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ProvasUcan/biblioteca-de-provas-e-materiais-site/HEAD/src/assets/images/ant-design_user-outlined.png
--------------------------------------------------------------------------------
/public/img/Recurso-Correccao-SDP-I-2016-01-15-A.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ProvasUcan/biblioteca-de-provas-e-materiais-site/HEAD/public/img/Recurso-Correccao-SDP-I-2016-01-15-A.pdf
--------------------------------------------------------------------------------
/src/assets/images/pexels-andrea-piacquadio-3765114.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ProvasUcan/biblioteca-de-provas-e-materiais-site/HEAD/src/assets/images/pexels-andrea-piacquadio-3765114.jpg
--------------------------------------------------------------------------------
/src/assets/images/pexels-nataliya-vaitkevich-6120397.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ProvasUcan/biblioteca-de-provas-e-materiais-site/HEAD/src/assets/images/pexels-nataliya-vaitkevich-6120397.jpg
--------------------------------------------------------------------------------
/src/assets/images/pexels-tima-miroshnichenko-5685821.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ProvasUcan/biblioteca-de-provas-e-materiais-site/HEAD/src/assets/images/pexels-tima-miroshnichenko-5685821.jpg
--------------------------------------------------------------------------------
/src/reducers/RootReducer.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from "redux";
2 | import { SearchReducer } from "./search/SearchReducer";
3 |
4 | export const rootReducer = combineReducers({
5 | searchReducer: SearchReducer,
6 | });
7 |
--------------------------------------------------------------------------------
/src/helpers/form/formHelper.js:
--------------------------------------------------------------------------------
1 | export const generateRemoteDestFolder = (course, year, semestre, subject, documentType) => {
2 | const destFolder = `${course},${year},${semestre}, ${subject}, ${documentType}`;
3 |
4 | return destFolder;
5 | }
--------------------------------------------------------------------------------
/src/reducers/Store.js:
--------------------------------------------------------------------------------
1 |
2 | import { configureStore } from '@reduxjs/toolkit'
3 | import { SearchReducer } from "./search/SearchReducer";
4 |
5 | const store = configureStore({
6 | reducer: {
7 | search: SearchReducer
8 | }
9 | });
10 |
11 | export default store;
12 |
--------------------------------------------------------------------------------
/src/pages/UserPage/UserSubPages/UserNotificationPage.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | function UserNotificationPage() {
4 | return (
5 |
6 | Notifications
7 |
Ola
8 |
9 | )
10 | }
11 |
12 | export default UserNotificationPage
13 |
--------------------------------------------------------------------------------
/src/hooks/loadingImage.js:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react";
2 |
3 | const useLoadingImage = (imgSrc) => {
4 | const [data, setData] = useState(null);
5 | const [isPending, setIsPending] = useState(true);
6 | const [error, setError] = useState(null);
7 |
8 |
9 | return { data, isPending, error };
10 | }
11 |
12 | export default useLoadingImage;
--------------------------------------------------------------------------------
/public/img/codicon_close.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/target/npmlist.json:
--------------------------------------------------------------------------------
1 | {"version":"0.1.0","name":"dojo-blog","dependencies":{"@testing-library/jest-dom":{"version":"5.14.1"},"@testing-library/react":{"version":"11.2.7"},"@testing-library/user-event":{"version":"12.8.3"},"cors":{"version":"2.8.5"},"react-dom":{"version":"17.0.2"},"react-router-dom":{"version":"5.3.0"},"react-scripts":{"version":"4.0.3"},"react":{"version":"17.0.2"},"web-vitals":{"version":"1.1.2"}}}
--------------------------------------------------------------------------------
/src/services/remote/auth/login.js:
--------------------------------------------------------------------------------
1 | import { httpRequest } from "../../http/httpService";
2 |
3 | export const login = async (body) => {
4 | try {
5 | const res = await httpRequest(`/auth/login`, "POST", body, {"Content-Type": "application/json"});
6 | const data = await res.json();
7 |
8 | return data;
9 | } catch (err) {
10 | console.log(err);
11 | throw new Error(err);
12 | }
13 | };
14 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import "./css/index.css";
4 | import App from "./components/App";
5 | import store from "./reducers/Store";
6 | import { Provider } from "react-redux";
7 |
8 | ReactDOM.render(
9 |
10 |
11 |
12 |
13 | ,
14 | document.getElementById("root")
15 | );
16 |
--------------------------------------------------------------------------------
/src/services/security/guards/userGuard.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Route, Redirect } from 'react-router-dom'
3 |
4 | const UserGuard = ({ component: Component, auth, getUserInfo, ...rest }) => {
5 | return (
6 | {
7 | return (auth === true ? : )
8 | }
9 | }>
10 |
11 | )
12 | }
13 | export default UserGuard;
--------------------------------------------------------------------------------
/src/services/security/guards/adminGuard.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import {Route, Redirect} from 'react-router-dom'
3 |
4 | const AdminGuard = ({component: Component, auth, ...rest}) => {
5 | return (
6 | {
7 | return (
8 | auth ?
9 | :
10 |
11 | )
12 | }}>
13 |
14 | )
15 | }
16 |
17 | export default AdminGuard;
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Bibliotecas de Provas UCAN
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | Biblioteca de Provas e Materiais
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/services/auth/authService.js:
--------------------------------------------------------------------------------
1 | import { apiBaseUrl } from "../../config/apiConfig"
2 | import { getActualUserData } from "../remote/user/user"
3 | import { httpRequest } from "../http/httpService"
4 |
5 | export const isValidToken = async () => {
6 | const res = await httpRequest(`/auth`, "POST");
7 | const data = await res.json()
8 |
9 | return data.status === 200
10 | }
11 |
12 | export const isAdminUser = async () => {
13 | const res = await getActualUserData();
14 |
15 | if (res.data !== undefined) {
16 | return res.data.user.roles.includes('admin')
17 | }
18 | return false;
19 | }
--------------------------------------------------------------------------------
/public/img/akar-icons_download.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/reducers/search/SearchReducer.js:
--------------------------------------------------------------------------------
1 | const initialState = {
2 | alreadyLoadedCourseStructure: false,
3 | courseStructure: localStorage.getItem("all-course-structure"),
4 | };
5 |
6 | export const SearchReducer = (state = initialState, action) => {
7 | switch (action.type) {
8 | case "search/update":
9 | return {
10 | ...state,
11 | alreadyLoadedCourseStructure:
12 | action.payload.alreadyLoadedCourseStructure,
13 | courseStructure: action.payload.courseStructure,
14 | };
15 | case "search/get":
16 | return { ...state };
17 | default:
18 | return { ...state };
19 | }
20 | };
21 |
--------------------------------------------------------------------------------
/src/hooks/dimensionHook.js:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react';
2 |
3 | function getWindowDimensions() {
4 | const { innerWidth: width, innerHeight: height } = window;
5 | return {
6 | width,
7 | height
8 | };
9 | }
10 |
11 | export default function useWindowDimensions() {
12 | const [windowDimensions, setWindowDimensions] = useState(getWindowDimensions());
13 |
14 | useEffect(() => {
15 | function handleResize() {
16 | setWindowDimensions(getWindowDimensions());
17 | }
18 |
19 | window.addEventListener('resize', handleResize);
20 | return () => window.removeEventListener('resize', handleResize);
21 | }, []);
22 |
23 | return windowDimensions;
24 | }
--------------------------------------------------------------------------------
/src/helpers/download/downloadHelper.js:
--------------------------------------------------------------------------------
1 | export const download = (url, fileName) => {
2 |
3 | var xhr = new XMLHttpRequest();
4 | const extension = url.split('.')[url.split('.').length - 1];
5 | url = url.replace('http', 'https')
6 |
7 | xhr.open("GET", url, true);
8 | xhr.responseType = "blob";
9 | xhr.onload = function () {
10 | var urlCreator = window.URL || window.webkitURL;
11 | var imageUrl = urlCreator.createObjectURL(this.response);
12 | var tag = document.createElement('a');
13 | tag.href = imageUrl;
14 | tag.download = `${fileName}.${extension}`;
15 | document.body.appendChild(tag);
16 | tag.click();
17 | document.body.removeChild(tag);
18 | }
19 | xhr.send();
20 | }
--------------------------------------------------------------------------------
/src/routes/admin/MainAdminRouter.js:
--------------------------------------------------------------------------------
1 | import React, {useState, useEffect} from 'react'
2 | import HomeAdminPage from '../../pages/Admin/HomeAdminPage'
3 | import AdminGuard from '../../services/security/guards/adminGuard'
4 |
5 | function MainAdminRouter({adminAuth}) {
6 | const [auth, setAuth] = useState(false)
7 |
8 | const handleAdminAuth = React.useCallback(async () => {
9 | const response = await adminAuth()
10 |
11 | setAuth(response)
12 | }, [adminAuth])
13 |
14 | useEffect(() => {
15 | handleAdminAuth()
16 | }, [handleAdminAuth])
17 |
18 | return (
19 | <>
20 |
21 | >
22 | )
23 | }
24 |
25 | export default MainAdminRouter
26 |
--------------------------------------------------------------------------------
/src/components/profile/AutoGeneratedImage.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | function AutoGeneratedImage({username}) {
4 | const generateName = () => {
5 | if (username === undefined) {
6 | return ''
7 | }
8 |
9 | if (username.includes(' ')) {
10 | username = username.split(' ')
11 |
12 | return username[0].slice(0, 1).toUpperCase() + username[username.length - 1].slice(0, 1).toUpperCase()
13 | }
14 | return username.slice(0,2).toUpperCase();
15 | }
16 |
17 | return (
18 |
19 |
20 | {
21 | generateName()
22 | }
23 |
24 |
25 | )
26 | }
27 |
28 | export default AutoGeneratedImage
29 |
--------------------------------------------------------------------------------
/src/pages/UserPage/UserSubPages/UserStatisticsPage.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react'
2 | import { getActualUserData } from '../../../services/remote/user/user';
3 |
4 | function UserStatisticsPage() {
5 | const [quantUploads, setQuantUploads] = useState(0)
6 |
7 | const handleGetUserInfo = async () => {
8 | const res = await getActualUserData();
9 |
10 | if (res.data !== undefined) {
11 | const data = res.data.user;
12 |
13 | setQuantUploads(data.quantOfFilesUploaded)
14 | }
15 | }
16 |
17 | useEffect(() => {
18 | handleGetUserInfo()
19 | }, [])
20 |
21 | return (
22 |
23 |
Já fez o upload de {quantUploads}
24 |
25 | )
26 | }
27 |
28 | export default UserStatisticsPage
29 |
--------------------------------------------------------------------------------
/.expo/README.md:
--------------------------------------------------------------------------------
1 | > Why do I have a folder named ".expo" in my project?
2 |
3 | The ".expo" folder is created when an Expo project is started using "expo start" command.
4 |
5 | > What does the "packager-info.json" file contain?
6 |
7 | The "packager-info.json" file contains port numbers and process PIDs that are used to serve the application to the mobile device/simulator.
8 |
9 | > What does the "settings.json" file contain?
10 |
11 | The "settings.json" file contains the server configuration that is used to serve the application manifest.
12 |
13 | > Should I commit the ".expo" folder?
14 |
15 | No, you should not share the ".expo" folder. It does not contain any information that is relevant for other developers working on the project, it is specific to your machine.
16 |
17 | Upon project creation, the ".expo" folder is already added to your ".gitignore" file.
18 |
--------------------------------------------------------------------------------
/src/components/navbar/notification/components/NotificationItem.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Link } from 'react-router-dom'
3 |
4 | function NotificationItem({
5 | id,
6 | notificationType,
7 | referenceId,
8 | createdAt,
9 | isReaded,
10 | handleShowNotificationContainer
11 | }) {
12 | return (
13 | {
17 | handleShowNotificationContainer(false)
18 | }}
19 | >
20 |
21 | {
22 | notificationType.includes('APPROVED') ? 'Submissão aceite!' : 'Submissão recusada!'
23 | }
24 |
25 |
26 | {createdAt}
27 |
28 | )
29 | }
30 |
31 | export default NotificationItem
32 |
--------------------------------------------------------------------------------
/src/services/remote/user/user.js:
--------------------------------------------------------------------------------
1 | import { apiBaseUrl } from "../../../config/apiConfig";
2 | import { httpRequest } from "../../http/httpService";
3 |
4 | export const getActualUserData = async () => {
5 | const res = await httpRequest(`/user`, "GET");
6 | const data = await res.json();
7 |
8 | return data;
9 | };
10 |
11 | export const getUserById = async (userId) => {
12 | const res = await httpRequest(`/user/${userId}`, "GET");
13 | const data = await res.json();
14 |
15 | return data.data;
16 | };
17 |
18 | export const getContributors = async (userId) => {
19 | const res = await httpRequest(`/user/contributors`, "GET");
20 | const data = await res.json();
21 |
22 | return data.data;
23 | };
24 |
25 | export const updateUser = async (userId, body) => {
26 | const res = await httpRequest(
27 | `${apiBaseUrl}/user/${userId}`,
28 | "PUT",
29 | JSON.stringify(body)
30 | );
31 | const data = await res.json();
32 |
33 | return data;
34 | };
35 |
--------------------------------------------------------------------------------
/src/services/remote/courseSubject/courseSubject.js:
--------------------------------------------------------------------------------
1 | import { apiBaseUrl } from "../../../config/apiConfig";
2 | import { httpRequest } from "../../http/httpService";
3 |
4 | export const createCourseSubject = async (body) => {
5 | const res = await httpRequest(`/association`, "POST", JSON.stringify(body));
6 | const data = await res.json();
7 |
8 | return data;
9 | };
10 |
11 | export const getCoursesSubjects = async () => {
12 | const res = await httpRequest(`/association`, "GET");
13 |
14 | const data = await res.json();
15 |
16 | return data.courses;
17 | };
18 |
19 | export const deleteCourseSubject = async (courseId) => {
20 | const res = await httpRequest(`/association/${courseId}`, "DELETE");
21 | const data = await res.json();
22 |
23 | return data.data;
24 | };
25 |
26 | export const updateCourseSubject = async (courseId, body) => {
27 | const res = await httpRequest(`/association/${courseId}`, "PUT", JSON.stringify(body));
28 | const data = await res.json();
29 |
30 | return data.data;
31 | };
32 |
--------------------------------------------------------------------------------
/src/components/App.js:
--------------------------------------------------------------------------------
1 | import NavBar from "./navbar/NavBar";
2 | import { BrowserRouter as Router, Switch } from "react-router-dom";
3 | import React from "react";
4 |
5 | import MainPublicRouter from "../routes/public/MainPublicRouter";
6 | import MainPrivateRouter from "../routes/private/MainPrivateRouter";
7 | import MainAdminRouter from "../routes/admin/MainAdminRouter";
8 | import { isAdminUser } from "../services/auth/authService";
9 |
10 | const App = () => {
11 | return (
12 |
13 |
14 | {
15 | <>
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | >
28 | }
29 |
30 |
31 | );
32 | };
33 |
34 | export default App;
35 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Creuma Kuzola
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/routes/private/MainPrivateRouter.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react'
2 |
3 | import UserGuard from "../../services/security/guards/userGuard"
4 | import { getActualUserData } from "../../services/remote/user/user";
5 | import { UserPage } from "../../pages/UserPage/UserPage";
6 | import { isValidToken } from "../../services/auth/authService";
7 | import NotificationPage from '../../pages/NotificationPage/NotificationPage';
8 |
9 |
10 | function MainPrivateRouter() {
11 | const [isAuthenticated, setIsAuthenticated] = useState(false);
12 |
13 | const verifyTokenValidation = async () => {
14 | const res = await isValidToken()
15 | setIsAuthenticated(res)
16 | }
17 |
18 | useEffect(() => {
19 | verifyTokenValidation()
20 | }, [])
21 |
22 | return (
23 | <>
24 |
25 |
26 |
27 |
28 | >
29 | )
30 | }
31 |
32 | export default MainPrivateRouter
33 |
--------------------------------------------------------------------------------
/public/img/fluent_eye-show-24-filled.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/services/http/httpService.js:
--------------------------------------------------------------------------------
1 | import { apiBaseUrl } from "../../config/apiConfig";
2 |
3 | const REQUEST_METHODS = ["PUT", "POST"];
4 |
5 | export const httpRequest = async (
6 | endpoint = apiBaseUrl,
7 | method = "GET",
8 | body = JSON.stringify({}),
9 | headers = {}
10 | ) => {
11 | headers = {
12 | Authorization: `Bearer ${
13 | localStorage.getItem("auth-token-biblioteca-de-provas")
14 | ? localStorage.getItem("auth-token-biblioteca-de-provas")
15 | : ""
16 | }`,
17 | "Access-Control-Allow-Origin":
18 | headers["Access-Control-Allow-Origin"] === undefined
19 | ? "*"
20 | : headers["Access-Control-Allow-Origin"],
21 | ...headers,
22 | };
23 |
24 | if (REQUEST_METHODS.indexOf(method) !== -1) {
25 | console.log({
26 | url: `${apiBaseUrl}${endpoint}`,
27 | method,
28 | body,
29 | headers,
30 | });
31 | const response = await fetch(`${apiBaseUrl}${endpoint}`, {
32 | method,
33 | body,
34 | headers,
35 | });
36 |
37 | return response;
38 | } else {
39 | const response = await fetch(`${apiBaseUrl}${endpoint}`, {
40 | method,
41 | headers,
42 | });
43 |
44 | return response;
45 | }
46 | };
47 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "homepage": "https://provasucan.vercel.app/",
3 | "name": "provasucan",
4 | "version": "0.1.1",
5 | "private": true,
6 | "dependencies": {
7 | "@reduxjs/toolkit": "^1.8.5",
8 | "@testing-library/jest-dom": "^5.11.4",
9 | "@testing-library/react": "^11.1.0",
10 | "@testing-library/user-event": "^12.1.10",
11 | "cors": "^2.8.5",
12 | "react": "^17.0.2",
13 | "react-dom": "^17.0.2",
14 | "react-redux": "^8.0.4",
15 | "react-router-dom": "^5.3.0",
16 | "react-scripts": "4.0.3",
17 | "redux": "^4.2.0",
18 | "web-vitals": "^1.0.1"
19 | },
20 | "scripts": {
21 | "start": "react-scripts start",
22 | "build": "react-scripts build",
23 | "test": "react-scripts test",
24 | "eject": "react-scripts eject"
25 | },
26 | "eslintConfig": {
27 | "extends": [
28 | "react-app",
29 | "react-app/jest"
30 | ]
31 | },
32 | "browserslist": {
33 | "production": [
34 | ">0.2%",
35 | "not dead",
36 | "not op_mini all"
37 | ],
38 | "development": [
39 | "last 1 chrome version",
40 | "last 1 firefox version",
41 | "last 1 safari version"
42 | ]
43 | },
44 | "devDependencies": {
45 | "gh-pages": "^3.2.3"
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/services/remote/subject/subjectRemote.js:
--------------------------------------------------------------------------------
1 | import { apiBaseUrl } from "../../../config/apiConfig";
2 | import { httpRequest } from "../../http/httpService";
3 |
4 | export const getAllSubjectsStructure = async function () {
5 | let auxAllSubjectsStructure = await httpRequest("/subject/all", "GET");
6 | auxAllSubjectsStructure = await auxAllSubjectsStructure.json();
7 |
8 | return auxAllSubjectsStructure.subjects;
9 | };
10 |
11 | export const createSubject = async (body) => {
12 | const res = await httpRequest("/subject", "POST", JSON.stringify(body));
13 | const data = await res.json();
14 |
15 | return data;
16 | };
17 |
18 | export const getSubjects = async () => {
19 | const res = await httpRequest("/subject", "GET");
20 | const data = await res.json();
21 |
22 | return data.subjects;
23 | };
24 |
25 | export const deleteSubject = async (subjectId) => {
26 | const res = await httpRequest(`/subject/${subjectId}`, "DELETE");
27 | const data = await res.json();
28 |
29 | return data.data;
30 | };
31 |
32 | export const updateSubject = async (subjectId, body) => {
33 | const res = await httpRequest(
34 | `/subject/${subjectId}`,
35 | "PUT",
36 | JSON.stringify(body)
37 | );
38 | const data = await res.json();
39 |
40 | return data.data;
41 | };
42 |
--------------------------------------------------------------------------------
/src/services/remote/assigments/assigmentsRemote.js:
--------------------------------------------------------------------------------
1 | import { apiBaseUrl } from "../../../config/apiConfig";
2 | import { httpRequest } from "../../http/httpService";
3 |
4 | export const getAssigments = async (course, subject, documentType) => {
5 | const res = await httpRequest(
6 | `/course/${course}/${subject}/${documentType}`,
7 | "GET"
8 | );
9 | const data = await res.json();
10 |
11 | return data.result.filesUrls;
12 | };
13 |
14 | export const uploadAssigments = async (
15 | submissionArea,
16 | files,
17 | userIdentification = "anonymous"
18 | ) => {
19 | var form = new FormData();
20 |
21 | form.append("course", submissionArea.course);
22 | form.append("year", submissionArea.year);
23 | form.append("semestre", submissionArea.semestre);
24 | form.append("subject", submissionArea.subject);
25 | form.append("documentType", submissionArea.documentType);
26 |
27 | form.append("userEmail", userIdentification);
28 |
29 | for (
30 | let actualFileIndex = 0;
31 | actualFileIndex < files.length;
32 | actualFileIndex++
33 | ) {
34 | form.append("files", files[actualFileIndex]);
35 | }
36 |
37 | try {
38 | await httpRequest(`/submission/upload`, "POST", form);
39 |
40 | return true;
41 | } catch (error) {
42 | return false;
43 | }
44 | };
45 |
--------------------------------------------------------------------------------
/public/img/radix-icons_hamburger-menu.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/components/assigments/AssigmentElement.js:
--------------------------------------------------------------------------------
1 | import { useContext } from "react";
2 | import { SearchContext } from "../../pages/HomePage/HomePage";
3 |
4 | const AssigmentElement = ({ assigment, id, previewAssigment, downloadAssigment }) => {
5 | const searchContext = useContext(SearchContext)
6 |
7 | return (
8 | {
9 | previewAssigment(id)
10 | }}>
11 |
12 |
13 |
14 |
15 |
16 | {
17 | e.stopPropagation();
18 | previewAssigment(id)
19 | }} />
20 |
21 |
22 | {
23 | e.stopPropagation();
24 | downloadAssigment(assigment, searchContext.name)
25 | }} className="hover-container-menu--item">
26 |
27 |
28 |
29 |
30 |
31 | );
32 | }
33 |
34 | export default AssigmentElement;
--------------------------------------------------------------------------------
/public/img/eos-icons_loading.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/pages/ContributorsPage/ContributorsPage.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import AutoGeneratedImage from "../../components/profile/AutoGeneratedImage";
3 | import { getContributors } from "../../services/remote/user/user";
4 |
5 | function ContributorsPage() {
6 | const [contributors, setContributors] = useState([]);
7 |
8 | const handleGetContributors = async () => {
9 | const res = await getContributors();
10 |
11 | if (res.users !== undefined) {
12 | setContributors(res.users);
13 | }
14 | };
15 |
16 | useEffect(() => {
17 | handleGetContributors();
18 | }, []);
19 |
20 | return (
21 | <>
22 |
23 | Um grande obrigado para todos os nossos contribudores que ajudam a
24 | ajudar o próximo! ᇐ
25 |
26 |
27 | {contributors.map((contributor) => (
28 |
29 |
36 |
{contributor.username}
37 |
38 | ))}
39 |
40 | >
41 | );
42 | }
43 |
44 | export default ContributorsPage;
45 |
--------------------------------------------------------------------------------
/src/routes/public/MainPublicRouter.js:
--------------------------------------------------------------------------------
1 | // @ts-nocheck
2 | import React from 'react'
3 | import { Route } from "react-router-dom";
4 | import AboutPage from "../../pages/AboutPage/AboutPage";
5 | import LoginPage from "../../pages/Login/LoginPage";
6 | import SignupPage from '../../pages/Signup/SignupPage';
7 |
8 | import HomePage from "../../pages/HomePage/HomePage";
9 | import ContributorsPage from "../../pages/ContributorsPage/ContributorsPage";
10 | import { getAllCoursesStructure } from "../../services/remote/course/courseRemote";
11 | import { getAssigments } from "../../services/remote/assigments/assigmentsRemote";
12 | import Contribute from "../../components/contribute/Contribute";
13 |
14 |
15 | function MainRouter() {
16 | return (
17 | <>
18 |
19 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | >
34 | )
35 | }
36 |
37 | export default MainRouter
38 |
--------------------------------------------------------------------------------
/src/JSON/academicStructure.json:
--------------------------------------------------------------------------------
1 | {
2 | "Engenharia Informática": {
3 | "1º Ano": {
4 | "1º Semestre": {
5 | "Analise Matemática": [
6 | "Frequências",
7 | "Exames",
8 | "Recursos",
9 | "Exercícios"
10 | ],
11 | "Fundamentos de Programação 1": [
12 | "Frequências",
13 | "Exames",
14 | "Recursos",
15 | "Exercícios"
16 | ],
17 | "Fisica Moderna": [
18 | "Frequências",
19 | "Exames",
20 | "Recursos",
21 | "Exercícios"
22 | ],
23 | "Algebra": [
24 | "Frequências",
25 | "Exames",
26 | "Recursos",
27 | "Exercícios"
28 | ]
29 | }
30 | },
31 | "2º Ano": {
32 | "1º Semestre": {
33 | "Analise Matemática": [
34 | "Frequências",
35 | "Exames",
36 | "Recursos",
37 | "Exercícios"
38 | ],
39 | "Fundamentos de Programação 1": [
40 | "Frequências",
41 | "Exames",
42 | "Recursos",
43 | "Exercícios"
44 | ],
45 | "Fisica Moderna": [
46 | "Frequências",
47 | "Exames",
48 | "Recursos",
49 | "Exercícios"
50 | ],
51 | "Algebra": [
52 | "Frequências",
53 | "Exames",
54 | "Recursos",
55 | "Exercícios"
56 | ]
57 | }
58 | }
59 | }
60 | }
--------------------------------------------------------------------------------
/src/services/remote/notifications/notificationRemote.js:
--------------------------------------------------------------------------------
1 | import { apiBaseUrl } from "../../../config/apiConfig";
2 | import { httpRequest } from "../../http/httpService";
3 |
4 | export const createNotification = async (body) => {
5 | const res = await httpRequest(
6 | `/submission/notification`,
7 | "POST",
8 | JSON.stringify(body)
9 | );
10 | const data = await res.json();
11 |
12 | return data;
13 | };
14 |
15 | export const getQuantNotifications = async () => {
16 | const res = await httpRequest(`/submission/notification/count`, "GET");
17 |
18 | const data = await res.json();
19 |
20 | return data;
21 | };
22 |
23 | export const getNotifications = async () => {
24 | const res = await httpRequest(`/submission/notification/all`, "GET");
25 | const data = await res.json();
26 |
27 | return data;
28 | };
29 |
30 | export const getSpecificNotification = async (id) => {
31 | const res = await httpRequest(`/submission/notification/${id}`, "GET");
32 | const data = await res.json();
33 |
34 | return data;
35 | };
36 |
37 | export const deleteNotification = async (id) => {
38 | const res = await httpRequest(`/submission/notification/${id}`, "DELETE");
39 | const data = await res.json();
40 |
41 | return data.data;
42 | };
43 |
44 | export const updateNotification = async (id, body) => {
45 | const res = await httpRequest(`/submission/notification/${id}`, "PUT", JSON.stringify(body));
46 | const data = await res.json();
47 |
48 | return data.data;
49 | };
50 |
--------------------------------------------------------------------------------
/src/pages/Admin/SubmissionsPages/ApprovedSubmissions/ApprovedSubmission.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react'
2 | import { getApprovedSubmissions } from '../../../../services/remote/submissions/submissionsRemote'
3 | import SubmissionForm from '../components/SubmissionForm'
4 |
5 | function ApprovedSubmission() {
6 | const [submissions, setSubmissions] = useState([])
7 |
8 | const handleGetApprovedSubmissions = async () => {
9 | const response = await getApprovedSubmissions()
10 |
11 | if (response !== undefined) {
12 | setSubmissions(response)
13 | }
14 | }
15 |
16 | useEffect(() => {
17 | handleGetApprovedSubmissions()
18 | }, [])
19 | return (
20 |
21 | {
22 | submissions.map((submission, id) => (
23 |
37 | ))
38 | }
39 |
40 | )
41 | }
42 |
43 | export default ApprovedSubmission
44 |
--------------------------------------------------------------------------------
/src/pages/AboutPage/components/ContributeWayCard.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | function ContributeWayCard({ title, instructions }) {
4 | return (
5 |
6 |
{title}
7 |
8 |
9 | {
10 | instructions.map(instruction => (
11 |
{instruction}
12 | ))
13 | }
14 |
15 |
16 | )
17 | }
18 |
19 | export default ContributeWayCard
20 |
21 | const styles = {
22 | contributeWayCard: {
23 | display: 'flex',
24 | flexDirection: 'column',
25 | width: '30%',
26 | boxShadow: '0 0rem 1rem #0003',
27 | borderRadius: '1rem',
28 | padding: '4rem 2rem',
29 | borderTop: '1rem solid #00B2FF',
30 | margin: '0 5rem 0 0'
31 | },
32 | contributeWayCardImage: {
33 | width: '24.2rem',
34 | height: '24.2rem',
35 | borderRadius: '1rem',
36 | objectFit: 'cover',
37 | marginBottom: '1.5rem',
38 | border: '1rem solid #00B2FF',
39 | display: 'none'
40 | },
41 | contributeWayTitle: {
42 | fontSize: '2.4rem',
43 | color: '#333',
44 | marginBottom: '2rem'
45 | },
46 | contributeWayDescContainer: {
47 | marginTop: '1rem',
48 | padding: '1rem',
49 | backgroundColor: '#0002',
50 | borderRadius: '1rem'
51 | },
52 | contributeWayDesc: {
53 | fontSize: '1.8rem',
54 | marginBottom: '1rem'
55 | },
56 |
57 | }
--------------------------------------------------------------------------------
/src/pages/Admin/SubmissionsPages/RejectedSubmissions/NotApprovedSubmission.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react'
2 | import { getRejectedSubmissions } from '../../../../services/remote/submissions/submissionsRemote'
3 | import SubmissionForm from '../components/SubmissionForm'
4 |
5 | function RejectedSubmission() {
6 | const [submissions, setSubmissions] = useState([])
7 |
8 | const handleGetRejectedSubmissions = async () => {
9 | const response = await getRejectedSubmissions()
10 |
11 | if (response !== undefined) {
12 | setSubmissions(response)
13 | }
14 | }
15 |
16 | useEffect(() => {
17 | handleGetRejectedSubmissions()
18 | }, [])
19 |
20 | return (
21 |
22 | {
23 | submissions.map((submission, id) => (
24 |
38 | ))
39 | }
40 |
41 | )
42 | }
43 |
44 | export default RejectedSubmission
45 |
--------------------------------------------------------------------------------
/src/components/navbar/NavBar.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import useWindowDimensions from "../../hooks/dimensionHook";
3 | import Menu from "./components/Menu";
4 |
5 | const NavBar = () => {
6 | const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
7 | const [workWithMobileMenu, setWorkWithMobileMenu] = useState(false);
8 | const { width } = useWindowDimensions();
9 |
10 | useEffect(() => {
11 | if (width <= 1024) {
12 | setWorkWithMobileMenu(true);
13 | }
14 | }, [width]);
15 | return (
16 |
17 |
18 |
19 | Bibliotecas de Provas UCAN
20 |
21 | Nos ajude a ajudar!
22 |
23 |
24 | {workWithMobileMenu && (
25 |
{
31 | setIsMobileMenuOpen(!isMobileMenuOpen);
32 | }}
33 | >
34 | {!isMobileMenuOpen ? (
35 |
36 | ) : (
37 |
38 | )}
39 |
40 | )}
41 |
42 | {workWithMobileMenu ? isMobileMenuOpen ?
: <>> :
}
43 |
44 | );
45 | };
46 |
47 | export default NavBar;
48 |
--------------------------------------------------------------------------------
/src/pages/Admin/SubmissionsPages/NotApprovedSubmissions/NotApprovedSubmission.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react'
2 | import { getNotApprovedSubmissions } from '../../../../services/remote/submissions/submissionsRemote'
3 | import SubmissionForm from '../components/SubmissionForm'
4 |
5 | function NotApprovedSubmission() {
6 | const [submissions, setSubmissions] = useState([])
7 |
8 | const handleGetNotApprovedSubmissions = async () => {
9 | const response = await getNotApprovedSubmissions()
10 |
11 | if (response !== undefined) {
12 | setSubmissions(response)
13 | }
14 | }
15 |
16 | useEffect(() => {
17 | handleGetNotApprovedSubmissions()
18 | }, [])
19 |
20 | return (
21 |
22 | {
23 | submissions.map((submission, id) => (
24 |
38 | ))
39 | }
40 |
41 | )
42 | }
43 |
44 | export default NotApprovedSubmission
45 |
--------------------------------------------------------------------------------
/src/pages/Admin/Management/Components/ContainerItem.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react'
2 |
3 | function ContainerItem({ id, name, handleUpdate, handleDelete }) {
4 | const [submitValue, setSubmitValue] = useState(name)
5 | const [textInputDisable, setTextInputDisable] = useState(true)
6 |
7 | return (
8 |
9 | {
10 | !textInputDisable &&
11 |
Clique ENTER ao final da edição
12 | }
13 |
14 | {
17 | if (e.keyCode === 13) {
18 | handleUpdate(id, submitValue)
19 | setTextInputDisable(true)
20 | }
21 | }}
22 | value={submitValue}
23 | disabled={textInputDisable}
24 | onChange={(e) => {
25 | setSubmitValue(e.target.value)
26 | }}
27 | />
28 | {
29 | if (!textInputDisable) {
30 | setSubmitValue(name)
31 | }
32 | setTextInputDisable(!textInputDisable)
33 | }}>{textInputDisable ? 'Editar' : 'Cancelar'}
34 |
35 |
{
36 | handleDelete(id)
37 | }}>Deletar
38 |
39 | )
40 | }
41 |
42 | export default ContainerItem
43 |
--------------------------------------------------------------------------------
/src/components/navbar/notification/ShowNotificationContainer.js:
--------------------------------------------------------------------------------
1 |
2 |
3 | import React, { useState, useEffect } from 'react'
4 | import { getNotifications } from '../../../services/remote/notifications/notificationRemote'
5 | import NotificationItem from './components/NotificationItem'
6 |
7 | function ShowNotificationContainer({handleShowNotificationContainer}) {
8 | const [notifications, setNotifications] = useState([])
9 |
10 | const handleGetNotifications = async () => {
11 | try {
12 | const response = await getNotifications()
13 |
14 | if (response.status === 200) {
15 | setNotifications(response.notifications)
16 | }
17 | } catch (err) {
18 | console.log(err)
19 | }
20 | }
21 |
22 | useEffect(() => {
23 | handleGetNotifications()
24 | }, [])
25 |
26 | return (
27 |
28 | {
29 | handleShowNotificationContainer(false)
30 | }}>Back
31 | {
32 | notifications.map(notification => (
33 |
42 | ))
43 | }
44 |
45 | )
46 | }
47 |
48 | export default ShowNotificationContainer
49 |
--------------------------------------------------------------------------------
/src/pages/Admin/HomeAdminPage.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Link, Switch } from 'react-router-dom'
3 | import AdminGuard from '../../services/security/guards/adminGuard'
4 | import ManagementPage from './Management/ManagementPage'
5 | import SubmissionPage from './SubmissionsPages/SubmissionPage'
6 |
7 | function HomeAdminPage({ auth }) {
8 | return (
9 |
10 |
11 | Bem-vindo sr(a).
Admin
12 |
13 |
14 |
15 |
16 |
17 | Submissões
18 | Cursos & Disciplinas
19 | Configurações
20 |
21 |
22 |
23 |
24 |
25 |
29 |
30 |
34 |
35 |
36 |
37 |
38 |
39 | )
40 | }
41 |
42 | export default HomeAdminPage
43 |
--------------------------------------------------------------------------------
/src/components/assigments/PreviewAssigment.js:
--------------------------------------------------------------------------------
1 | import React, {useContext} from 'react'
2 | import ReactDOM from 'react-dom'
3 | import { download } from '../../helpers/download/downloadHelper'
4 | import { SearchContext } from '../../pages/HomePage/HomePage'
5 |
6 | function PreviewAssigment({ assigment, closePreviewAssigment, nextAssigment, prevAssigment, quantAssigments, actualAssigment }) {
7 | const searchContext = useContext(SearchContext)
8 |
9 | return ReactDOM.createPortal(
10 |
11 |
12 |
13 | {
14 | e.stopPropagation()
15 | download(assigment, searchContext.name)
16 | }}>Download
17 |
18 | Close
19 |
20 |
21 |
22 |
23 | <
24 |
25 |
26 |
27 |
28 |
29 | >
30 |
31 |
32 |
33 |
{actualAssigment} de {quantAssigments}
34 |
35 |
36 | ,
37 | document.getElementById('preview-root')
38 | )
39 | }
40 |
41 | export default PreviewAssigment
--------------------------------------------------------------------------------
/src/pages/Admin/Management/ManagementPage.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Link, Switch } from 'react-router-dom'
3 | import AdminGuard from '../../../services/security/guards/adminGuard'
4 | import ManagementCoursePage from './Course/ManagementCoursePage'
5 | import ManagementCourseSubjectPage from './CourseSubject/ManagementCourseSubjectPage'
6 | import ManagementSubjectPage from './Subject/ManagementSubjectPage'
7 |
8 |
9 | function ManagementPage({ auth }) {
10 | return (
11 |
12 |
13 |
14 | Cursos
15 | Disciplinas
16 | Cursos & Disciplinas
17 |
18 |
19 |
20 |
21 |
22 |
27 |
28 |
33 |
34 |
39 |
40 |
41 |
42 | )
43 | }
44 |
45 | export default ManagementPage
46 |
--------------------------------------------------------------------------------
/src/pages/Admin/SubmissionsPages/SubmissionPage.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Link, Switch } from 'react-router-dom'
3 | import AdminGuard from '../../../services/security/guards/adminGuard'
4 | import ApprovedSubmission from './ApprovedSubmissions/ApprovedSubmission'
5 | import NotApprovedSubmission from './NotApprovedSubmissions/NotApprovedSubmission'
6 | import RejectedSubmission from './RejectedSubmissions/NotApprovedSubmission'
7 |
8 |
9 | function SubmissionPage({ auth }) {
10 | return (
11 |
12 |
13 |
14 |
15 |
16 | Aprovados
17 | Não Aprovados
18 | Rejeitados
19 |
20 |
21 |
22 |
23 |
24 |
29 |
30 |
35 |
36 |
41 |
42 |
43 |
44 |
45 | )
46 | }
47 |
48 | export default SubmissionPage
49 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Situação
2 |
3 | A situação é a seguinte:
4 | Temos como objectivo o lançamento do projecto até o ínicio das aulas, para que esteja disponivel para nós estudantes que seria de mais valia para todos.
5 |
6 | Alguns detalhes técnicos foram abordados no caso, desde a estrutura de armazenamento de arquivos(Provas e Projectos) como também a questão de desenvolvimento da parte do front e back.
7 |
8 | ## Stack
9 |
10 | **Front-end**:
11 | - React.js (Actualmente saindo do Vue.js para o React.js)
12 | - HTML
13 | - CSS
14 | - JS
15 |
16 | **Back-end**:
17 | - Node.js
18 | - MongoDB
19 | - Express
20 |
21 |
22 | **Design:** [Design](https://www.figma.com/file/Sh0UMVOC7a3EuCVByCoMyJ/Biblioteca-de-provas?node-id=0%3A1)
23 |
24 |
25 | # Biblioteca-de-provas-e-materiais-site
26 |
27 | Para melhorar a experiência do estudante, decidimos criar um site para o repositório Biblioteca-de-provas-e-materiais com todas as provas.
28 | Desse jeito será mais fácil e simples para você obter o material!
29 |
30 | ## Seja um dos contribuidores
31 |
32 | Este é um projeto totalmente livre que aceita contribuições via pull requests no GitHub. Este documento tem a responsabilidade de alinhar as contribuições de acordo com os padrões estabelecidos no mesmo. Em caso de dúvidas, abra uma issue.
33 |
34 | O repositório apenas cumprirá o seu propósito com a participação de todos. Contribua!
35 |
36 | #### Passos para se tornar um contribuidor
37 |
38 | 1 - Faça Fork deste repositório
39 | 2 - Solicite o pull request
40 | 3 - Insira um pequeno sobre , sobre o que você colocou
41 | 4 - Tenha o sorriso no rosto :)
42 |
43 | ## Criadores
44 |
45 | Criado por Creuma Kuzola e Eufránio Diogo 💻 em 9 de Agosto de 2021
46 |
47 |
48 |
--------------------------------------------------------------------------------
/src/services/remote/submissions/submissionsRemote.js:
--------------------------------------------------------------------------------
1 | import { apiBaseUrl } from "../../../config/apiConfig";
2 | import { httpRequest } from "../../http/httpService";
3 |
4 | export const getApprovedSubmissions = async () => {
5 | const response = await httpRequest(`/submission/approve`, "GET");
6 | const data = await response.json();
7 |
8 | return data.submissions;
9 | };
10 |
11 | export const getNotApprovedSubmissions = async () => {
12 | const response = await httpRequest(`/submission/unapprove`, "GET");
13 | const data = await response.json();
14 |
15 | return data.submissions;
16 | };
17 |
18 | export const getRejectedSubmissions = async () => {
19 | const response = await httpRequest(`/submission/reject`, "GET");
20 | const data = await response.json();
21 |
22 | return data.submissions;
23 | };
24 |
25 | export const getSpecificSubmission = async (submissionId) => {
26 | const response = await httpRequest(`/submission/${submissionId}`, "GET");
27 | const data = await response.json();
28 |
29 | return data;
30 | };
31 |
32 | export const approveSubmission = async (submissionId) => {
33 | const response = await httpRequest(
34 | `/submission/approve/${submissionId}`,
35 | "PUT"
36 | );
37 | const data = await response.json();
38 | return data;
39 | };
40 |
41 | export const desapproveSubmission = async (submissionId) => {
42 | const response = await httpRequest(
43 | `/submission/unapprove/${submissionId}`,
44 | "PUT"
45 | );
46 | const data = await response.json();
47 | return data;
48 | };
49 |
50 | export const rejectSubmission = async (submissionId) => {
51 | const response = await httpRequest(
52 | `/submission/reject/${submissionId}`,
53 | "PUT"
54 | );
55 | const data = await response.json();
56 | return data.status;
57 | };
58 |
59 | export const deleteSubmission = async (submissionId) => {
60 | const response = await fetch(`/submission/${submissionId}`, "DELETE");
61 | const data = await response.json();
62 | return data.status;
63 | };
64 |
--------------------------------------------------------------------------------
/src/services/remote/course/courseRemote.js:
--------------------------------------------------------------------------------
1 | import store from "../../../reducers/Store";
2 | import { httpRequest } from "../../http/httpService";
3 |
4 | /*
5 | 1- See if we have the structure locale
6 | If has, make a request to know if the structure is the same from the server,
7 | if it's the same from the server only return the structure already stored
8 | if it's not the same structure update the local structure and update it
9 | if doesn't not have save the retrived from the server
10 | */
11 | export const getAllCoursesStructure = async function () {
12 | const state = store.getState().search;
13 | let auxAllCoursesStructure;
14 |
15 | if (state.alreadyLoadedCourseStructure) {
16 | auxAllCoursesStructure = state.courseStructure;
17 | } else {
18 | auxAllCoursesStructure = await httpRequest(`/course/all`, "GET");
19 |
20 | auxAllCoursesStructure = await auxAllCoursesStructure.json();
21 | auxAllCoursesStructure = auxAllCoursesStructure.courses;
22 |
23 | store.dispatch({
24 | type: "search/update",
25 | payload: {
26 | alreadyLoadedCourseStructure: true,
27 | courseStructure: auxAllCoursesStructure,
28 | },
29 | });
30 | }
31 |
32 | return auxAllCoursesStructure;
33 | };
34 |
35 | export const createCourse = async (body) => {
36 | const res = await httpRequest(`/course`, "POST", JSON.stringify(body));
37 | const data = await res.json();
38 |
39 | return data;
40 | };
41 |
42 | export const getCourses = async () => {
43 | const res = await httpRequest(`/course`, "GET");
44 | const data = await res.json();
45 |
46 | return data.courses;
47 | };
48 |
49 | export const deleteCourse = async (courseId) => {
50 | const res = await httpRequest(`/course/${courseId}`, "DELETE");
51 | const data = await res.json();
52 |
53 | return data.data;
54 | };
55 |
56 | export const updateCourse = async (courseId, body) => {
57 | const res = await httpRequest(
58 | `/course/${courseId}`,
59 | "PUT",
60 | JSON.stringify(body)
61 | );
62 | const data = await res.json();
63 |
64 | return data.data;
65 | };
66 |
--------------------------------------------------------------------------------
/src/components/contribute/Contribute.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import Form from "./form/Form";
3 |
4 | const Contribute = ({ getAllCoursesStructure }) => {
5 | const [forms, setForms] = useState([]);
6 | const [formId, setFormId] = useState(0);
7 | const [allCoursesStructure, setAllCoursesStructure] = useState({});
8 |
9 | const handleAllCourseStructure = React.useCallback(
10 | async function () {
11 | if (Object.keys(allCoursesStructure).length === 0) {
12 | const courses = await getAllCoursesStructure();
13 |
14 | setAllCoursesStructure(courses);
15 | }
16 | },
17 | [getAllCoursesStructure, allCoursesStructure]
18 | );
19 |
20 | const createForm = () => {
21 | if (Object.keys(allCoursesStructure).length !== 0) {
22 | const form = {
23 | formId: "form-" + formId,
24 | handleDelete: deleteForm,
25 | allCoursesStructure: allCoursesStructure,
26 | };
27 |
28 | setForms((prev) => prev.concat(form));
29 | setFormId((formId) => formId + 1);
30 | } else {
31 | //alert("Carrengando estrutura de cursos");
32 | }
33 | };
34 |
35 | const deleteForm = (id) => {
36 | setForms((prev) => prev.filter((form) => form.formId !== id));
37 | };
38 |
39 | useEffect(() => {
40 | handleAllCourseStructure();
41 | }, []);
42 |
43 | return (
44 |
45 |
50 |
51 |
52 |
53 | Aqui a sua contribuição e ajuda têm valor{" "}
54 |
55 |
56 |
57 | Contribuir
58 |
59 |
60 |
61 | {forms.map((form, index) => (
62 |
68 | ))}
69 |
70 |
71 | );
72 | };
73 |
74 | export default Contribute;
75 |
--------------------------------------------------------------------------------
/src/pages/NotificationPage/NotificationPage.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react'
2 | import { getSpecificNotification } from '../../services/remote/notifications/notificationRemote'
3 | import { getSpecificSubmission } from '../../services/remote/submissions/submissionsRemote'
4 | import SubmissionForm from '../Admin/SubmissionsPages/components/SubmissionForm'
5 |
6 | function NotificationPage({ getUserInfo }) {
7 | const [notificationId, setNotificationId] = useState(window.location.pathname.split('/')[window.location.pathname.split('/').length - 1])
8 | const [notification, setNofication] = useState({})
9 | const [isSubmissionGetted, setIsSubmissionGetted] = useState(false)
10 | const [submission, setSubmission] = useState({
11 | _id: '',
12 | isApproved: false,
13 | isRejected: true,
14 | userId: '',
15 | subject: '',
16 | documentType: '',
17 | createdAt: '',
18 | quantFiles: 0,
19 | filesTypes: [],
20 | filesUrls: []
21 | })
22 |
23 | const handleGetNotification = React.useCallback(async () => {
24 | const id = window.location.pathname.split('/')[window.location.pathname.split('/').length - 1]
25 | const res = await getSpecificNotification(id)
26 | setNotificationId(id)
27 |
28 | if (res !== undefined) {
29 | const notification = res.notification;
30 | setNofication(notification)
31 | handleGetSpecificSubmission(notification.referenceId)
32 | }
33 | }, [])
34 |
35 | const handleGetSpecificSubmission = async (submissionId) => {
36 | const res = await getSpecificSubmission(submissionId)
37 |
38 | setSubmission(res.data.submission)
39 | setIsSubmissionGetted(true)
40 | }
41 |
42 | useEffect(() => {
43 | handleGetNotification()
44 | }, [handleGetNotification, notificationId])
45 |
46 | return (
47 |
48 | {
49 | isSubmissionGetted &&
50 | { }}
62 | userInterface={true}
63 | >
64 | }
65 |
66 |
67 | )
68 | }
69 |
70 | export default NotificationPage
71 |
--------------------------------------------------------------------------------
/src/components/assigments/AssigmentContainer.js:
--------------------------------------------------------------------------------
1 | import AssigmentElement from "./AssigmentElement";
2 | import PreviewAssigment from "./PreviewAssigment";
3 | import { useState } from 'react';
4 |
5 | const Loader = () => {
6 |
7 | return (
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | )
29 | }
30 |
31 |
32 | const AssigmentContainer = ({ assigments, isPedding, downloadAssigment }) => {
33 | const [actualAssigment, setActualAssigment] = useState(0);
34 | const [isPreviewAssigmentShowing, setIsPreviewAssigmentShowing] = useState(false)
35 |
36 | const previewAssigment = (id) => {
37 | id = Number(id.replace('assigment-', ''));
38 | setActualAssigment(id)
39 | setIsPreviewAssigmentShowing(true);
40 | }
41 |
42 | const closePreviewAssigment = () => {
43 | setIsPreviewAssigmentShowing(false);
44 | }
45 |
46 | const nextAssigment = (e) => {
47 | e.stopPropagation();
48 |
49 | if (actualAssigment + 1 < assigments.length) {
50 | setActualAssigment(actualAssigment + 1);
51 | }
52 | }
53 |
54 | const prevAssigment = (e) => {
55 | e.stopPropagation();
56 |
57 | if (actualAssigment > 0) {
58 | setActualAssigment(actualAssigment - 1);
59 | }
60 | }
61 |
62 | return (
63 |
64 | {(!isPedding && assigments.length > 0) &&
65 | assigments.map((assigment, index) => (
66 |
67 | ))
68 | }
69 | {(!isPedding && assigments.length === 0) &&
70 |
Sem resultados!
71 | }
72 | {isPedding &&
73 | Loader()
74 | }
75 | {isPreviewAssigmentShowing &&
76 |
77 | }
78 |
79 | );
80 | }
81 |
82 | export default AssigmentContainer;
--------------------------------------------------------------------------------
/src/pages/UserPage/UserPage.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react'
2 | import { Switch, Route, Link } from 'react-router-dom'
3 | import defaultProfilePicture from '../../assets/images/pexels-andrea-piacquadio-3765114.jpg'
4 | import AutoGeneratedImage from '../../components/profile/AutoGeneratedImage'
5 | import UserConfigurationPage from './UserSubPages/UserConfigurationPage'
6 | import UserNotificationPage from './UserSubPages/UserNotificationPage'
7 | import UserStatisticsPage from './UserSubPages/UserStatisticsPage'
8 |
9 |
10 | export const UserPage = ({ getUserInfo }) => {
11 | const [user, setUser] = useState({
12 | profilePicture: '',
13 | username: '',
14 | email: '',
15 | course: '',
16 | year: ''
17 | })
18 |
19 | const handleGetUserInfo = React.useCallback(async () => {
20 | try {
21 | const response = await getUserInfo()
22 | setUser(response.data.user)
23 | } catch (error) {
24 | console.error(error)
25 | }
26 | }, [getUserInfo])
27 |
28 | useEffect(() => {
29 | handleGetUserInfo()
30 | }, [handleGetUserInfo])
31 |
32 | return (
33 |
34 |
35 |
36 | {user.profilePicture === '' || user.profilePicture === undefined ?
37 |
: (
40 |
41 | )}
42 |
43 |
44 |
45 |
46 |
Alterar de imagem
47 |
48 |
49 |
50 |
{user.username}
51 |
{user.course}
52 |
{user.year}
53 |
54 |
55 |
56 |
57 |
58 | Estatísticas
59 |
60 |
61 | Configurações
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 | )
78 | }
79 |
--------------------------------------------------------------------------------
/src/pages/Admin/Management/Course/ManagementCoursePage.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react'
2 | import { createCourse, getCourses, deleteCourse, updateCourse } from '../../../../services/remote/course/courseRemote'
3 | import ContainerItem from '../Components/ContainerItem'
4 |
5 | function ManagementCoursePage({ auth }) {
6 | const [showCreateForm, setShowCreateForm] = useState(false)
7 | const [courseName, setCourseName] = useState('')
8 | const [courses, setCourses] = useState([])
9 |
10 | const handleCreateCourse = async (e) => {
11 | e.preventDefault();
12 |
13 | try {
14 | await createCourse({
15 | course: courseName
16 | })
17 | } catch (error) {
18 | console.log(error)
19 | }
20 | }
21 |
22 | const handleGetCourses = async () => {
23 | try {
24 | const res = await getCourses()
25 |
26 | setCourses(res)
27 | } catch (error) {
28 | console.log(error)
29 | }
30 | }
31 |
32 | const handleDeleteCourse = async (id) => {
33 | try {
34 | await deleteCourse(id)
35 | } catch (error) {
36 | console.log(error)
37 | }
38 | }
39 |
40 | const handleUpdateCourse = async (id, value) => {
41 | try {
42 | await updateCourse(id, {
43 | course: value
44 | })
45 | } catch (error) {
46 | console.log(error)
47 | }
48 | }
49 |
50 | const handleShowCreateForm = () => {
51 | setShowCreateForm(!showCreateForm)
52 | }
53 |
54 | useEffect(() => {
55 | handleGetCourses()
56 | }, [])
57 | return (
58 |
59 |
60 |
61 |
65 | {!showCreateForm ? 'Criar Curso' : 'Ocultar Formulário'}
66 |
67 |
68 |
69 |
70 | {
71 | showCreateForm &&
72 | <>
73 |
74 |
87 |
88 |
89 | >
90 | }
91 |
92 |
93 |
94 | {
95 | courses.map(course => (
96 |
103 | ))
104 | }
105 |
106 |
107 | )
108 | }
109 |
110 | export default ManagementCoursePage
111 |
--------------------------------------------------------------------------------
/src/pages/Admin/Management/Subject/ManagementSubjectPage.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react'
2 | import { createSubject, getSubjects, deleteSubject, updateSubject } from '../../../../services/remote/subject/subjectRemote'
3 | import ContainerItem from '../Components/ContainerItem'
4 |
5 | function ManagementSubjectPage({ auth }) {
6 | const [showCreateForm, setShowCreateForm] = useState(false)
7 | const [subjectName, setSubjectName] = useState('')
8 | const [subjects, setSubjects] = useState([])
9 |
10 | const handleCreateSubject = async (e) => {
11 | e.preventDefault();
12 |
13 | try {
14 | await createSubject({
15 | subject: subjectName
16 | })
17 | } catch (error) {
18 | console.log(error)
19 | }
20 | }
21 |
22 | const handleGetSubjects = async () => {
23 | try {
24 | const res = await getSubjects()
25 |
26 | setSubjects(res)
27 | } catch (error) {
28 | console.log(error)
29 | }
30 | }
31 |
32 | const handleDeleteSubject = async (id) => {
33 | try {
34 | await deleteSubject(id)
35 | } catch (error) {
36 | console.log(error)
37 | }
38 | }
39 |
40 | const handleUpdateSubject = async (id, value) => {
41 | try {
42 | await updateSubject(id, {
43 | subject: value
44 | })
45 | } catch (error) {
46 | console.log(error)
47 | }
48 | }
49 |
50 | const handleShowCreateForm = () => {
51 | setShowCreateForm(!showCreateForm)
52 | }
53 |
54 | useEffect(() => {
55 | handleGetSubjects()
56 | }, [])
57 | return (
58 |
59 |
60 |
61 |
65 | {!showCreateForm ? 'Criar Disciplina' : 'Ocultar Formulário'}
66 |
67 |
68 |
69 |
70 | {
71 | showCreateForm &&
72 | <>
73 |
74 |
87 |
88 |
89 | >
90 | }
91 |
92 |
93 |
94 | {
95 | subjects.map(subject => (
96 |
103 | ))
104 | }
105 |
106 |
107 | )
108 | }
109 |
110 | export default ManagementSubjectPage
111 |
--------------------------------------------------------------------------------
/src/pages/AboutPage/AboutPage.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import image1 from '../../assets/images/pexels-fauxels-3184325.jpg'
3 | import winnerPhoto from '../../assets/images/pexels-nataliya-vaitkevich-6120397.jpg'
4 | import ContributeWayCard from './components/ContributeWayCard'
5 |
6 |
7 | function AboutPage() {
8 | return (
9 |
10 |
11 |
12 |
13 |
14 |
15 |
O que é a Biblioteca de Provas UCAN ?
16 |
É um projecto de caracter público para estudantes da Universidade Católica de Angola, com o objectivo principal de ajudar e resolver o problema de acesso a provas passadas de disciplinas, para que os estudantes se preparem antecipadamente de maneira direcionada para um melhor desempenho acadêmico
17 |
18 |
19 |
20 |
21 |
22 |
23 |
Quem pode ajudar?
24 |
25 |
26 |
Você pode ser dos grandes impulsionadores desse projecto e ainda receber reconhecimento por isso se desejar ou não. Então todos podem contribuir
27 |
28 |
29 |
30 |
31 |
32 |
33 |
Como contribuir?
34 |
35 |
36 |
37 |
47 |
58 |
59 |
60 |
61 | )
62 | }
63 |
64 | export default AboutPage
65 |
66 |
67 | const styles = {
68 | container: {
69 | marginTop: '5rem'
70 | },
71 | questionRow: {
72 | display: 'flex',
73 | justifyContent: 'space-between',
74 | marginBottom: '15rem'
75 | },
76 | heading1: {
77 | fontSize: '6.2rem',
78 | fontWeight: 'bold',
79 | marginBottom: '1.5rem',
80 | color: '#333',
81 | },
82 | normalText: {
83 | fontSize: '2rem',
84 | color: '#444',
85 | lineHeight: '1.5',
86 | },
87 | firstImage: {
88 | width: '45%',
89 | borderRadius: '1rem',
90 | borderWidth: '1rem',
91 | borderColor: '#00B2FF',
92 | objectFit: 'cover',
93 | marginRight: '5rem'
94 | },
95 | winnerPhoto: {
96 | height: '12.8rem',
97 | width: '12.8rem',
98 | objectFit: 'cover',
99 | borderRadius: '1rem',
100 | marginRight: '4rem'
101 | },
102 | questionSpecialContainer: {
103 | display: 'flex',
104 | justifyContent: 'flex-start',
105 | alignItems: 'center',
106 | marginBottom: '5rem'
107 | },
108 | secondRow: {
109 | marginBottom: '15rem'
110 | },
111 | thirdRow: {
112 | display: 'flex',
113 | flexDirection: 'column'
114 | },
115 | contributeWayCardsContainer: {
116 | display: 'flex',
117 | flexWrap: 'wrap',
118 | marginBottom: '15rem',
119 | },
120 |
121 | }
--------------------------------------------------------------------------------
/src/pages/HomePage/HomePage.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import { useSelector } from "react-redux";
3 | import AssigmentContainer from "../../components/assigments/AssigmentContainer";
4 | import SearchContainer from "../../components/search/SearchContainer";
5 | import { download } from "../../helpers/download/downloadHelper";
6 | export const SearchContext = React.createContext({});
7 |
8 | function HomePage({ getAllCoursesStructure, getAssigments }) {
9 | const courseStructure = useSelector((state) => state.search);
10 |
11 | const [assigments, setAssigments] = useState([]);
12 | const [actualCourse, setActualCourse] = useState("");
13 | const [isAllCoursesStructurePending, setAllCoursesStructurePending] =
14 | useState(true);
15 | const [actualSubject, setActualSubject] = useState("");
16 | const [actualDocumentType, setActualDocumentType] = useState("");
17 | const [allCoursesStructure, setAllCoursesStructure] = useState({});
18 | const [
19 | errorWhileGettingAllCourseStructure,
20 | setErrorWhileGettingAllCourseStructure,
21 | ] = useState(false);
22 | const [isPedding, setIsPedding] = useState(false);
23 |
24 | const handleGetAssigment = async () => {
25 | setIsPedding(true);
26 |
27 | const actualCourseId = actualCourse.split("#")[0].trim();
28 | const actualSubjectId = actualSubject.split("#")[0].trim();
29 |
30 | try {
31 | const assigments = await getAssigments(
32 | actualCourseId,
33 | actualSubjectId,
34 | actualDocumentType
35 | );
36 |
37 | setIsPedding(false);
38 | setAssigments(assigments);
39 | } catch (error) {
40 | setIsPedding(false);
41 | }
42 | };
43 |
44 | const searchButton = async () => {
45 | await handleGetAssigment();
46 | };
47 |
48 | const courseChange = function (value) {
49 | setActualCourse(value);
50 | };
51 | const subjectChange = function (value) {
52 | setActualSubject(value);
53 | };
54 | const documentTypeChange = function (value) {
55 | setActualDocumentType(value);
56 | };
57 |
58 | const handleAllCourseStructure = React.useCallback(
59 | async function () {
60 | try {
61 | const courses = await getAllCoursesStructure();
62 |
63 | setAllCoursesStructure(courses);
64 | setAllCoursesStructurePending(false);
65 | setErrorWhileGettingAllCourseStructure(false);
66 | setAllCoursesStructurePending(false);
67 | } catch (error) {
68 | console.log(error);
69 | setErrorWhileGettingAllCourseStructure(true);
70 | setAllCoursesStructurePending(false);
71 | }
72 | },
73 | [getAllCoursesStructure]
74 | );
75 |
76 | useEffect(() => {
77 | handleAllCourseStructure();
78 | }, [handleAllCourseStructure]);
79 |
80 | return (
81 |
82 | {isAllCoursesStructurePending === false &&
83 | errorWhileGettingAllCourseStructure === false && (
84 | <>
85 |
95 | >
96 | )}
97 |
98 | {isAllCoursesStructurePending === true &&
99 | !errorWhileGettingAllCourseStructure && (
100 |
101 | <>
102 |
103 | >
104 |
105 | )}
106 |
107 | {errorWhileGettingAllCourseStructure && (
108 |
109 |
110 | Um erro aconteceu, lamentamos, contacte{" "}
111 | provasucan01@gmail.com para suporte e ajuda.
112 |
113 |
114 | )}
115 |
116 |
121 |
126 |
127 |
128 | );
129 | }
130 |
131 | export default HomePage;
132 |
--------------------------------------------------------------------------------
/src/pages/UserPage/UserSubPages/UserConfigurationPage.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react'
2 | import { getActualUserData, updateUser } from '../../../services/remote/user/user'
3 |
4 | function UserConfigurationPage() {
5 | const [email, setEmail] = useState('')
6 | const [userId, setUserId] = useState('')
7 | const [username, setUsername] = useState('')
8 | const [showUpInContributorsTab, setIsInContributors] = useState(false)
9 |
10 | const handleGetUserInfo = async () => {
11 | const res = await getActualUserData();
12 |
13 | if (res.data !== undefined) {
14 | const data = res.data.user;
15 |
16 | setUserId(data._id)
17 | setUsername(data.username)
18 | setEmail(data.email)
19 | setIsInContributors(data.showUpInContributorsTab !== undefined ? data.showUpInContributorsTab : false)
20 | }
21 | }
22 |
23 | const onUpdate = async (e) => {
24 | e.preventDefault()
25 |
26 | try {
27 | const response = await updateUser(userId, {
28 | email,
29 | username,
30 | showUpInContributorsTab
31 | })
32 |
33 | if (response.status === 200) {
34 | alert('Dados Actualizados')
35 | } else {
36 | alert('Dados incorrectos')
37 | }
38 | } catch (error) {
39 | console.log(error)
40 | }
41 | }
42 |
43 | useEffect(() => {
44 | handleGetUserInfo();
45 | }, [])
46 |
47 | return (
48 |
117 | )
118 | }
119 |
120 | export default UserConfigurationPage
121 |
122 | const styles = {
123 | formContainer: {
124 | backgroundColor: 'white',
125 | position: 'relative',
126 | display: 'flex',
127 | flexDirection: 'column',
128 | },
129 | formRow: {
130 | display: 'flex',
131 | flexDirection: 'column',
132 | margin: '4rem 0',
133 | },
134 | formLabel: {
135 | fontSize: '1.8rem',
136 | marginBottom: '1rem',
137 | },
138 | formInput: {
139 | fontSize: '1.8rem',
140 | color: '#333',
141 | padding: '1rem 2rem',
142 | borderRadius: '0.5rem',
143 | border: '0.1rem solid #0003'
144 | },
145 | submitButton: {
146 | fontSize: '2rem',
147 | padding: '1rem 2rem',
148 | border: 'none',
149 | backgroundColor: '#00B2FF',
150 | color: '#fff',
151 | borderRadius: '0.5rem',
152 | marginBottom: '2rem',
153 | cursor: 'pointer'
154 | },
155 | signupText: {
156 | fontSize: '1.4rem'
157 | },
158 | backgroundImage: {
159 | position: 'absolute',
160 | width: '50%',
161 | left: '50%',
162 | transform: 'translateX(-50%)',
163 | zIndex: -1
164 | }
165 | }
--------------------------------------------------------------------------------
/src/pages/Login/LoginPage.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import { Link } from "react-router-dom";
3 | import readerImage from "../../assets/images/undraw_book_lover_mkck.svg";
4 | import { login } from "../../services/remote/auth/login";
5 | //
6 | function LoginPage() {
7 | const [email, setEmail] = useState("");
8 | const [password, setPassword] = useState("");
9 | const [sendingLogin, setSendingLogin] = useState(false);
10 | const loginSubmit = async (e) => {
11 | e.preventDefault();
12 | setSendingLogin(true);
13 |
14 | try {
15 | console.log({
16 | email,
17 | password,
18 | });
19 | const response = await login(
20 | JSON.stringify({
21 | email: email,
22 | password: password,
23 | })
24 | );
25 |
26 | if (response.status === 200) {
27 | localStorage.setItem("auth-token-biblioteca-de-provas", response.token);
28 | window.location.pathname = "biblioteca-de-provas-e-materiais-site/";
29 | } else {
30 | alert("Dados incorrectos");
31 | }
32 | setSendingLogin(false);
33 | } catch (error) {
34 | console.log(error);
35 | }
36 | setSendingLogin(false);
37 | };
38 |
39 | return (
40 |
41 |
47 |
48 |
103 |
104 | );
105 | }
106 |
107 | export default LoginPage;
108 |
109 | const styles = {
110 | loginContainer: {
111 | width: "100%",
112 | display: "flex",
113 | justifyContent: "center",
114 | alignItems: "center",
115 | },
116 | formContainer: {
117 | width: "35%",
118 | backgroundColor: "white",
119 | position: "relative",
120 | padding: "4rem",
121 | border: "0.1rem solid #00B2FF",
122 | borderRadius: "0.5rem",
123 | display: "flex",
124 | flexDirection: "column",
125 | marginTop: "10rem",
126 | },
127 | formRow: {
128 | display: "flex",
129 | flexDirection: "column",
130 | margin: "4rem 0",
131 | },
132 | formLabel: {
133 | fontSize: "1.8rem",
134 | marginBottom: "1rem",
135 | },
136 | formInput: {
137 | fontSize: "1.8rem",
138 | color: "#333",
139 | padding: "1rem 2rem",
140 | borderRadius: "0.5rem",
141 | border: "0.1rem solid #0003",
142 | },
143 | submitButton: {
144 | fontSize: "2rem",
145 | padding: "1rem 2rem",
146 | border: "none",
147 | backgroundColor: "#00B2FF",
148 | color: "#fff",
149 | borderRadius: "0.5rem",
150 | marginBottom: "2rem",
151 | cursor: "pointer",
152 | },
153 | signupText: {
154 | fontSize: "1.4rem",
155 | },
156 | progressContainer: {
157 | display: "flex",
158 | alignItems: "center",
159 | justifyContent: "center",
160 | padding: "15px 0",
161 | background: "#00b2ff",
162 | color: "#fff",
163 | fontSize: "16px",
164 | borderRadius: "10px"
165 |
166 | },
167 | backgroundImage: {
168 | position: "absolute",
169 | width: "50%",
170 | left: "50%",
171 | transform: "translateX(-50%)",
172 | zIndex: -1,
173 | },
174 | };
175 |
--------------------------------------------------------------------------------
/src/pages/Signup/SignupPage.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react'
2 | import { Link } from 'react-router-dom'
3 | import { apiBaseUrl } from '../../config/apiConfig'
4 | import readerImage from '../../assets/images/undraw_book_lover_mkck.svg'
5 | //
6 | function SignupPage() {
7 | const [username, setUsername] = useState('')
8 | const [email, setEmail] = useState('')
9 | const [password, setPassword] = useState('')
10 |
11 | const signupSubmit = (e) => {
12 | e.preventDefault()
13 |
14 | fetch(`${apiBaseUrl}/signup`, {
15 | method: 'POST',
16 | body: JSON.stringify({
17 | username,
18 | email,
19 | password
20 | }),
21 | headers: {
22 | 'Content-Type': 'application/json'
23 | }
24 | })
25 | .then(response => response.json())
26 | .then(data => {
27 | if (data.status === 200) {
28 | localStorage.setItem('auth-token-biblioteca-de-provas', data.token)
29 | window.location.pathname = 'biblioteca-de-provas-e-materiais-site/'
30 | } else {
31 | alert('Dados incorrectos')
32 | }
33 |
34 | })
35 | .catch(error => {
36 | console.log(error)
37 | })
38 | }
39 | return (
40 |
41 |
42 |
43 |
115 |
116 |
117 | )
118 | }
119 |
120 | export default SignupPage
121 |
122 | const styles = {
123 | loginContainer: {
124 | width: '100%',
125 | display: 'flex',
126 | justifyContent: 'center',
127 | alignItems: 'center'
128 | },
129 | formContainer: {
130 | width: '35%',
131 | backgroundColor: 'white',
132 | position: 'relative',
133 | padding: '4rem',
134 | border: '0.1rem solid #00B2FF',
135 | borderRadius: '0.5rem',
136 | display: 'flex',
137 | flexDirection: 'column',
138 | marginTop: '10rem'
139 | },
140 | formRow: {
141 | display: 'flex',
142 | flexDirection: 'column',
143 | margin: '4rem 0',
144 | },
145 | formLabel: {
146 | fontSize: '1.8rem',
147 | marginBottom: '1rem',
148 | },
149 | formInput: {
150 | fontSize: '1.8rem',
151 | color: '#333',
152 | padding: '1rem 2rem',
153 | borderRadius: '0.5rem',
154 | border: '0.1rem solid #0003'
155 | },
156 | submitButton: {
157 | fontSize: '2rem',
158 | padding: '1rem 2rem',
159 | border: 'none',
160 | backgroundColor: '#00B2FF',
161 | color: '#fff',
162 | borderRadius: '0.5rem',
163 | marginBottom: '2rem',
164 | cursor: 'pointer'
165 | },
166 | signupText: {
167 | fontSize: '1.4rem'
168 | },
169 | backgroundImage: {
170 | position: 'absolute',
171 | width: '50%',
172 | left: '50%',
173 | transform: 'translateX(-50%)'
174 | },
175 | zIndex: -1
176 | }
--------------------------------------------------------------------------------
/src/components/navbar/components/Menu.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react'
2 | import { Route, Switch } from 'react-router'
3 | import { Link } from 'react-router-dom'
4 | import { isValidToken } from '../../../services/auth/authService'
5 | import { getActualUserData } from '../../../services/remote/user/user'
6 | import AutoGeneratedImage from '../../profile/AutoGeneratedImage'
7 | import ShowNotificationContainer from '../notification/ShowNotificationContainer'
8 |
9 | export default function Menu() {
10 | const [showLoginButton, setShowLoginButton] = useState(true)
11 | const [hasAdminRole, setHasAdminRole] = useState(false)
12 | const [userHasNotification, setUserHasNotification] = useState(false)
13 | const [quantUserNotification, setQuantUserNotification] = useState(0)
14 | const [userProfilePicture, setUserProfilePicture] = useState('')
15 | const [username, setUsername] = useState('')
16 | const [email, setEmail] = useState('')
17 | const [showNotificationMainContainer, setShowNotificationMainContainer] = useState(false)
18 |
19 | const verifyTokenValidation = React.useCallback(async () => {
20 | const res = await isValidToken()
21 | setShowLoginButton(!res)
22 | }, [])
23 |
24 | const verifyAdminRole = async (user) => {
25 | setHasAdminRole(user.roles.includes('admin'))
26 | }
27 |
28 | const verifyUserRole = React.useCallback(async () => {
29 | const res = await getActualUserData();
30 |
31 | if (res.data !== undefined) {
32 | const user = res.data.user
33 | setQuantUserNotification(user.quantNewNotifications)
34 | setUserHasNotification(user.hasNewNotifications)
35 | if (user.username !== undefined) {
36 | setUsername(user.username)
37 | }
38 | setEmail(user.email)
39 | setUserProfilePicture(user.profilePicture)
40 | verifyAdminRole(user)
41 | } else {
42 | localStorage.removeItem('auth-token-biblioteca-de-provas')
43 | }
44 | }, [])
45 |
46 | const handleShowNotificationContainer = (newShowNotificationContainer) => {
47 | setShowNotificationMainContainer(newShowNotificationContainer)
48 | setQuantUserNotification(0)
49 | setUserHasNotification(false)
50 | }
51 |
52 | useEffect(() => {
53 | try {
54 | verifyTokenValidation()
55 | verifyUserRole()
56 | } catch (error) {
57 | console.log(error)
58 | }
59 | }, [verifyUserRole, verifyTokenValidation])
60 |
61 | return (
62 |
63 |
64 |
65 |
Home
66 |
Sobre
67 | {
68 | showLoginButton &&
69 |
Login
70 | }
71 |
Contribuidores
72 |
Contribua
73 | {
74 | hasAdminRole &&
75 |
Admin
76 | }
77 | {
78 | !showLoginButton &&
79 | <>
80 |
81 |
{
82 | setShowNotificationMainContainer(false)
83 | }}>
84 |
85 | {username !== '' &&
86 |
89 | }
90 |
91 |
92 |
93 |
94 |
95 |
96 | Profile
97 | {
99 | setShowNotificationMainContainer(true)
100 | }}>
101 | Notificações
102 | {
103 | userHasNotification === true &&
104 |
105 | {quantUserNotification}
106 |
107 | }
108 |
109 | {
110 | localStorage.setItem('auth-token-biblioteca-de-provas', '')
111 | setShowLoginButton(true)
112 | setHasAdminRole(false)
113 | }}>Logout
114 |
115 |
116 | {showNotificationMainContainer &&
117 |
118 |
119 |
120 | }
121 |
122 |
123 |
124 | >
125 | }
126 |
127 |
128 |
129 |
130 | )
131 | }
132 |
--------------------------------------------------------------------------------
/src/pages/Admin/SubmissionsPages/components/SubmissionForm.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react'
2 | import { createNotification } from '../../../../services/remote/notifications/notificationRemote'
3 | import { approveSubmission, desapproveSubmission, rejectSubmission, deleteSubmission } from '../../../../services/remote/submissions/submissionsRemote'
4 | import { getUserById } from '../../../../services/remote/user/user'
5 |
6 |
7 | function SubmissionForm(
8 | {
9 | submissionId,
10 | isApproved,
11 | isRejected,
12 | userId,
13 | subject,
14 | documentType,
15 | createdAt,
16 | quantFiles,
17 | filesTypes,
18 | files,
19 | updateAction,
20 | ...rest
21 | }
22 | ) {
23 | const [username, setUsername] = useState('')
24 | const [isImagePreviewerOpen, setIsImagePreviewerOpen] = useState(false)
25 |
26 | const handleGetUserInfo = React.useCallback(async () => {
27 | const user = (await getUserById(userId)).user
28 |
29 | setUsername(user.username ? user.username : user.email)
30 | }, [userId])
31 |
32 | const handleApproveSubmission = async () => {
33 | const result = await approveSubmission(submissionId)
34 | const data = result.data
35 |
36 | await createNotification({
37 | notification: {
38 | referenceId: data._id,
39 | reciverId: data.userId,
40 | notificationType: 'SUBMISSION_NOTIFICATION_APPROVED',
41 | message: '',
42 | }
43 | })
44 | .then((data) => {
45 | })
46 | .catch((err) => {
47 | console.log(err)
48 | })
49 | }
50 |
51 | const handleDesapproveSubmission = async () => {
52 | await desapproveSubmission(submissionId)
53 | }
54 |
55 | const handleRejectSubmission = async () => {
56 | await rejectSubmission(submissionId)
57 | }
58 |
59 | const handleDeleteRejectedSubmission = async () => {
60 | await deleteSubmission(submissionId)
61 | }
62 |
63 |
64 | useEffect(() => {
65 | handleGetUserInfo()
66 | }, [handleGetUserInfo])
67 |
68 | return (
69 |
70 |
71 |
72 | Status
73 | {isRejected ? 'Rejeitado' : 'Não Reijatada'}
76 | {isApproved ? 'Aprovada' : 'Não Aprovada'}
79 |
80 |
81 |
82 |
83 |
84 | Usuario:
85 | {username}
86 |
87 |
88 | Disciplina:
89 | {subject}
90 |
91 |
92 | Documento:
93 | {documentType}
94 |
95 |
96 | Data Submissão:
97 | {createdAt}
98 |
99 |
100 |
101 | {
102 | files.map((file, index) => (
103 |
104 |
109 |
110 |
{
111 | e.preventDefault();
112 | e.stopPropagation();
113 | //deleteIndividualFile(index);
114 | }}>X
115 |
116 | ))
117 | }
118 |
119 |
120 |
121 | {quantFiles} ficheiro(s) {`(${filesTypes.join(',')})`}
122 |
123 |
124 |
125 | {
126 | rest.userInterface !== true &&
127 | <>
128 | {
131 | isApproved ? handleDesapproveSubmission() : handleApproveSubmission()
132 |
133 | updateAction()
134 | }}
135 | className="submission-form-model-btn active-button">
136 | {isApproved ? 'Desaprovar' : 'Aprovar'}
137 |
138 |
139 | {
141 | isRejected ? handleDeleteRejectedSubmission() : handleRejectSubmission()
142 |
143 | updateAction()
144 | }}
145 | className="submission-form-model-btn reject-button">
146 | {isRejected ? 'Deletar' : 'Rejeitar'}
147 |
148 | >
149 | }
150 |
151 |
152 |
153 |
154 |
155 |
156 | )
157 | }
158 |
159 | export default SubmissionForm
160 |
--------------------------------------------------------------------------------
/src/pages/Admin/Management/CourseSubject/ManagementCourseSubjectPage.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import { getSubjects } from "../../../../services/remote/subject/subjectRemote";
3 | import { getCourses } from "../../../../services/remote/course/courseRemote";
4 |
5 | import {
6 | createCourseSubject,
7 | getCoursesSubjects,
8 | deleteCourseSubject,
9 | updateCourseSubject,
10 | } from "../../../../services/remote/courseSubject/courseSubject.js";
11 | import ContainerItem from "../Components/ContainerItem";
12 |
13 | function ManagementCourseSubjectPage({ auth }) {
14 | const [showCreateForm, setShowCreateForm] = useState(false);
15 | const [courseName, setCourseName] = useState("");
16 | const [courses, setCourses] = useState([]);
17 | const [subjectName, setSubjectName] = useState("");
18 | const [subjects, setSubjects] = useState([]);
19 | const [year, setYear] = useState(1);
20 | const [years, setYears] = useState([1, 2, 3, 4, 5, 6]);
21 | const [semestre, setSemestre] = useState(1);
22 | const [semestres, setSemestres] = useState([1, 2]);
23 |
24 | const handleCreateCourseSubject = async (e) => {
25 | e.preventDefault();
26 |
27 | try {
28 | await createCourseSubject({
29 | course: courseName,
30 | subject: subjectName,
31 | year,
32 | semestre,
33 | });
34 | } catch (error) {
35 | console.log(error);
36 | }
37 | };
38 |
39 | const handleDeleteSubject = async (id) => {
40 | try {
41 | await deleteCourseSubject(id);
42 | } catch (error) {
43 | console.log(error);
44 | }
45 | };
46 |
47 | const handleUpdateSubject = async (id, value) => {
48 | try {
49 | await updateCourseSubject(id, {
50 | subject: value,
51 | });
52 | } catch (error) {
53 | console.log(error);
54 | }
55 | };
56 |
57 | const handleShowCreateForm = () => {
58 | setShowCreateForm(!showCreateForm);
59 | };
60 |
61 | const handleGetSubjects = async () => {
62 | try {
63 | const res = await getSubjects();
64 |
65 | setSubjectName(res[0]._id);
66 | setSubjects(res);
67 | } catch (error) {
68 | console.log(error);
69 | }
70 | };
71 |
72 | const handleGetCourses = async () => {
73 | try {
74 | const res = await getCourses();
75 |
76 | setCourseName(res[0]._id);
77 | setCourses(res);
78 | } catch (error) {
79 | console.log(error);
80 | }
81 | };
82 |
83 | useEffect(() => {
84 | handleGetCourses();
85 | handleGetSubjects();
86 | }, []);
87 | return (
88 |
89 |
90 |
91 |
92 | {!showCreateForm ? "Criar Associação" : "Ocultar Formulário"}
93 |
94 |
95 |
96 |
97 | {showCreateForm && (
98 | <>
99 |
173 |
174 | >
175 | )}
176 |
177 |
178 | {subjects.map((subject) => (
179 |
186 | ))}
187 |
188 |
189 | );
190 | }
191 |
192 | export default ManagementCourseSubjectPage;
193 |
--------------------------------------------------------------------------------
/src/components/search/SearchContainer.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 |
3 | const SearchContainer = ({
4 | handleSearch,
5 | handleActualCourseChange,
6 | handleActualSubjectChange,
7 | handleActualDocumentType,
8 | actualCourse,
9 | allCoursesStructure,
10 | }) => {
11 | const [actualYear, setActualYear] = useState("");
12 | const [actualSemestre, setActualSemestre] = useState("");
13 | const [courses, setCourses] = useState([]);
14 | const [years, setYears] = useState([]);
15 | const [semestres, setSemestres] = useState([]);
16 | const [subjects, setSubjects] = useState([]);
17 | const [coursesStructure, setCoursesStructure] = useState({});
18 |
19 | const searchStructure = React.useCallback(
20 | async (selectedCourse = "", selectedYear = 0, selectedSemestre = 0) => {
21 | const courses = Object.keys(allCoursesStructure);
22 |
23 | if (courses.length !== 0) {
24 | setCoursesStructure(allCoursesStructure);
25 | setCourses(courses);
26 |
27 | const course = courses[0];
28 |
29 | const year = Object.keys(allCoursesStructure[course][course])[0];
30 | setYears(Object.keys(allCoursesStructure[course][course]));
31 | setActualYear(year);
32 |
33 | const semestre = Object.keys(
34 | allCoursesStructure[course][course][year]
35 | )[0];
36 | setActualSemestre(semestre);
37 | setSemestres(Object.keys(allCoursesStructure[course][course][year]));
38 |
39 | const subjects = Object.keys(
40 | allCoursesStructure[course][course][year][semestre]
41 | );
42 |
43 | setSubjects(subjects);
44 |
45 | handleActualCourseChange(course);
46 | handleActualSubjectChange(
47 | subjects[0]
48 | );
49 | handleActualDocumentType("Frequências");
50 | }
51 | },
52 | [
53 | allCoursesStructure,
54 | handleActualCourseChange,
55 | handleActualDocumentType,
56 | handleActualSubjectChange,
57 | ]
58 | );
59 |
60 | const yearChange = function (e) {
61 | const year = e.target.value;
62 |
63 | setActualYear(year);
64 | setSemestres(
65 | Object.keys(allCoursesStructure[actualCourse][actualCourse][year])
66 | );
67 | setSubjects(
68 | Object.keys(
69 | allCoursesStructure[actualCourse][actualCourse][year][actualSemestre]
70 | )
71 | );
72 |
73 | setActualSemestre(
74 | Object.keys(allCoursesStructure[actualCourse][actualCourse][year])[0]
75 | );
76 | handleActualSubjectChange(
77 | Object.keys(
78 | allCoursesStructure[actualCourse][actualCourse][year][actualSemestre]
79 | )[0]
80 | );
81 | };
82 |
83 | const semestreChange = function (e) {
84 | const semestre = e.target.value;
85 |
86 | setActualSemestre(semestre);
87 | setCourses(
88 | Object.keys(
89 | allCoursesStructure[actualCourse][actualCourse][actualYear][semestre]
90 | )
91 | );
92 |
93 | handleActualSubjectChange(
94 | Object.keys(
95 | allCoursesStructure[actualCourse][actualCourse][actualYear][semestre]
96 | )[0]
97 | );
98 | };
99 |
100 | const courseChange = function (e) {
101 | handleActualCourseChange(e.target.value);
102 | const course = e.target.value;
103 |
104 | try {
105 | const years = Object.keys(allCoursesStructure[course][course]);
106 | const semestres = Object.keys(
107 | allCoursesStructure[course][course][actualYear]
108 | );
109 | const subjects = Object.keys(
110 | allCoursesStructure[course][course][actualYear][actualSemestre]
111 | );
112 |
113 | setYears(years);
114 | setSemestres(semestres);
115 | setSubjects(subjects);
116 | } catch (err) {
117 | setYears([]);
118 | setSemestres([]);
119 | setSubjects([]);
120 | }
121 | };
122 | const subjectChange = function (e) {
123 | handleActualSubjectChange(e.target.value);
124 | };
125 | const documentTypeChange = function (e) {
126 | handleActualDocumentType(e.target.value);
127 | };
128 |
129 | useEffect(() => {
130 | searchStructure();
131 | }, []);
132 |
133 | return (
134 |
135 |
136 |
142 | {courses.map((courseIdentification) => {
143 | const splitedCourseIdentification = courseIdentification.split("#");
144 | const courseName = splitedCourseIdentification[1].trim();
145 |
146 | return {courseName} ;
147 | })}
148 |
149 |
150 |
156 | {years.map((year) => (
157 | {year} º Ano
158 | ))}
159 |
160 |
161 |
167 | {semestres.map((semestre) => (
168 | {semestre} º Semestre
169 | ))}
170 |
171 |
172 |
178 | {subjects.map((subjectIdentification) => {
179 | const splitedSubjectIdentification = subjectIdentification.split("#");
180 | const subjectName = splitedSubjectIdentification[1].trim();
181 |
182 | return {subjectName} ;
183 | })}
184 |
185 |
186 |
192 | Frequências
193 | Exames
194 | Recursos
195 |
196 |
197 |
198 |
199 | Pesquisar
200 |
201 |
202 |
203 | );
204 | };
205 |
206 | export default SearchContainer;
207 |
--------------------------------------------------------------------------------
/src/css/normalize/normalize.css:
--------------------------------------------------------------------------------
1 | /*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
2 |
3 | /* Document
4 | ========================================================================== */
5 |
6 | /**
7 | * 1. Correct the line height in all browsers.
8 | * 2. Prevent adjustments of font size after orientation changes in iOS.
9 | */
10 |
11 | html {
12 | line-height: 1.15; /* 1 */
13 | -webkit-text-size-adjust: 100%; /* 2 */
14 | font-size: 62.5%;
15 | }
16 |
17 | /* Sections
18 | ========================================================================== */
19 |
20 | /**
21 | * Remove the margin in all browsers.
22 | */
23 |
24 | body {
25 | margin: 0;
26 | }
27 |
28 | /**
29 | * Render the `main` element consistently in IE.
30 | */
31 |
32 | main {
33 | display: block;
34 | }
35 |
36 | /**
37 | * Correct the font size and margin on `h1` elements within `section` and
38 | * `article` contexts in Chrome, Firefox, and Safari.
39 | */
40 |
41 | h1 {
42 | font-size: 2em;
43 | margin: 0.67em 0;
44 | }
45 |
46 | /* Grouping content
47 | ========================================================================== */
48 |
49 | /**
50 | * 1. Add the correct box sizing in Firefox.
51 | * 2. Show the overflow in Edge and IE.
52 | */
53 |
54 | hr {
55 | box-sizing: content-box; /* 1 */
56 | height: 0; /* 1 */
57 | overflow: visible; /* 2 */
58 | }
59 |
60 | /**
61 | * 1. Correct the inheritance and scaling of font size in all browsers.
62 | * 2. Correct the odd `em` font sizing in all browsers.
63 | */
64 |
65 | pre {
66 | font-family: monospace, monospace; /* 1 */
67 | font-size: 1em; /* 2 */
68 | }
69 |
70 | /* Text-level semantics
71 | ========================================================================== */
72 |
73 | /**
74 | * Remove the gray background on active links in IE 10.
75 | */
76 |
77 | a {
78 | background-color: transparent;
79 | }
80 |
81 | /**
82 | * 1. Remove the bottom border in Chrome 57-
83 | * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
84 | */
85 |
86 | abbr[title] {
87 | border-bottom: none; /* 1 */
88 | text-decoration: underline; /* 2 */
89 | text-decoration: underline dotted; /* 2 */
90 | }
91 |
92 | /**
93 | * Add the correct font weight in Chrome, Edge, and Safari.
94 | */
95 |
96 | b,
97 | strong {
98 | font-weight: bolder;
99 | }
100 |
101 | /**
102 | * 1. Correct the inheritance and scaling of font size in all browsers.
103 | * 2. Correct the odd `em` font sizing in all browsers.
104 | */
105 |
106 | code,
107 | kbd,
108 | samp {
109 | font-family: monospace, monospace; /* 1 */
110 | font-size: 1em; /* 2 */
111 | }
112 |
113 | /**
114 | * Add the correct font size in all browsers.
115 | */
116 |
117 | small {
118 | font-size: 80%;
119 | }
120 |
121 | /**
122 | * Prevent `sub` and `sup` elements from affecting the line height in
123 | * all browsers.
124 | */
125 |
126 | sub,
127 | sup {
128 | font-size: 75%;
129 | line-height: 0;
130 | position: relative;
131 | vertical-align: baseline;
132 | }
133 |
134 | sub {
135 | bottom: -0.25em;
136 | }
137 |
138 | sup {
139 | top: -0.5em;
140 | }
141 |
142 | /* Embedded content
143 | ========================================================================== */
144 |
145 | /**
146 | * Remove the border on images inside links in IE 10.
147 | */
148 |
149 | img {
150 | border-style: none;
151 | }
152 |
153 | /* Forms
154 | ========================================================================== */
155 |
156 | /**
157 | * 1. Change the font styles in all browsers.
158 | * 2. Remove the margin in Firefox and Safari.
159 | */
160 |
161 | button,
162 | input,
163 | optgroup,
164 | select,
165 | textarea {
166 | font-family: inherit; /* 1 */
167 | font-size: 100%; /* 1 */
168 | line-height: 1.15; /* 1 */
169 | margin: 0; /* 2 */
170 | }
171 |
172 | /**
173 | * Show the overflow in IE.
174 | * 1. Show the overflow in Edge.
175 | */
176 |
177 | button,
178 | input { /* 1 */
179 | overflow: visible;
180 | }
181 |
182 | /**
183 | * Remove the inheritance of text transform in Edge, Firefox, and IE.
184 | * 1. Remove the inheritance of text transform in Firefox.
185 | */
186 |
187 | button,
188 | select { /* 1 */
189 | text-transform: none;
190 | }
191 |
192 | /**
193 | * Correct the inability to style clickable types in iOS and Safari.
194 | */
195 |
196 | button,
197 | [type="button"],
198 | [type="reset"],
199 | [type="submit"] {
200 | -webkit-appearance: button;
201 | }
202 |
203 | /**
204 | * Remove the inner border and padding in Firefox.
205 | */
206 |
207 | button::-moz-focus-inner,
208 | [type="button"]::-moz-focus-inner,
209 | [type="reset"]::-moz-focus-inner,
210 | [type="submit"]::-moz-focus-inner {
211 | border-style: none;
212 | padding: 0;
213 | }
214 |
215 | /**
216 | * Restore the focus styles unset by the previous rule.
217 | */
218 |
219 | button:-moz-focusring,
220 | [type="button"]:-moz-focusring,
221 | [type="reset"]:-moz-focusring,
222 | [type="submit"]:-moz-focusring {
223 | outline: 1px dotted ButtonText;
224 | }
225 |
226 | /**
227 | * Correct the padding in Firefox.
228 | */
229 |
230 | fieldset {
231 | padding: 0.35em 0.75em 0.625em;
232 | }
233 |
234 | /**
235 | * 1. Correct the text wrapping in Edge and IE.
236 | * 2. Correct the color inheritance from `fieldset` elements in IE.
237 | * 3. Remove the padding so developers are not caught out when they zero out
238 | * `fieldset` elements in all browsers.
239 | */
240 |
241 | legend {
242 | box-sizing: border-box; /* 1 */
243 | color: inherit; /* 2 */
244 | display: table; /* 1 */
245 | max-width: 100%; /* 1 */
246 | padding: 0; /* 3 */
247 | white-space: normal; /* 1 */
248 | }
249 |
250 | /**
251 | * Add the correct vertical alignment in Chrome, Firefox, and Opera.
252 | */
253 |
254 | progress {
255 | vertical-align: baseline;
256 | }
257 |
258 | /**
259 | * Remove the default vertical scrollbar in IE 10+.
260 | */
261 |
262 | textarea {
263 | overflow: auto;
264 | }
265 |
266 | /**
267 | * 1. Add the correct box sizing in IE 10.
268 | * 2. Remove the padding in IE 10.
269 | */
270 |
271 | [type="checkbox"],
272 | [type="radio"] {
273 | box-sizing: border-box; /* 1 */
274 | padding: 0; /* 2 */
275 | }
276 |
277 | /**
278 | * Correct the cursor style of increment and decrement buttons in Chrome.
279 | */
280 |
281 | [type="number"]::-webkit-inner-spin-button,
282 | [type="number"]::-webkit-outer-spin-button {
283 | height: auto;
284 | }
285 |
286 | /**
287 | * 1. Correct the odd appearance in Chrome and Safari.
288 | * 2. Correct the outline style in Safari.
289 | */
290 |
291 | [type="search"] {
292 | -webkit-appearance: textfield; /* 1 */
293 | outline-offset: -2px; /* 2 */
294 | }
295 |
296 | /**
297 | * Remove the inner padding in Chrome and Safari on macOS.
298 | */
299 |
300 | [type="search"]::-webkit-search-decoration {
301 | -webkit-appearance: none;
302 | }
303 |
304 | /**
305 | * 1. Correct the inability to style clickable types in iOS and Safari.
306 | * 2. Change font properties to `inherit` in Safari.
307 | */
308 |
309 | ::-webkit-file-upload-button {
310 | -webkit-appearance: button; /* 1 */
311 | font: inherit; /* 2 */
312 | }
313 |
314 | /* Interactive
315 | ========================================================================== */
316 |
317 | /*
318 | * Add the correct display in Edge, IE 10+, and Firefox.
319 | */
320 |
321 | details {
322 | display: block;
323 | }
324 |
325 | /*
326 | * Add the correct display in all browsers.
327 | */
328 |
329 | summary {
330 | display: list-item;
331 | }
332 |
333 | /* Misc
334 | ========================================================================== */
335 |
336 | /**
337 | * Add the correct display in IE 10+.
338 | */
339 |
340 | template {
341 | display: none;
342 | }
343 |
344 | /**
345 | * Add the correct display in IE 10.
346 | */
347 |
348 | [hidden] {
349 | display: none;
350 | }
351 |
--------------------------------------------------------------------------------
/public/img/undraw_Team_re_0bfe.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/contribute/form/Form.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import { generateRemoteDestFolder } from "../../../helpers/form/formHelper";
3 | import { uploadAssigments } from "../../../services/remote/assigments/assigmentsRemote";
4 |
5 | const Form = ({ id, handleDelete, allCoursesStructure }) => {
6 | const [avaibleCourses, setAvailableCourses] = useState([]);
7 | const [avaibleYears, setAvaibleYears] = useState([]);
8 | const [avaibleSemestres, setAvaibleSemestres] = useState([]);
9 | const [avaibleSubjects, setAvaibleSubjects] = useState([]);
10 | const [filesToSubmitList, setFilesToSubmitList] = useState([]);
11 |
12 | const [formState, setFormState] = useState("sended");
13 |
14 | const [courseSelected, setCourseSelected] = useState("");
15 | const [yearSelected, setYearSelected] = useState("");
16 | const [semestreSelected, setSemestreSelected] = useState("");
17 | const [subjectSelected, setSubjectSelected] = useState("");
18 | const [documentType, setDocumentTypeSelected] = useState("");
19 | const [coursesStructure, setCoursesStructure] = useState({});
20 |
21 | const setInitialStateOfForm = React.useCallback(async () => {
22 | const courses = Object.keys(allCoursesStructure);
23 |
24 | if (courses.length !== 0) {
25 | setCoursesStructure(allCoursesStructure);
26 | setAvailableCourses(courses);
27 |
28 | const course = courses[0];
29 |
30 | const year = Object.keys(allCoursesStructure[course][course])[0];
31 | setAvaibleYears(Object.keys(allCoursesStructure[course][course]));
32 | setYearSelected(year);
33 |
34 | const semestre = Object.keys(
35 | allCoursesStructure[course][course][year]
36 | )[0];
37 | setSemestreSelected(semestre);
38 | setAvaibleSemestres(
39 | Object.keys(allCoursesStructure[course][course][year])
40 | );
41 |
42 | const subjects = Object.keys(
43 | allCoursesStructure[course][course][year][semestre]
44 | );
45 |
46 | setAvaibleSubjects(subjects);
47 |
48 | setCourseSelected(course);
49 | setSubjectSelected(subjects[0]);
50 | setDocumentTypeSelected("Frequências");
51 | }
52 | }, [allCoursesStructure]);
53 |
54 | const courseChange = function (e) {
55 | const course = e.target.value;
56 | setCourseSelected(course);
57 |
58 | try {
59 | const years = Object.keys(allCoursesStructure[course][course]);
60 | const year = years[0];
61 |
62 | setYearSelected(year);
63 |
64 | const semestres = Object.keys(allCoursesStructure[course][course][year]);
65 | const semestre = semestres[0];
66 |
67 | setSemestreSelected(semestre);
68 |
69 | const subjects = Object.keys(
70 | allCoursesStructure[course][course][year][semestre]
71 | );
72 | const subject = subjects[0];
73 |
74 | setSubjectSelected(subject);
75 |
76 | setAvaibleYears(years);
77 | setAvaibleSemestres(semestres);
78 | setAvaibleSubjects(subjects);
79 | } catch (err) {
80 | setAvaibleYears([]);
81 | setAvaibleSemestres([]);
82 | setAvaibleSubjects([]);
83 | }
84 | };
85 |
86 | const handleYearChange = (e) => {
87 | const year = e.target.value;
88 |
89 | setYearSelected(year);
90 |
91 | const semestres = Object.keys(
92 | allCoursesStructure[courseSelected][courseSelected][year]
93 | );
94 | const semestre = semestres[0];
95 |
96 | setAvaibleSemestres(semestres);
97 | setSemestreSelected(semestre);
98 |
99 | const subjects = Object.keys(
100 | allCoursesStructure[courseSelected][courseSelected][year][semestre]
101 | );
102 | const subject = subjects[0];
103 |
104 | setAvaibleSubjects(subjects);
105 | setSubjectSelected(subject);
106 | };
107 |
108 | const handleSemestre = (e) => {
109 | const semestre = e.target.value;
110 |
111 | setSemestreSelected(semestre);
112 |
113 | const subjects = Object.keys(
114 | allCoursesStructure[courseSelected][courseSelected][yearSelected][
115 | semestre
116 | ]
117 | );
118 | const subject = subjects[0];
119 |
120 | setAvaibleSubjects(subjects);
121 | setSubjectSelected(subject);
122 | };
123 |
124 | const subjectChange = function (e) {
125 | setSubjectSelected(e.target.value);
126 | };
127 |
128 | const documentTypeChange = function (e) {
129 | setDocumentTypeSelected(e.target.value);
130 | };
131 |
132 | const activeLoader = (id) => {
133 | document.getElementById(
134 | "sending-form-loader-container" + id
135 | ).style.display = "flex";
136 | document.getElementById(
137 | "sending-form-loader-container" + id
138 | ).style.opacity = "1";
139 | };
140 |
141 | const removeLoader = (id) => {
142 | document.getElementById(
143 | "sending-form-loader-container" + id
144 | ).style.opacity = "0";
145 | setTimeout(() => {
146 | document.getElementById(
147 | "sending-form-loader-container" + id
148 | ).style.display = "none";
149 | }, 1505);
150 | };
151 |
152 | const submitForm = async (e, id) => {
153 | e.preventDefault();
154 | setFormState("sending");
155 | activeLoader(id);
156 |
157 | const files = document.getElementById("files" + id).files;
158 |
159 | const actualCourseId = courseSelected.split("#")[0].trim();
160 | const actualSubjectId = subjectSelected.split("#")[0].trim();
161 |
162 | if (files.length !== 0) {
163 | try {
164 | const response = await uploadAssigments(
165 | {
166 | course: actualCourseId,
167 | year: yearSelected,
168 | semestre: semestreSelected,
169 | subject: actualSubjectId,
170 | documentType: documentType,
171 | },
172 | files
173 | );
174 |
175 | if (response) {
176 | setFormState("sended");
177 | setTimeout(() => {
178 | handleDelete(id);
179 | }, 3500);
180 | } else {
181 | setFormState("error");
182 | setTimeout(() => {
183 | removeLoader(id);
184 | }, 3500);
185 | }
186 | } catch (error) {}
187 | }
188 | };
189 |
190 | const deleteIndividualFile = (index) => {
191 | const auxFileList = filesToSubmitList.filter((file, i) => i !== index);
192 | setFilesToSubmitList(auxFileList);
193 | };
194 |
195 | const addFiles = (id) => {
196 | const files = document.getElementById("files" + id).files;
197 | const auxFilesList = [];
198 |
199 | for (
200 | let actualFileIndex = 0;
201 | actualFileIndex < files.length;
202 | actualFileIndex++
203 | ) {
204 | auxFilesList.push(files[actualFileIndex]);
205 | }
206 |
207 | setFilesToSubmitList(filesToSubmitList.concat(auxFilesList));
208 | };
209 |
210 | useEffect(() => {
211 | setInitialStateOfForm();
212 | }, [setInitialStateOfForm]);
213 |
214 | return (
215 |
378 | );
379 | };
380 |
381 | export default Form;
382 |
--------------------------------------------------------------------------------
/src/css/index.css:
--------------------------------------------------------------------------------
1 | * {
2 | margin: 0;
3 | padding: 0;
4 | box-sizing: border-box;
5 | font-family: Rubik;
6 | }
7 |
8 | html {
9 | font-size: 62.5%;
10 | }
11 |
12 | body {
13 | overflow-x: hidden;
14 | display: grid;
15 | place-items: center;
16 | padding: 0;
17 | margin: 0;
18 | background-color: #FBFBFB;
19 | }
20 |
21 | @font-face {
22 | font-family: Rubik;
23 | src: url('../font/Rubik-Regular.ttf');
24 | }
25 |
26 | #root {
27 | width: 95%;
28 | display: flex;
29 | flex-direction: column;
30 | position: relative;
31 | min-height: 100vh;
32 | }
33 |
34 | .main-app-container {
35 | min-height: 100vh;
36 | }
37 |
38 | .header {
39 | display: flex;
40 | justify-content: space-between;
41 | padding: 5rem 0;
42 | position: relative;
43 | }
44 |
45 | .header-content {
46 | display: flex;
47 | flex-direction: column;
48 | }
49 |
50 | .header-title {
51 | font-size: 3.2rem;
52 | font-weight: bold;
53 | display: inline-block;
54 | }
55 |
56 | .header-motivation-text {
57 | font-size: 1.4rem;
58 | color: #333;
59 | margin-top: 0.5rem;
60 | }
61 |
62 | .menu-container {
63 | position: relative;
64 | display: flex;
65 | align-items: center;
66 | }
67 |
68 | .menu-link {
69 | text-decoration: none;
70 | color: #333;
71 | font-size: 1.8rem;
72 | padding: 1rem 2rem;
73 | margin-left: 1rem;
74 | border-radius: 0.5rem;
75 | position: relative;
76 | border: none;
77 | cursor: pointer;
78 | width: 100%;
79 | text-align: left;
80 | }
81 |
82 | .dropbtn {
83 | background-color: #04AA6D;
84 | color: white;
85 | width: 5.5rem;
86 | height: 5.5rem;
87 | border: none;
88 | border-radius: 50%;
89 | overflow: hidden;
90 | }
91 |
92 | .user-profile-picture {
93 | width: 6.4rem;
94 | height: 6.4rem;
95 | border-radius: 50%;
96 | overflow: hidden;
97 | }
98 |
99 | /* The container - needed to position the dropdown content */
100 |
101 | .dropdown {
102 | position: relative;
103 | display: inline-block;
104 | margin-left: 1rem;
105 | }
106 |
107 | /* Dropdown Content (Hidden by Default) */
108 |
109 | .dropdown-content {
110 | display: none;
111 | position: absolute;
112 | background-color: #f1f1f1;
113 | min-width: 160px;
114 | box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2);
115 | z-index: 1;
116 | right: 0;
117 | padding-top: 1rem;
118 | }
119 |
120 | /* Links inside the dropdown */
121 |
122 | .dropdown-content a {
123 | color: black;
124 | padding: 12px 16px;
125 | text-decoration: none;
126 | display: block;
127 | }
128 |
129 | /* Change color of dropdown links on hover */
130 |
131 | .dropdown-content a:hover {
132 | background-color: #ddd;
133 | }
134 |
135 | /* Show the dropdown menu on hover */
136 |
137 | .dropdown:hover .dropdown-content {
138 | display: block;
139 | }
140 |
141 | /* Change the background color of the dropdown button when the dropdown content is shown */
142 |
143 | .dropdown-content .menu-link {
144 | margin: 0;
145 | }
146 |
147 | .notification-container {
148 | padding: 1rem;
149 | }
150 |
151 | .notification-item {
152 | padding: 1rem 2rem;
153 | border-radius: 0.5rem;
154 | margin-bottom: 0.5rem;
155 | cursor: pointer;
156 | position: relative;
157 | overflow: hidden;
158 | border: 0.1rem solid #0003;
159 | }
160 |
161 | .unreaded-notification::after, .readed-notification::after {
162 | content: '';
163 | position: absolute;
164 | width: 2%;
165 | height: 100%;
166 | background-color: #00A7CC;
167 | left: 0;
168 | top: 0;
169 | }
170 |
171 | .readed-notification::after {
172 | background-color: #F55151;
173 | }
174 |
175 | .notification-item:hover {
176 | background-color: #00000015;
177 | }
178 |
179 | .notification-item:last-child {
180 | margin-bottom: 0;
181 | }
182 |
183 | .notification-type {
184 | font-size: 1.8rem;
185 | margin-bottom: 0.5rem;
186 | }
187 |
188 | .notification-date {
189 | color: #272727;
190 | font-size: 1.4rem;
191 | margin-top: 0.25rem;
192 | }
193 |
194 | .user-notification-container {
195 | min-width: 20.5vw;
196 | max-height: 40vh;
197 | overflow-y: auto;
198 | }
199 |
200 | .back-notification-container {
201 | background-color: rgba(0, 0, 0, 0);
202 | border: 0.1rem solid #00000015;
203 | padding: 1rem 2rem;
204 | margin-bottom: 1rem;
205 | border-radius: 0.5rem;
206 | cursor: pointer;
207 | }
208 |
209 | .back-notification-container:hover {
210 | background-color: rgba(0, 0, 0, 0.219);
211 | }
212 |
213 | .auto-generated-image-container {
214 | position: absolute;
215 | width: 100%;
216 | height: 100%;
217 | top: 0;
218 | left: 0;
219 | display: flex;
220 | justify-content: center;
221 | align-items: center;
222 | background-color: #222;
223 | }
224 |
225 | .auto-generated-image-name {
226 | font-size: 2.2rem;
227 | color: #fff;
228 | }
229 |
230 | .user-profile-quick-menu .user-profile-quick-menu-item:hover .menu-link {
231 | background-color: #000000d0;
232 | color: #fff;
233 | }
234 |
235 | .menu-link:hover {
236 | background-color: rgba(0, 0, 0, 0.178);
237 | }
238 |
239 | .contribute-link, .admin-menu-link {
240 | padding: 1.5rem 5rem;
241 | font-size: 2rem;
242 | background-color: #00B2FF;
243 | color: #fff;
244 | font-weight: medium;
245 | text-align: center;
246 | transition: 0.5s;
247 | top: 0;
248 | }
249 |
250 | .admin-menu-link {
251 | background-color: #222222;
252 | }
253 |
254 | .contribute-link:hover {
255 | background-color: #00B2FF;
256 | top: -10%;
257 | }
258 |
259 | .admin-menu-link:hover {
260 | background-color: #222;
261 | padding-top: 1rem;
262 | padding-bottom: 1rem;
263 | }
264 |
265 | .search-container {
266 | width: 100%;
267 | position: relative;
268 | display: flex;
269 | margin: 2rem 0;
270 | flex-wrap: wrap;
271 | flex-direction: column;
272 | }
273 |
274 | .search-container--top {
275 | display: flex;
276 | flex-wrap: wrap;
277 | }
278 |
279 | .search-container--element {
280 | padding: 1rem 2rem;
281 | margin-right: 2rem;
282 | display: inline-block;
283 | font-size: 2rem;
284 | border: none;
285 | border-radius: 0.5rem;
286 | border: 0.2rem solid #c4c4c42b;
287 | background-color: #c4c4c42b;
288 | margin-right: 1rem;
289 | margin-bottom: 1rem;
290 | }
291 |
292 | .search-container--element:last-child {
293 | margin-right: 12.5rem;
294 | }
295 |
296 | .search-button {
297 | padding: 1rem 4rem;
298 | border: none;
299 | border-radius: 0.5rem;
300 | cursor: pointer;
301 | background-color: #00B2FF;
302 | font-size: 1.8rem;
303 | color: #fff;
304 | }
305 |
306 | .search-button:hover {
307 | background-color: #00B2FF;
308 | }
309 |
310 | .assigment-container {
311 | position: relative;
312 | display: flex;
313 | flex-wrap: wrap;
314 | margin: 5rem 0;
315 | }
316 |
317 | .assigment-item {
318 | width: 25rem;
319 | height: 25.5rem;
320 | border-radius: 0.5rem;
321 | position: relative;
322 | margin-right: 2rem;
323 | margin-bottom: 2rem;
324 | cursor: pointer;
325 | border: 0.1rem solid #0002;
326 | cursor: pointer;
327 | overflow: hidden;
328 | transition: 0.5s;
329 | }
330 |
331 | .assigment-item:hover {
332 | border: 0.1rem solid #00A7CC;
333 | }
334 |
335 | .assigment-item-preview {
336 | position: absolute;
337 | width: 100%;
338 | height: 100%;
339 | border-radius: 0.5rem;
340 | object-fit: cover;
341 | }
342 |
343 | .assigment-item-hover-container {
344 | display: flex;
345 | position: absolute;
346 | bottom: 0;
347 | left: 0;
348 | background: rgba(24, 24, 24, 0.233);
349 | width: 100%;
350 | height: 15%;
351 | justify-content: flex-end;
352 | align-items: center;
353 | border-radius: 0.5rem;
354 | opacity: 0;
355 | transition: 0.5s;
356 | }
357 |
358 | .assigment-item:hover .assigment-item-hover-container {
359 | opacity: 1;
360 | }
361 |
362 | .hover-container-menu {
363 | list-style: none;
364 | position: relative;
365 | display: flex;
366 | align-items: center;
367 | justify-content: flex-end;
368 | border-radius: 0.5rem;
369 | }
370 |
371 | .hover-container-menu--item {
372 | width: 3.2rem;
373 | height: 3.2rem;
374 | border-radius: 0.5rem;
375 | background-color: #fff;
376 | display: flex;
377 | justify-content: center;
378 | align-items: center;
379 | cursor: pointer;
380 | transition: 0.5s;
381 | margin-right: 0.5rem;
382 | }
383 |
384 | .hover-container-menu--item:hover {
385 | background-color: #dadada;
386 | }
387 |
388 | .hover-container-menu--item-img {
389 | width: 3.0rem;
390 | height: 3.0rem;
391 | }
392 |
393 | .loading-container {
394 | position: absolute;
395 | display: flex;
396 | justify-content: center;
397 | align-items: center;
398 | top: 0;
399 | left: 0;
400 | background-color: #fff;
401 | z-index: 1;
402 | width: 100%;
403 | height: 100%;
404 | border-radius: 0.5rem;
405 | }
406 |
407 | .loading-element {
408 | animation: loading-animation 1s infinite linear;
409 | }
410 |
411 | @keyframes loading-animation {
412 | 0% {
413 | transform: rotate(0deg);
414 | }
415 |
416 | 100% {
417 | transform: rotate(360deg);
418 | }
419 | }
420 |
421 | .contribute-container {
422 | display: flex;
423 | flex-direction: column;
424 | position: relative;
425 | }
426 |
427 | .contributers-ilustration {
428 | position: absolute;
429 | right: 0;
430 | top: 0;
431 | width: 20vw;
432 | z-index: -1;
433 | }
434 |
435 | .create-new-form {
436 | padding: 1rem 2rem;
437 | background: #00B2FF;
438 | font-size: 2rem;
439 | color: white;
440 | display: inline-block;
441 | width: 10%;
442 | border: none;
443 | border-radius: 0.5rem;
444 | margin: 2rem 0;
445 | cursor: pointer;
446 | }
447 |
448 | .create-new-form:hover {
449 | background-color: #00b3ffbb;
450 | }
451 |
452 | .contribute-form-container {
453 | display: flex;
454 | flex-wrap: wrap;
455 | }
456 |
457 | .form-container {
458 | width: 35%;
459 | padding: 2rem 2rem;
460 | border: 0.1rem solid rgba(0, 0, 0, 0.198);
461 | border-radius: 0.5rem;
462 | position: relative;
463 | margin: 2rem 4rem 2rem 0;
464 | }
465 |
466 | .form-container .form-row-line {
467 | display: flex;
468 | flex-direction: column;
469 | }
470 |
471 | .config-form {
472 | width: 25%;
473 | }
474 |
475 | .delete-button {
476 | background-color: #f55151c7;
477 | color: white;
478 | margin-bottom: 2rem;
479 | display: inline-block;
480 | }
481 |
482 | .subject-form-name {
483 | margin-bottom: 2rem;
484 | }
485 |
486 | .delete-button:hover {
487 | background-color: #f55151;
488 | }
489 |
490 | .input-field-text {
491 | padding: 1rem 2rem;
492 | margin-bottom: 1rem;
493 | border: none;
494 | border-radius: 0.5rem;
495 | width: 100%;
496 | font-size: 1.4rem;
497 | color: rgb(34, 34, 34);
498 | border: 0.1rem solid #5e5d5d;
499 | }
500 |
501 | .photo-container {
502 | display: flex;
503 | align-items: center;
504 | flex-wrap: wrap;
505 | height: 12.5rem;
506 | width: 100%;
507 | margin-top: 1rem;
508 | border-radius: 0.5rem;
509 | border: 0.1rem solid #5e5d5d;
510 | position: relative;
511 | overflow-x: auto;
512 | padding: 0 1rem;
513 | }
514 |
515 | .individual-file-container {
516 | width: 10.5rem;
517 | height: 90%;
518 | border-radius: 0.5rem;
519 | position: relative;
520 | border: 0.1rem solid #5e5d5d;
521 | margin: 1rem 1rem 1rem 0;
522 | display: inline-block;
523 | }
524 |
525 | .file-preview-img {
526 | position: absolute;
527 | width: 100%;
528 | height: 100%;
529 | top: 0;
530 | left: 0;
531 | object-fit: cover;
532 | }
533 |
534 | .btn-standard {
535 | padding: 1rem 2rem;
536 | border: none;
537 | border-radius: 0.5rem;
538 | font-size: 1.4rem;
539 | font-weight: bold;
540 | text-align: center;
541 | cursor: pointer;
542 | }
543 |
544 | .individual-file-container-delete-button {
545 | padding: 1rem;
546 | border: none;
547 | border-radius: 50%;
548 | font-size: 1.2rem;
549 | background-color: #f55151c7;
550 | font-weight: bold;
551 | color: white;
552 | text-align: center;
553 | position: absolute;
554 | right: -10%;
555 | top: -10%;
556 | cursor: pointer;
557 | }
558 |
559 | .files-input {
560 | position: absolute;
561 | top: 0;
562 | left: 0;
563 | width: 100%;
564 | height: 100%;
565 | opacity: 0;
566 | cursor: pointer;
567 | }
568 |
569 | .add-more-content {
570 | position: relative;
571 | cursor: pointer;
572 | margin: auto 0;
573 | background-color: #333;
574 | color: white;
575 | font-size: 1.4rem;
576 | margin-bottom: 1rem;
577 | display: block;
578 | }
579 |
580 | .send-form-submission {
581 | background-color: #00B2FF;
582 | color: white;
583 | margin-top: 2rem;
584 | }
585 |
586 | .sending-form-loader-container {
587 | display: flex;
588 | flex-direction: column;
589 | align-items: center;
590 | justify-content: center;
591 | position: absolute;
592 | top: 0;
593 | left: 0;
594 | width: 100%;
595 | height: 100%;
596 | border-radius: 0.5rem;
597 | background-color: rgba(255, 255, 255, 0.822);
598 | opacity: 0;
599 | display: none;
600 | transition: 0.5s;
601 | }
602 |
603 | .loader-spinner {
604 | width: 12.8rem;
605 | height: 12.8rem;
606 | border-radius: 50%;
607 | border: 0.5rem solid #00b3ff5e;
608 | border-top: 0.5rem solid #00B2FF;
609 | animation: loader-spinner-animation 1s infinite linear;
610 | }
611 |
612 | .loader-spinner-text {
613 | margin-top: 1rem;
614 | font-size: 1.6rem;
615 | color: #5e5d5d;
616 | text-align: center;
617 | }
618 |
619 | @keyframes loader-spinner-animation {
620 | 0% {
621 | transform: rotate(0);
622 | }
623 |
624 | 100% {
625 | transform: rotate(360deg);
626 | }
627 | }
628 |
629 | .error-signal-container {
630 | position: relative;
631 | width: 12.8rem;
632 | height: 12.8rem;
633 | }
634 |
635 | .error-signal-container::after {
636 | content: '';
637 | position: absolute;
638 | height: 100%;
639 | width: 0.5rem;
640 | background-color: #F55151;
641 | top: 0;
642 | left: 50%;
643 | transform: translateX(-50%) rotate(45deg);
644 | border-radius: 2rem;
645 | }
646 |
647 | .error-signal-container::before {
648 | content: '';
649 | position: absolute;
650 | height: 100%;
651 | width: 0.5rem;
652 | background-color: #F55151;
653 | top: 0;
654 | left: 50%;
655 | transform: translateX(-50%) rotate(-45deg);
656 | border-radius: 2rem;
657 | }
658 |
659 | .contributor-photo-container {
660 | width: 20rem;
661 | height: 20rem;
662 | position: relative;
663 | border-radius: 1rem;
664 | overflow: hidden;
665 | margin-right: 2rem;
666 | margin-top: 2rem;
667 | }
668 |
669 | .contributor-username {
670 | font-size: 2rem;
671 | text-align: center;
672 | }
673 |
674 | .contributors-container {
675 | display: flex;
676 | flex-wrap: wrap;
677 | }
678 |
679 | .desc-text {
680 | font-size: 2.4rem;
681 | color: #222;
682 | margin: 1rem 0;
683 | }
684 |
685 | .error-text {
686 | color: #f55151;
687 | }
688 |
689 | .sended-signal-container {
690 | width: 12.8rem;
691 | height: 12.8rem;
692 | position: relative;
693 | }
694 |
695 | .sended-signal-container::after {
696 | content: '';
697 | position: absolute;
698 | width: 1rem;
699 | height: 50%;
700 | background-color: #00cc33;
701 | bottom: 0;
702 | left: 20%;
703 | transform: rotate(-45deg);
704 | border-radius: 2rem;
705 | }
706 |
707 | .sended-signal-container::before {
708 | content: '';
709 | position: absolute;
710 | width: 1rem;
711 | height: 121%;
712 | right: 18%;
713 | top: -10%;
714 | background-color: #00cc33;
715 | transform: rotate(45deg);
716 | border-radius: 2rem;
717 | }
718 |
719 | .menu-button {
720 | position: relative;
721 | display: inline-block;
722 | transition: 0.5s;
723 | }
724 |
725 | .close-menu {
726 | position: relative;
727 | margin-left: 88%;
728 | }
729 |
730 | .open-menu {
731 | width: 6.4rem;
732 | height: 6.4rem;
733 | position: absolute;
734 | top: 5%;
735 | right: 5%;
736 | }
737 |
738 | .h1-text-title {
739 | margin-top: 50vh;
740 | color: #666666;
741 | text-align: center;
742 | font-size: 2.5rem;
743 | }
744 |
745 | .p-text-title {
746 | color: #5e5d5d;
747 | font-size: 1.4rem;
748 | text-align: center;
749 | margin-bottom: 20vh;
750 | }
751 |
752 | .span-text-title {
753 | color: #00A7CC;
754 | }
755 |
756 | .blue-rectangule {
757 | background-color: #00A7CC;
758 | width: 10vw;
759 | height: 1vh;
760 | margin-right: 80vw;
761 | }
762 |
763 | .link-github {
764 | text-decoration: none;
765 | color: #666666;
766 | font-size: 1.4rem;
767 | margin-top: 2vh;
768 | margin-right: 86.5vw;
769 | font-weight: bold;
770 | }
771 |
772 | .link-github:hover {
773 | color: black;
774 | }
775 |
776 | .main-menu {
777 | background: #fbfbfb;
778 | box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.25);
779 | width: 50%;
780 | height: 100vh;
781 | right: 0;
782 | top: 0;
783 | position: fixed;
784 | transition: 0.5s;
785 | opacity: 0;
786 | }
787 |
788 | .content-table-container {
789 | position: relative;
790 | width: 90%;
791 | height: 80%;
792 | left: 50%;
793 | transform: translate(-50%, 0);
794 | display: flex;
795 | flex-direction: column;
796 | overflow-y: auto;
797 | }
798 |
799 | .details {
800 | width: 100%;
801 | }
802 |
803 | .details-summary {
804 | border-radius: 0.5rem;
805 | padding: 1rem;
806 | box-sizing: border-box;
807 | background-color: rgb(196, 196, 196, 0.17);
808 | font-size: 1.4rem;
809 | font-family: Rubik, Verdana, Geneva, Tahoma, sans-serif;
810 | margin: 1rem 0;
811 | }
812 |
813 | .content-list {
814 | list-style: none;
815 | }
816 |
817 | .content-list--item {
818 | position: relative;
819 | border-radius: 0.5rem;
820 | margin: 1rem 0;
821 | cursor: pointer;
822 | }
823 |
824 | .download-link-of-material {
825 | text-decoration: none;
826 | font-size: 1.4rem;
827 | color: white;
828 | font-weight: bolder;
829 | display: block;
830 | padding: 1rem;
831 | position: relative;
832 | }
833 |
834 | .download-icon {
835 | position: absolute;
836 | right: 5%;
837 | top: 50%;
838 | transform: translateY(-50%);
839 | width: 2rem;
840 | }
841 |
842 | .blue {
843 | background-color: #00B2FF;
844 | }
845 |
846 | .orange {
847 | background-color: #F5A951;
848 | }
849 |
850 | .red {
851 | background-color: #F55151;
852 | }
853 |
854 | @media screen and (max-width: 768px) {
855 | .form-container {
856 | width: 90%;
857 | }
858 | }
859 |
860 | .volume-div {
861 | display: flex;
862 | height: 100px;
863 | align-items: center;
864 | position: relative;
865 | justify-self: center;
866 | margin: 0 auto;
867 | }
868 |
869 | .volume-div .volume-bar {
870 | width: 30px;
871 | height: 3px;
872 | background-color: #00B2FF;
873 | margin: 0 2px;
874 | animation: volume-animation 1s linear infinite;
875 | transition: 0.5s;
876 | }
877 |
878 | .volume-bar:nth-child(1) {
879 | animation-delay: 0.5s;
880 | }
881 |
882 | .volume-bar:nth-child(2) {
883 | animation-delay: 0.6s;
884 | }
885 |
886 | .volume-bar:nth-child(3) {
887 | animation-delay: 0.7s;
888 | }
889 |
890 | .volume-bar:nth-child(4) {
891 | animation-delay: 0.8s;
892 | }
893 |
894 | .volume-bar:nth-child(5) {
895 | animation-delay: 0.9s;
896 | }
897 |
898 | .volume-bar:nth-child(6) {
899 | animation-delay: 1s;
900 | }
901 |
902 | @keyframes volume-animation {
903 | 0% {
904 | height: 3px;
905 | }
906 |
907 | 25% {
908 | height: 100%;
909 | }
910 |
911 | 100% {
912 | height: 3px;
913 | }
914 | }
915 |
916 | #assigment-preview-root {
917 | position: fixed;
918 | top: 0;
919 | left: 0;
920 | width: 100%;
921 | height: 100vh;
922 | background-color: rgba(0, 0, 0, 0.35);
923 | z-index: 2;
924 | display: flex;
925 | justify-content: center;
926 | align-items: center;
927 | }
928 |
929 | .preview-container {
930 | position: relative;
931 | display: flex;
932 | flex-direction: column;
933 | width: 50%;
934 | height: 90vh;
935 | background-color: #fff;
936 | border-radius: 0.5rem;
937 | }
938 |
939 | .top-part-preview {
940 | display: flex;
941 | margin-bottom: 1rem;
942 | justify-content: flex-end;
943 | margin-top: 1rem;
944 | }
945 |
946 | .preview-button {
947 | padding: 1rem 2rem;
948 | border: none;
949 | font-size: 1.4rem;
950 | font-weight: bold;
951 | color: #fff;
952 | cursor: pointer;
953 | border-radius: 0.5rem;
954 | }
955 |
956 | .download-button-preview-container {
957 | background-color: #00A7CCcc;
958 | }
959 |
960 | .download-button-preview-container:hover {
961 | background-color: #00a7cc;
962 | }
963 |
964 | .close-button-preview-container {
965 | background-color: #f55151cc;
966 | margin: 0 1rem;
967 | }
968 |
969 | .close-button-preview-container:hover {
970 | background-color: #f55151;
971 | }
972 |
973 | .preview-container--main-child {
974 | display: flex;
975 | position: relative;
976 | align-items: center;
977 | justify-content: space-between;
978 | height: 90%;
979 | }
980 |
981 | .preview-assigment-img {
982 | width: 85%;
983 | height: 90%;
984 | object-fit: cover;
985 | }
986 |
987 | .control-button {
988 | padding: 2rem;
989 | font-size: 1.8rem;
990 | background-color: #3338;
991 | color: #fff;
992 | cursor: pointer;
993 | border: none;
994 | }
995 |
996 | .control-button:hover {
997 | background-color: #333f;
998 | }
999 |
1000 | .prev-assigment {
1001 | margin-left: 1rem;
1002 | }
1003 |
1004 | .next-assigment {
1005 | margin-right: 1rem;
1006 | }
1007 |
1008 | .preview-container-counter {
1009 | text-align: center;
1010 | font-size: 1.6rem;
1011 | align-self: center;
1012 | }
1013 |
1014 | .invisible-link {
1015 | text-decoration: none;
1016 | }
1017 |
1018 | .no-assigments-text {
1019 | font-size: 2.4rem;
1020 | color: #333;
1021 | text-align: center;
1022 | margin: 10rem auto;
1023 | letter-spacing: 1rem;
1024 | }
1025 |
1026 | .login-container {
1027 | width: '100%';
1028 | display: 'flex';
1029 | justify-content: 'center';
1030 | align-items: 'center';
1031 | }
1032 |
1033 | .photo-container-signup {
1034 | width: 14.2rem;
1035 | height: 14.2rem;
1036 | border-radius: 0.5rem;
1037 | border: 0.1rem solid #333;
1038 | position: relative;
1039 | left: 50%;
1040 | transform: translateX(-50%);
1041 | cursor: pointer;
1042 | }
1043 |
1044 | .photo-container-signup .change-image-container {
1045 | border-radius: 0 0 0.5rem 0.5rem;
1046 | }
1047 |
1048 | .photo-container-signup:hover .change-image-container {
1049 | display: flex;
1050 | }
1051 |
1052 | .quant-notifications {
1053 | padding: 0.2rem 1rem;
1054 | margin-left: 1rem;
1055 | background-color: red;
1056 | border-radius: 0.5rem;
1057 | color: #fff;
1058 | }
1059 |
1060 | .user-profile-picture {
1061 | position: absolute;
1062 | width: 100%;
1063 | height: 100%;
1064 | border-radius: 0.5rem;
1065 | object-fit: cover;
1066 | top: 0;
1067 | left: 0;
1068 | }
1069 |
1070 | #profile-picture-input {
1071 | opacity: 0;
1072 | position: absolute;
1073 | left: 0;
1074 | top: 0;
1075 | width: 100%;
1076 | height: 100%;
1077 | }
1078 |
1079 | .user-profile-main-container {
1080 | width: 100%;
1081 | display: flex;
1082 | flex-direction: column;
1083 | }
1084 |
1085 | .user-profile-header-container {
1086 | display: flex;
1087 | }
1088 |
1089 | .image-container {
1090 | width: 20rem;
1091 | height: 20rem;
1092 | border: 0.1rem solid #0000002d;
1093 | position: relative;
1094 | border-radius: 1rem;
1095 | margin-right: 2rem;
1096 | position: relative;
1097 | cursor: pointer;
1098 | overflow: hidden;
1099 | }
1100 |
1101 | .user-image {
1102 | position: absolute;
1103 | top: 0;
1104 | left: 0;
1105 | width: 100%;
1106 | height: 100%;
1107 | object-fit: cover;
1108 | border-radius: 1rem;
1109 | }
1110 |
1111 | .change-image-container {
1112 | position: absolute;
1113 | display: flex;
1114 | align-items: center;
1115 | justify-content: center;
1116 | background-color: #00000086;
1117 | bottom: 0;
1118 | left: 0;
1119 | border-radius: 0 0 1rem 1rem;
1120 | width: 100%;
1121 | padding: 1rem 0;
1122 | display: none;
1123 | }
1124 |
1125 | .change-image-text {
1126 | font-size: 1.4rem;
1127 | color: #fff;
1128 | }
1129 |
1130 | .image-container:hover .change-image-container {
1131 | display: flex;
1132 | }
1133 |
1134 | .user-important-info {
1135 | font-size: 3.2rem;
1136 | margin-bottom: 1rem;
1137 | }
1138 |
1139 | .user-other-info {
1140 | font-size: 1.6rem;
1141 | color: #333;
1142 | }
1143 |
1144 | .user-profile-menu {
1145 | display: flex;
1146 | margin: 5rem 0;
1147 | list-style: none
1148 | }
1149 |
1150 | .user-profile-menu-item, .control-panel-options-menu-item {
1151 | position: relative;
1152 | font-size: 1.8rem;
1153 | color: #222;
1154 | padding: 1rem 2rem;
1155 | margin-right: 4rem;
1156 | cursor: pointer;
1157 | text-decoration: none;
1158 | }
1159 |
1160 | .user-profile-menu-item:hover:after, .control-panel-options-menu-item:hover:after {
1161 | background-color: #00A7CC;
1162 | border: 0.1rem solid #00A7CC;
1163 | }
1164 |
1165 | .user-profile-menu-item::after, .control-panel-options-menu-item::after {
1166 | content: '';
1167 | position: absolute;
1168 | bottom: -0.5rem;
1169 | left: 0;
1170 | padding: 0.2rem 0;
1171 | width: 100%;
1172 | border-radius: 2rem;
1173 | border: 0.1rem solid #222;
1174 | }
1175 |
1176 | .admin-main-container {
1177 | display: flex;
1178 | flex-direction: column;
1179 | width: 100%;
1180 | }
1181 |
1182 | .top-greeting-header {
1183 | display: block;
1184 | align-items: flex-end;
1185 | margin-bottom: 5rem;
1186 | }
1187 |
1188 | .top-greeting-header-sub-text {
1189 | font-size: 1.8rem;
1190 | }
1191 |
1192 | .top-greeting-header .user-important-info {
1193 | display: inline-block;
1194 | margin-bottom: 0;
1195 | margin-left: 1rem;
1196 | }
1197 |
1198 | .main-container {
1199 | display: flex;
1200 | justify-content: space-between;
1201 | width: 95vw;
1202 | }
1203 |
1204 | .main-control-panel-container {
1205 | padding-right: 2rem;
1206 | position: relative;
1207 | display: flex;
1208 | justify-content: center;
1209 | width: 10%;
1210 | }
1211 |
1212 | .main-control-panel-container::after {
1213 | content: '';
1214 | position: absolute;
1215 | top: 0;
1216 | right: 0;
1217 | border-radius: 1rem;
1218 | width: 0.5rem;
1219 | height: 100%;
1220 | background-color: rgba(24, 24, 24, 0.233);
1221 | }
1222 |
1223 | .control-panel-options-menu {
1224 | display: flex;
1225 | flex-direction: column;
1226 | align-items: center;
1227 | list-style-type: none;
1228 | }
1229 |
1230 | .control-panel-options-menu-item {
1231 | text-align: center;
1232 | margin: 2rem 0;
1233 | }
1234 |
1235 | .submission-controller-main-container {
1236 | display: flex;
1237 | flex-direction: column;
1238 | margin-left: 5rem;
1239 | width: 85vw;
1240 | }
1241 |
1242 | .top-container {
1243 | margin-bottom: 2.5rem;
1244 | display: flex;
1245 | flex-direction: column;
1246 | }
1247 |
1248 | #submission-search-input {
1249 | padding: 1rem;
1250 | font-size: 1.4rem;
1251 | color: #444;
1252 | border-radius: 0.5rem;
1253 | border: 1px solid #333;
1254 | margin-bottom: 2.5rem;
1255 | width: 50%;
1256 | }
1257 |
1258 | .submissions-types-menu-container {
1259 | display: flex;
1260 | }
1261 |
1262 | .submission-type-item {
1263 | padding: 1rem;
1264 | border-radius: 0.5rem;
1265 | border: 0.1rem solid #222;
1266 | color: #222;
1267 | font-size: 1.4rem;
1268 | margin-right: 2rem;
1269 | text-decoration: none;
1270 | }
1271 |
1272 | .submissions-container {
1273 | display: flex;
1274 | flex-wrap: wrap;
1275 | }
1276 |
1277 | .submission-form-model {
1278 | margin: 2rem 2rem 1rem 0;
1279 | padding: 1rem;
1280 | border-radius: 0.5rem;
1281 | border: 0.1rem solid #333;
1282 | width: 30%;
1283 | }
1284 |
1285 | .normal-label-layout {
1286 | margin-bottom: 0.5rem;
1287 | font-size: 1.4rem;
1288 | color: #444;
1289 | }
1290 |
1291 | .submission-status {
1292 | margin-left: 0.5rem;
1293 | padding: 0.5rem 1rem;
1294 | border-radius: 0.5rem;
1295 | font-size: 1.4rem;
1296 | color: #fff;
1297 | }
1298 |
1299 | .form-model-top-container-label {
1300 | margin-bottom: 1.5rem;
1301 | }
1302 |
1303 | .bold {
1304 | color: #000;
1305 | font-weight: bold;
1306 | }
1307 |
1308 | .btns-form-model-container {
1309 | margin: 1rem 0 0 0;
1310 | }
1311 |
1312 | .submission-form-model-btn {
1313 | padding: 1rem 1.5rem;
1314 | margin-right: 1rem;
1315 | border: none;
1316 | border-radius: 0.5rem;
1317 | color: #fff;
1318 | background-color: #000;
1319 | font-size: 1.4rem;
1320 | cursor: pointer;
1321 | }
1322 |
1323 | .active-button {
1324 | background-color: #00A8E8;
1325 | }
1326 |
1327 | .active-button:hover {
1328 | background-color: #00A8E8cb;
1329 | }
1330 |
1331 | .reject-button {
1332 | background-color: #222222;
1333 | }
1334 |
1335 | .reject-button:hover {
1336 | background-color: #222222cb;
1337 | }
1338 |
1339 | .course-main-controller-panel {
1340 | display: flex;
1341 | }
1342 |
1343 | .create-button {
1344 | display: inline-block;
1345 | padding: 1rem 2rem;
1346 | background-color: #00a7cce0;
1347 | color: #fff;
1348 | font-weight: bold;
1349 | border: none;
1350 | border-radius: 0.5rem;
1351 | cursor: pointer;
1352 | }
1353 |
1354 | .create-button:hover {
1355 | background-color: #00a7cc;
1356 | }
1357 |
1358 | .container-element-top {
1359 | display: flex;
1360 | align-items: center;
1361 | }
1362 |
1363 | .display-container-items {
1364 | display: flex;
1365 | flex-wrap: wrap;
1366 | }
1367 |
1368 | .management-container-element {
1369 | padding: 1rem;
1370 | border-radius: 0.5rem;
1371 | display: flex;
1372 | flex-direction: column;
1373 | border: 0.1rem solid #22222265;
1374 | width: 20%;
1375 | position: relative;
1376 | margin-top: 1rem;
1377 | margin-right: 1rem;
1378 | }
1379 |
1380 | .management-container-element-top {
1381 | display: flex;
1382 | justify-content: space-between;
1383 | margin-bottom: 1rem;
1384 | }
1385 |
1386 | .management-input-field-text {
1387 | width: 80%;
1388 | padding: 0.5rem 1rem;
1389 | font-size: 1.4rem;
1390 | border: 0.1rem solid #22222265;
1391 | border-radius: 0.5rem;
1392 | }
1393 |
1394 | .management-edit-button {
1395 | background-color: #d87b12;
1396 | color: #fff;
1397 | font-weight: bold;
1398 | border-radius: 0.5rem;
1399 | padding: 1rem;
1400 | border: none;
1401 | cursor: pointer;
1402 | margin-left: 0.5rem;
1403 | }
1404 |
1405 | .management-delete-button-course-element {
1406 | display: inline-block;
1407 | flex: 1;
1408 | align-self: flex-start;
1409 | padding: 1rem;
1410 | border: none;
1411 | background-color: #F55151;
1412 | color: #fff;
1413 | font-weight: bold;
1414 | border-radius: 0.5rem;
1415 | cursor: pointer;
1416 | }
1417 |
1418 | .info-text {
1419 | color: #333;
1420 | font-size: 1.4rem;
1421 | margin-bottom: 1rem;
1422 | }
1423 |
1424 | .meu-button-hamburguer {
1425 | display: none;
1426 | }
1427 |
1428 | .form-login-signup-heading-2 {
1429 | font-size: 4.6rem;
1430 | margin-bottom: 1.5rem;
1431 | color: #222;
1432 | }
1433 |
1434 |
1435 | @media screen and (max-width: 1024px) {
1436 | .header {
1437 | flex-direction: column;
1438 | position: relative;
1439 | }
1440 |
1441 | .header-content {
1442 | position: relative !important;
1443 | }
1444 |
1445 | .menu-container {
1446 | flex-direction: column;
1447 | margin: 0;
1448 | margin-top: 2rem;
1449 | width: 40%;
1450 | }
1451 |
1452 | .menu-link {
1453 | margin: 0;
1454 | margin-top: 1rem;
1455 | }
1456 |
1457 | .search-container--top {
1458 | flex-direction: column;
1459 | }
1460 |
1461 | .create-new-form {
1462 | width: 25%;
1463 | }
1464 |
1465 | .login-signup-form {
1466 | width: 90% !important;
1467 | }
1468 |
1469 | .login-signup-form-img {
1470 | width: 90% !important;
1471 | }
1472 |
1473 | .config-form {
1474 | width: 85%;
1475 | }
1476 |
1477 | .mobile-menu-hamburguer {
1478 | position: absolute;
1479 | right: 0;
1480 | border: none;
1481 | padding: 2rem;
1482 | color: #fff;
1483 | border-radius: 0.5rem;
1484 | background: #00b2ff;
1485 | }
1486 |
1487 | .preview-container {
1488 | width: 90%;
1489 | }
1490 |
1491 | .menu-container {
1492 | align-self: end;
1493 | padding: 1rem;
1494 | border: 0.1rem solid #2224;
1495 | border-radius: 1rem;
1496 | }
1497 | .question-row {
1498 | flex-direction: column;
1499 | }
1500 | .h1--heading {
1501 | font-size: 3.2rem !important;
1502 | }
1503 | .contribute-way-card {
1504 | width: 90% !important;
1505 | margin-top: 2rem !important;
1506 | }
1507 | .about-img {
1508 | width: 100% !important;
1509 | margin-bottom: 2rem !important;
1510 | }
1511 | }
1512 |
1513 | @media screen and (max-width: 768px) {
1514 | .menu-container {
1515 | width: 60%;
1516 | }
1517 | .create-new-form {
1518 | width: 40%;
1519 | }
1520 | }
1521 |
1522 | @media screen and (max-width: 425px) {
1523 | .menu-container {
1524 | width: 90%;
1525 | align-self: center;
1526 | }
1527 | }
--------------------------------------------------------------------------------