├── 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 | 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 |
30 | 35 |
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 | 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 | 34 |
35 | 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 | 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 | 17 | 18 | 19 |
20 | 21 |
22 | 25 | 26 | 27 | 28 | 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 | Error not found 50 | 51 |
52 |

53 | Aqui a sua contribuição e ajuda têm valor{" "} 54 |

55 |
56 | 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 | 67 |
68 |
69 | 70 | { 71 | showCreateForm && 72 | <> 73 |
74 |
75 | { 82 | setCourseName(e.target.value) 83 | }} /> 84 | 85 | 86 |
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 | 67 |
68 |
69 | 70 | { 71 | showCreateForm && 72 | <> 73 |
74 |
75 | { 82 | setSubjectName(e.target.value) 83 | }} /> 84 | 85 | 86 |
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 |
49 |
55 |
56 |
57 | 63 | { 70 | const text = e.target.value; 71 | setUsername(text) 72 | }} 73 | /> 74 |
75 | 76 |
77 | 83 | { 90 | const text = e.target.value; 91 | setEmail(text) 92 | }} 93 | /> 94 |
95 | 96 | 97 |
98 | 104 | 105 | { 107 | setIsInContributors(e.target.checked) 108 | }} /> 109 | 110 |
111 | 112 | 113 |
114 | 115 |
116 |
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 |
54 |

Login

55 | 56 |
57 |
58 | 61 | { 68 | const text = e.target.value; 69 | setEmail(text); 70 | }} 71 | /> 72 |
73 |
74 | 77 | { 84 | const text = e.target.value; 85 | setPassword(text); 86 | }} 87 | /> 88 |
89 | 90 | {sendingLogin == false ? ( 91 | 92 | ) : ( 93 |
94 | Loading... 95 |
96 | )} 97 |
98 | 99 |

100 | Não possui uma conta? Registre-se{" "} 101 |

102 |
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 |
49 |

Cadastro

50 |
51 |
52 | 58 | { 66 | const text = e.target.value; 67 | setUsername(text) 68 | }} 69 | /> 70 |
71 | 72 |
73 | 79 | { 87 | const text = e.target.value; 88 | setEmail(text) 89 | }} 90 | /> 91 |
92 |
93 | 94 | { 102 | const text = e.target.value; 103 | setPassword(text) 104 | }} 105 | /> 106 |
107 | 108 | 109 |
110 | 111 | 112 |

Já possui uma conta? Acesse agora

113 | 114 |
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 | 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 | 115 |
116 | )) 117 | } 118 |
119 | 120 |
121 | {quantFiles} ficheiro(s) {`(${filesTypes.join(',')})`} 122 |
123 | 124 |
125 | { 126 | rest.userInterface !== true && 127 | <> 128 | 138 | 139 | 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 | 94 |
95 |
96 | 97 | {showCreateForm && ( 98 | <> 99 |
100 |
101 |

102 | Curso:{" "} 103 | 116 |

117 | 118 |

119 | Ano:{" "} 120 | 132 |

133 | 134 |

135 | Semestre:{" "} 136 | 148 |

149 | 150 |

151 | Disciplina:{" "} 152 | 164 |

165 | 166 | 171 |
172 |
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 | 149 | 150 | 160 | 161 | 171 | 172 | 185 | 186 | 196 |
197 |
198 | 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 |
{ 221 | submitForm(e, id); 222 | }} 223 | > 224 | { 227 | e.stopPropagation(); 228 | handleDelete(id); 229 | }} 230 | > 231 | Deletar 232 | 233 | 234 |

235 | {subjectSelected.indexOf("#") !== -1 236 | ? subjectSelected.split("#")[1].trim() 237 | : ""} 238 |

239 |
240 | 253 | 254 | 264 | 265 | 275 | 276 | 290 | 291 | 301 |
302 | 303 | { 310 | e.stopPropagation(); 311 | addFiles(id); 312 | }} 313 | multiple={true} 314 | /> 315 | Adicionar Provas 316 | 317 | 318 |
319 | {filesToSubmitList.map((file, index) => ( 320 |
321 | 326 | 327 | 337 |
338 | ))} 339 |
340 | 341 | 344 | 345 |
349 | {formState === "sended" && ( 350 | <> 351 |
352 |

353 | Sua submissão foi enviada 💗 354 |
355 | Nossos administradores a verificarão 356 |

357 | 358 | )} 359 | {formState === "sending" && ( 360 | <> 361 |
362 |

Enviando Submissão...

363 | 364 | )} 365 | 366 | {formState === "error" && ( 367 | <> 368 |
369 |

370 | Algum erro aconteceu 371 |
372 | Tente mais tarde! 373 |

374 | 375 | )} 376 |
377 |
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 | } --------------------------------------------------------------------------------