├── .gitignore
├── README.md
├── package-lock.json
├── package.json
├── public
├── _redirects
├── favicon.ico
├── index.html
├── logo192.png
├── logo512.png
├── manifest.json
└── robots.txt
├── src
├── App.js
├── Components
│ ├── Announcements
│ │ ├── AddAnnouncement
│ │ │ ├── Body.js
│ │ │ └── index.js
│ │ └── AnnouncementsPanel.js
│ ├── Courses
│ │ ├── AddCourses
│ │ │ ├── Body.js
│ │ │ └── index.js
│ │ ├── AllCourses
│ │ │ ├── Body.js
│ │ │ └── index.js
│ │ ├── Context
│ │ │ ├── context.js
│ │ │ ├── index.js
│ │ │ └── withCourse.js
│ │ ├── CourseBar.js
│ │ ├── CoursePage
│ │ │ ├── Assignment
│ │ │ │ ├── AdminSubmission
│ │ │ │ │ ├── Body.js
│ │ │ │ │ └── index.js
│ │ │ │ ├── AssignmentModal.js
│ │ │ │ ├── AssignmentPage.js
│ │ │ │ └── withAssignment.js
│ │ │ ├── Body.js
│ │ │ ├── Curriculum
│ │ │ │ ├── CurriculumModal.js
│ │ │ │ ├── CurriculumPage.js
│ │ │ │ └── withCurriculum.js
│ │ │ └── index.js
│ │ ├── CourseShowcase.js
│ │ └── EditCourses
│ │ │ ├── Body.js
│ │ │ └── index.js
│ ├── Dashboard
│ │ ├── MainPalette
│ │ │ ├── AdminCoursePalette.js
│ │ │ ├── CoursePanel.js
│ │ │ └── StudentCoursePalette.js
│ │ └── index.js
│ ├── Firebase
│ │ ├── context.js
│ │ ├── firebase.js
│ │ └── index.js
│ ├── LandingPage
│ │ └── HomePage.js
│ ├── Login
│ │ ├── LoginForm.js
│ │ └── index.js
│ ├── Navbar
│ │ ├── SideNavigation.js
│ │ └── TopNavBar.js
│ ├── Session
│ │ ├── context.js
│ │ ├── index.js
│ │ ├── withAuthentication.js
│ │ └── withAuthorization.js
│ ├── Signout
│ │ └── SignOutButton.js
│ ├── Signup
│ │ ├── SignupForm.js
│ │ └── index.js
│ ├── Templates
│ │ └── MainTemplate.js
│ └── TextField.js
├── assets
│ ├── 006023-Free-vector_-Macbook-Ipad-and-Iphone-by-Frexy-Dribbble-removebg-preview (1).png
│ ├── logo.svg
│ └── lp.png
├── index.js
└── serviceWorker.js
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # netlify
15 | /.netlify
16 |
17 | # test
18 | /test
19 |
20 | # misc
21 | .DS_Store
22 | .env
23 | .env.local
24 | .env.development.local
25 | .env.test.local
26 | .env.production.local
27 |
28 | npm-debug.log*
29 | yarn-debug.log*
30 | yarn-error.log*
31 |
32 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Demo: https://webcademy.netlify.app/
2 |
3 | Built with Firebase and React.
4 |
5 | Test built with Puppeteer framework and Mocha.
6 |
7 |
8 | ### Note: This is a quick prototype of LMS built with React. Please expect some bugs and most stylings were done inline for quick testing. PR request is accepted!
9 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "eduwebfyp",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@ckeditor/ckeditor5-build-balloon": "^17.0.0",
7 | "@ckeditor/ckeditor5-react": "^2.1.0",
8 | "@react-firebase/auth": "^0.2.10",
9 | "@testing-library/jest-dom": "^4.2.4",
10 | "@testing-library/react": "^9.3.2",
11 | "@testing-library/user-event": "^7.1.2",
12 | "Dante2": "^0.5.0-rc50",
13 | "alloyeditor": "^2.11.5",
14 | "draft-js": "^0.11.4",
15 | "express": "^4.17.1",
16 | "firebase": "^7.8.2",
17 | "mocha": "^7.1.1",
18 | "prism": "^4.1.2",
19 | "prismjs": "^1.19.0",
20 | "puppeteer-core": "^3.0.0",
21 | "react": "^16.12.0",
22 | "react-dom": "^16.12.0",
23 | "react-draft-wysiwyg": "^1.14.4",
24 | "react-html-parser": "^2.0.2",
25 | "react-router-dom": "^5.1.2",
26 | "react-scripts": "3.4.0",
27 | "recompose": "^0.30.0",
28 | "rsuite": "^4.2.1",
29 | "suneditor-react": "^2.6.7"
30 | },
31 | "scripts": {
32 | "test": "mocha --timeout 0 account.test.js",
33 | "start": "react-scripts start",
34 | "build": "react-scripts build",
35 | "eject": "react-scripts eject"
36 | },
37 | "eslintConfig": {
38 | "extends": "react-app"
39 | },
40 | "browserslist": {
41 | "production": [
42 | ">0.2%",
43 | "not dead",
44 | "not op_mini all"
45 | ],
46 | "development": [
47 | "last 1 chrome version",
48 | "last 1 firefox version",
49 | "last 1 safari version"
50 | ]
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/public/_redirects:
--------------------------------------------------------------------------------
1 | /* /index.html 200
2 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cheezeburger/React-Learning-Management-System/4f2fdc4a222a445af1854d0d606d780ce5e92b88/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
9 |
10 |
11 |
15 |
16 |
20 |
21 |
30 | Webcademy
31 |
32 |
33 |
34 |
35 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cheezeburger/React-Learning-Management-System/4f2fdc4a222a445af1854d0d606d780ce5e92b88/public/logo192.png
--------------------------------------------------------------------------------
/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cheezeburger/React-Learning-Management-System/4f2fdc4a222a445af1854d0d606d780ce5e92b88/public/logo512.png
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import 'rsuite/dist/styles/rsuite-default.css';
2 | import 'react-draft-wysiwyg/dist/react-draft-wysiwyg.css';
3 |
4 | import React from 'react';
5 | import { Route, BrowserRouter as Router } from 'react-router-dom';
6 |
7 | import Login from './Components/Login/';
8 | import HomePage from './Components/LandingPage/HomePage.js';
9 | import SignUp from './Components/Signup';
10 | import Dashboard from './Components/Dashboard';
11 |
12 | import { withAuthentication } from './Components/Session';
13 | import AddCourses from './Components/Courses/AddCourses';
14 | import EditCourses from './Components/Courses/EditCourses';
15 | import CoursePage from './Components/Courses/CoursePage';
16 | import CurriculumPage from './Components/Courses/CoursePage/Curriculum/CurriculumPage';
17 | import Announcement from './Components/Announcements/AddAnnouncement';
18 | import AllCourses from './Components/Courses/AllCourses';
19 | import AssignmentPage from './Components/Courses/CoursePage/Assignment/AssignmentPage';
20 | import AdminSubmission from './Components/Courses/CoursePage/Assignment/AdminSubmission';
21 |
22 | const App = () => (
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
40 |
45 |
46 |
47 | );
48 |
49 | export default withAuthentication(App);
50 |
--------------------------------------------------------------------------------
/src/Components/Announcements/AddAnnouncement/Body.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {
3 | Container,
4 | FormGroup,
5 | Form,
6 | FormControl,
7 | ControlLabel,
8 | SelectPicker,
9 | Button,
10 | ButtonToolbar,
11 | Alert,
12 | } from "rsuite";
13 |
14 | class Body extends React.Component{
15 | constructor(props) {
16 | super(props);
17 | this.state = {
18 | showError: false,
19 | errorPlacement: "bottomStart",
20 | formValue: {
21 | courseId: '',
22 | announcement: '',
23 | },
24 | userCreatedCourses: null,
25 |
26 | };
27 | this.formSubmit = this.formSubmit.bind(this);
28 | this.getCreatedCourses = this.getCreatedCourses.bind(this);
29 | }
30 |
31 | formSubmit() {
32 | const { courseId, announcement } = this.state.formValue;
33 |
34 | if (!courseId ||!announcement) {
35 | Alert.error("Please fill in all fields.", 5000);
36 | } else {
37 | this.props.firebase.course(courseId).child('announcements').push(
38 | {
39 | createdOn: Date.now(),
40 | createdBy: this.props.authUser.name,
41 | content: announcement,
42 | },
43 | err => {
44 | if (err) {
45 | Alert.error(
46 | "An error occurred while creating announcement. Please try again.",
47 | 5000
48 | );
49 | }
50 | }
51 | ).then(rsp => {
52 | Alert.success('Announcement created successfully.');
53 | this.props.history.push(`/dashboard`);
54 | })
55 | }
56 | }
57 |
58 | getCreatedCourses(){
59 | let userCreatedCourses = this.props.coursesList;
60 | const uid = this.props.authUser.uid;
61 |
62 | userCreatedCourses = userCreatedCourses.filter(course=> {
63 | return course.createdBy === uid
64 | })
65 |
66 | userCreatedCourses = userCreatedCourses.map(course=> {
67 | return{
68 | label: course.title,
69 | value: course.uid,
70 | };
71 | })
72 |
73 | this.setState({userCreatedCourses});
74 | }
75 |
76 | componentDidMount(){
77 | this.getCreatedCourses();
78 | }
79 | render() {
80 | return (
81 |
82 |
83 |
84 |
85 |
120 |
121 |
122 |
123 | );
124 | }
125 | }
126 |
127 | export default Body;
--------------------------------------------------------------------------------
/src/Components/Announcements/AddAnnouncement/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {compose} from 'recompose';
3 | import mainTemplate from '../../Templates/MainTemplate'
4 | import {withFirebase} from '../../Firebase';
5 | import {withAuthorization} from '../../Session'
6 | import withCourse from '../../Courses/Context/withCourse'
7 | import {NavLink} from 'react-router-dom';
8 | import AnnouncementsPanel from '../../Announcements/AnnouncementsPanel';
9 | import { Breadcrumb } from 'rsuite';
10 | import Body from './Body';
11 |
12 | const Header = (props) => (
13 |
18 | )
19 |
20 | const BreadCrumb = () => (
21 |
22 | Dashboard
23 | Courses
24 | Add New Announcement
25 |
26 | )
27 |
28 | const condition = authUser => authUser && !!(authUser.roles.userRole === 'admin');
29 |
30 | let AddAnnouncement = mainTemplate(Header, BreadCrumb, Body);
31 |
32 | export default compose(
33 | withAuthorization(condition),
34 | withCourse,
35 | withFirebase,
36 | )(AddAnnouncement);
37 |
38 |
--------------------------------------------------------------------------------
/src/Components/Announcements/AnnouncementsPanel.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { Avatar, Badge, Icon, IconButton, Drawer, Tag } from 'rsuite';
3 | import withCourse from '../Courses/Context/withCourse';
4 |
5 | class AnnouncementsPanel extends Component{
6 | constructor(props){
7 | super(props);
8 |
9 | this.state = {
10 | drawerShow: false
11 | };
12 |
13 | this.toggleDrawer = this.toggleDrawer.bind(this);
14 | this.renderAdmin = this.renderAdmin.bind(this);
15 | }
16 |
17 | toggleDrawer(){
18 | this.setState({drawerShow: !this.state.drawerShow});
19 | }
20 |
21 | renderAdmin(){
22 | const uid = this.props.authUser.uid;
23 | let userCourses = !!(this.props.authUser.coursesCreated)? true: false;
24 |
25 | //User created courses
26 | if(userCourses){
27 | userCourses = this.props.coursesList.filter(course => {
28 | return course.createdBy === uid && course.announcements;
29 | })
30 | let announcements = [];
31 |
32 | userCourses.forEach(course => {
33 | const announcementKey = Object.keys(course.announcements);
34 |
35 | announcementKey.forEach(aKey => {
36 | const date = new Date(course.announcements[aKey].createdOn).toLocaleString();
37 | announcements.push({
38 | courseTitle: course.title,
39 | content: course.announcements[aKey].content,
40 | createdOn: date,
41 | createdBy: course.announcements[aKey].createdBy,
42 | })
43 | })
44 | })
45 | announcements = announcements.sort((a, b) => (a.createdOn < b.createdOn) ? 1 : ((b.createdOn < a.createdOn) ? -1 : 0));
46 |
47 | return(
48 |
49 | {announcements.length?
50 | announcements.map((a, i) => {
51 | return(
52 | <>
53 |
{a.courseTitle}
54 |
{a.createdOn} - {a.createdBy? a.createdBy: 'Unknown'}
55 |
{a.content}
56 |
57 | >
58 | )
59 | })
60 | :
No announcements have been created...
61 | }
62 |
63 |
64 | )
65 | }
66 | else{
67 | return(
68 | Nothing to display...
69 | )
70 | }
71 |
72 | }
73 |
74 | renderStudent(){
75 | let enrolledCourses = this.props.authUser.enrolledCourses;
76 | let announcements = [];
77 |
78 | if(enrolledCourses){
79 | enrolledCourses = Object.keys(enrolledCourses).map(key => {
80 | return enrolledCourses[key].courseId;
81 | })
82 |
83 | enrolledCourses = this.props.coursesList.filter(course => {
84 | return enrolledCourses.includes(course.uid.substr(1));
85 | })
86 |
87 | enrolledCourses.forEach(course => {
88 | if(course.announcements){
89 | Object.keys(course.announcements).forEach(aKey => {
90 | const date = new Date(course.announcements[aKey].createdOn).toLocaleString();
91 | announcements.push({
92 | courseTitle: course.title,
93 | content: course.announcements[aKey].content,
94 | createdOn: date,
95 | createdBy: course.announcements[aKey].createdBy,
96 | })
97 | })
98 | }
99 | })
100 |
101 | announcements = announcements.sort((a, b) => (a.createdOn < b.createdOn) ? 1 : ((b.createdOn < a.createdOn) ? -1 : 0));
102 | return(
103 |
104 | {announcements.length?
105 | announcements.map((a, i) => {
106 | return(
107 |
108 |
{a.courseTitle}
109 |
{a.createdOn} - {a.createdBy? a.createdBy: 'Unknown'}
110 |
{a.content}
111 |
112 |
113 | )
114 | })
115 | :
No announcements have been created...
116 | }
117 |
118 |
119 | )
120 | }
121 | }
122 |
123 | render(){
124 | return(
125 |
126 |
127 |
128 | }
131 | onClick= {this.toggleDrawer}
132 | />
133 |
134 |
135 |
136 |
137 |
138 | Announcements
139 |
140 | {
141 | this.props.authUser.roles.userRole === 'admin'?
142 | this.renderAdmin()
143 | : this.renderStudent()
144 |
145 | }
146 |
147 |
148 |
149 |
150 | )
151 | }
152 | }
153 |
154 | export default withCourse(AnnouncementsPanel);
--------------------------------------------------------------------------------
/src/Components/Courses/AddCourses/Body.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import {
3 | Container,
4 | FormGroup,
5 | Form,
6 | Input,
7 | FormControl,
8 | ControlLabel,
9 | SelectPicker,
10 | Button,
11 | ButtonToolbar,
12 | Alert,
13 | Uploader,
14 | Icon
15 | } from "rsuite";
16 |
17 | const qualificationData = [
18 | { label: "Primary School", value: "primary-school" },
19 | { label: "High School", value: "high-school" },
20 | { label: "College", value: "college" },
21 | { label: "Degree", value: "degree" },
22 | { label: "Masters", value: "master" },
23 | { label: "Other", value: "other" }
24 | ];
25 |
26 | const difficultyLevel = [
27 | { label: "Fundamental", value: "fundamental" },
28 | { label: "Intermediate", value: "intermediate" },
29 | { label: "Advanced", value: "advanced" },
30 | { label: "Fundamental-Intermediate", value: "fundamental-intermediate" },
31 | { label: "Fundamental-Advanced", value: "fundamental-advanced" },
32 | { label: "Other", value: "other" }
33 | ];
34 |
35 | const durationAcceptor = ({ onChange }) => {
36 | return (
37 | {
40 | onChange(e);
41 | }}
42 | style={{ width: "90px" }}
43 | />
44 | );
45 | };
46 |
47 | class Body extends React.Component {
48 | constructor(props) {
49 | super(props);
50 | this.state = {
51 | showError: false,
52 | errorPlacement: "bottomStart",
53 | formValue: {
54 | title: "",
55 | qualification: "",
56 | levelOfStudy: "",
57 | duration: 0,
58 | description: "",
59 | prerequisite: ""
60 | },
61 | featureImage: null,
62 | uploading: false,
63 | createCourseBtnShow: false,
64 | };
65 | this.formSubmit = this.formSubmit.bind(this);
66 | }
67 |
68 | formSubmit() {
69 | this.setState({createCourseBtnShow: true});
70 |
71 | const {
72 | title,
73 | qualification,
74 | levelOfStudy,
75 | duration,
76 | description,
77 | prerequisite
78 | } = this.state.formValue;
79 | const uid = this.props.firebase.auth.currentUser.uid;
80 | if (
81 | !title ||
82 | !qualification ||
83 | !levelOfStudy ||
84 | !duration ||
85 | !description ||
86 | !prerequisite ||
87 | !this.state.setFileInfo
88 | ) {
89 | Alert.error("Please fill in all fields.", 5000);
90 | this.setState({createCourseBtnShow: false});
91 | } else {
92 | this.props.firebase
93 | .courses()
94 | .push(
95 | {
96 | createdBy: uid,
97 | title,
98 | qualification,
99 | levelOfStudy,
100 | duration,
101 | description,
102 | prerequisite,
103 | image: this.state.setFileInfo
104 | },
105 | err => {
106 | if (err) {
107 | Alert.error(
108 | "An error occurred while creating course. Please try again.",
109 | 5000
110 | );
111 | this.setState({createCourseBtnShow: false});
112 | }
113 | }
114 | )
115 | .then(rsp => {
116 | const courseId = rsp.key.substr(1, rsp.key.length);
117 | this.props.firebase
118 | .user(uid)
119 | .child("coursesCreated")
120 | .push({ courseId });
121 | this.props.history.push(`/courses/${courseId}`);
122 | })
123 | .catch(err =>{
124 | console.log(err);
125 | })
126 | }
127 | }
128 |
129 | previewFile(file, callback) {
130 | const reader = new FileReader();
131 | reader.onloadend = () => {
132 | callback(reader.result);
133 | };
134 | reader.readAsDataURL(file);
135 | }
136 |
137 | render() {
138 | return (
139 |
140 |
141 |
142 | {
148 | this.setState({ uploading: true });
149 | this.previewFile(file.blobFile, value => {
150 | this.setState({ setFileInfo: value });
151 | });
152 | }}
153 | onError={() => {
154 | // Alert.error("Upload Failed. Please try again.");
155 | }}
156 | >
157 |
169 |
170 |
171 |
172 |
228 |
229 |
230 |
231 | );
232 | }
233 | }
234 |
235 | export default Body;
--------------------------------------------------------------------------------
/src/Components/Courses/AddCourses/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {compose} from 'recompose';
3 | import mainTemplate from '../../Templates/MainTemplate'
4 | import {withFirebase} from '../../Firebase';
5 | import {withAuthorization} from '../../Session'
6 | import {NavLink} from 'react-router-dom';
7 | import AnnouncementsPanel from '../../Announcements/AnnouncementsPanel';
8 | import { Breadcrumb } from 'rsuite';
9 | import Body from './Body';
10 |
11 | const Header = (props) => (
12 |
17 | )
18 |
19 | const BreadCrumb = () => (
20 |
21 | Dashboard
22 | Courses
23 | Add New Course
24 |
25 | )
26 |
27 | const condition = authUser => authUser && !!(authUser.roles['userRole'] === 'admin');
28 |
29 | let AddCoursePage = mainTemplate(Header, BreadCrumb, Body);
30 |
31 | export default compose(
32 | withAuthorization(condition),
33 | withFirebase,
34 | )(AddCoursePage);
35 |
36 |
--------------------------------------------------------------------------------
/src/Components/Courses/AllCourses/Body.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Container } from 'rsuite';
3 | import CourseShowcase from '../CourseShowcase';
4 |
5 | export default class Body extends React.Component{
6 |
7 | render(){
8 | const coursesList = this.props.coursesList;
9 |
10 | return(
11 |
12 | {
13 | coursesList && coursesList.length?
14 | Array.from(coursesList).reverse().map(course => {
15 | return (
16 |
17 | )
18 | })
19 | : No courses found...
20 | }
21 |
22 | )
23 | }
24 | }
--------------------------------------------------------------------------------
/src/Components/Courses/AllCourses/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import AnnouncementsPanel from '../../Announcements/AnnouncementsPanel';
3 | import mainTemplate from '../../Templates/MainTemplate';
4 | import { Breadcrumb } from 'rsuite';
5 | import Body from './Body';
6 | import withCourse from '../Context/withCourse';
7 | import { NavLink } from 'react-router-dom';
8 |
9 | const Header = (props) => (
10 |
15 | )
16 |
17 | const BreadCrumb = (props) => (
18 |
19 | Dashboard
20 | All Courses
21 |
22 | )
23 |
24 | const body = (props) => {
25 | return(
26 |
30 |
31 | )
32 | }
33 |
34 |
35 |
36 | const WrappedComponents = mainTemplate(Header, BreadCrumb, body);
37 |
38 | export default withCourse(WrappedComponents);
39 |
--------------------------------------------------------------------------------
/src/Components/Courses/Context/context.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const CourseContext = React.createContext(null);
4 |
5 | export default CourseContext;
--------------------------------------------------------------------------------
/src/Components/Courses/Context/index.js:
--------------------------------------------------------------------------------
1 | import CourseContext from './context';
2 | import withCourse from './withCourse';
3 |
4 | export default {CourseContext, withCourse};
--------------------------------------------------------------------------------
/src/Components/Courses/Context/withCourse.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { withFirebase } from '../../Firebase';
3 | import { Loader } from 'rsuite';
4 |
5 | const withCourse = Component => {
6 | class WithCourse extends React.Component{
7 | constructor(props){
8 | super(props);
9 |
10 | this.state = {
11 | coursesList: null,
12 | course: null,
13 | loading: true,
14 | }
15 |
16 | this.getCourse = this.getCourse.bind(this);
17 | }
18 |
19 | componentDidMount(){
20 | this.listener = this.props.firebase.onAuthCourseListener((coursesList)=>{
21 | this.setState({ coursesList, loading: false })
22 | },
23 | ()=>{
24 | this.setState({ coursesList: null, loading: false })
25 | }
26 | )
27 | }
28 |
29 | async getCourse(id){
30 | const {coursesList} = this.state;
31 | let foundCourse = null;
32 |
33 | await new Promise(rs => {
34 | for(let i = 0; i< coursesList.length; i++){
35 | if(coursesList.uid === id){
36 | foundCourse = coursesList[i];
37 | rs();
38 | break;
39 | }
40 | }
41 | })
42 | return foundCourse;
43 | }
44 |
45 | render(){
46 | return(
47 | <>
48 | {
49 | this.state.loading?
50 |
51 | :
52 | (
53 | this.state.coursesList?
54 |
55 | :
56 | )
57 | }
58 | >
59 | )
60 | }
61 | }
62 |
63 | return withFirebase(WithCourse);
64 | }
65 |
66 | export default withCourse;
--------------------------------------------------------------------------------
/src/Components/Courses/CourseBar.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { Container, Progress, Button } from 'rsuite';
3 | const { Line } = Progress;
4 |
5 | export default class CourseBar extends Component{
6 | render(){
7 | return(
8 |
11 |
29 |
30 | )
31 | }
32 | }
--------------------------------------------------------------------------------
/src/Components/Courses/CoursePage/Assignment/AdminSubmission/Body.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {
3 | Container,
4 | Panel,
5 | Table,
6 | Button,
7 | Modal,
8 | Form,
9 | FormGroup,
10 | ControlLabel,
11 | FormControl,
12 | InputNumber,
13 | Divider,
14 | Alert
15 | } from 'rsuite';
16 | import ReactHtmlParser from 'react-html-parser';
17 |
18 | export default class Body extends React.Component {
19 | constructor(props) {
20 | super(props);
21 |
22 | this.state = {
23 | show: false,
24 | modalData: {},
25 | formValue: {}
26 | };
27 | }
28 |
29 | reviewAssignment = rowData => {
30 | this.props.firebase
31 | .user(rowData.studentId)
32 | .child('enrolledCourses')
33 | .on('value', snapshot => {
34 | let match = [];
35 | let submissions = [];
36 | let coursesList = this.props.coursesList;
37 |
38 | snapshot.forEach(item => {
39 | let course = item.val();
40 | course.enrolledCourseKey = item.key;
41 |
42 | if (course.submissions) {
43 | submissions.push(course);
44 | }
45 | });
46 |
47 | //Get courseId and submissions of the course
48 | submissions = submissions
49 | .map(item => {
50 | const innerKeys = Object.keys(item.submissions);
51 |
52 | const s = innerKeys.map(submissionKey => {
53 | return {
54 | submissionKey,
55 | data: item.submissions[submissionKey]
56 | };
57 | });
58 |
59 | return {
60 | courseId: item.courseId,
61 | enrolledCourseKey: item.enrolledCourseKey,
62 | submissions: s
63 | };
64 | })
65 | .flat();
66 |
67 | //Get matching course with matching rowData.snapshotKey
68 | match = submissions.filter(item => {
69 | const temp = Object.keys(item.submissions).map(key => {
70 | return item.submissions[key].submissionKey;
71 | });
72 |
73 | return temp.includes(rowData.snapshotKey);
74 | });
75 |
76 | //Get only the submission that match the rowData.snapshotKey
77 | match = match.map(item => {
78 | item.submissions = item.submissions.filter(inner => {
79 | return inner.submissionKey === rowData.snapshotKey;
80 | });
81 |
82 | item.submissions = item.submissions[0];
83 | return item;
84 | });
85 |
86 | let assId = match.map(item => {
87 | return `-${item.submissions.data.assId}`;
88 | })[0];
89 |
90 | //Extracting assignments to obtain original questions
91 | let assignments = coursesList.map(item => {
92 | return item.assignments;
93 | });
94 |
95 | assignments = assignments.filter((course, i) => {
96 | if(course){
97 | const keys = Object.keys(course);
98 | return keys.includes(assId);
99 | }
100 | });
101 |
102 | assignments = assignments[0][assId].questions;
103 |
104 | this.setState({
105 | show: true,
106 | modalData: {
107 | questions: assignments,
108 | submissionData: match[0],
109 | rowData
110 | }
111 | });
112 | });
113 | };
114 |
115 | close = () => {
116 | this.setState({ show: false });
117 | };
118 |
119 | onReviewSubmit = modalData => {
120 | let { marks, comments } = this.state.formValue;
121 | const uid = this.props.firebase.auth.currentUser.uid;
122 | let { rowData, submissionData } = modalData;
123 | marks = marks ? marks : rowData.marks ? rowData.marks : null;
124 |
125 | if (!marks) {
126 | Alert.error('Marks cannot be empty.');
127 | } else {
128 | const date = Date.now();
129 |
130 | comments = comments
131 | ? comments
132 | : rowData.comments
133 | ? rowData.comments
134 | : null;
135 | this.props.firebase
136 | .course(`-${submissionData.courseId}`)
137 | .child('submissions')
138 | .child(rowData.key)
139 | .update({
140 | status: 'reviewed',
141 | comments,
142 | reviewedOn: date,
143 | reviewedBy: uid,
144 | marks: marks
145 | })
146 | .then(() => {
147 | this.props.firebase
148 | .user(rowData.studentId)
149 | .child('enrolledCourses')
150 | .child(submissionData.enrolledCourseKey)
151 | .child('submissions')
152 | .child(submissionData.submissions.submissionKey)
153 | .update({
154 | status: 'reviewed',
155 | reviewedOn: date,
156 | reviewer: this.props.authUser.name,
157 | reviewerId: uid,
158 | comments,
159 | marks
160 | })
161 | .then(() => {
162 | Alert.success('Successfully reviewed.');
163 | this.setState({ show: false });
164 | });
165 | });
166 | }
167 | };
168 |
169 | renderModal = modalData => {
170 | let { submissionData, questions, rowData } = modalData;
171 | let answer = submissionData.submissions.data.answers;
172 |
173 | answer = Object.keys(answer).map(key => {
174 | return answer[key];
175 | });
176 |
177 | let qna = [];
178 |
179 | for (let i = 0; i < questions.length; i++) {
180 | qna.push({
181 | question: questions[i],
182 | answer: answer[i]
183 | });
184 | }
185 |
186 | return (
187 |
188 |
189 | Assignment Review
190 |
191 | Student Submission
192 |
193 |
279 |
280 |
281 | );
282 | };
283 |
284 | render() {
285 | const modalData =
286 | this.state.modalData &&
287 | this.state.modalData.questions &&
288 | this.state.modalData.submissionData
289 | ? this.state.modalData
290 | : null;
291 | let coursesList = this.props.coursesList;
292 | let uid = this.props.authUser ? this.props.authUser.uid : null;
293 | let submissions = [];
294 | let coursesCreated =
295 | this.props.authUser && this.props.authUser.coursesCreated
296 | ? this.props.authUser.coursesCreated
297 | : null;
298 |
299 | if (coursesCreated) {
300 | coursesCreated = Object.keys(coursesCreated).map(key => {
301 | return coursesCreated[key].courseId;
302 | });
303 |
304 | coursesList = coursesList.filter(item => {
305 | return item.createdBy === uid;
306 | });
307 |
308 | submissions = coursesList.map(course => {
309 | return {
310 | title: course.title,
311 | submissions: course.submissions
312 | };
313 | });
314 |
315 | submissions = submissions.map(item => {
316 | if (item.submissions) {
317 | item.submissions = Object.keys(item.submissions).map(
318 | key => {
319 | item.submissions[key].key = key;
320 | return item.submissions[key];
321 | }
322 | );
323 | }
324 |
325 | return item;
326 | });
327 | if (submissions.submissions) {
328 | submissions.submissions.reverse();
329 | }
330 | }
331 |
332 | return (
333 |
335 | {modalData ? this.renderModal(modalData) : null}
336 |
337 | {submissions.map(item => {
338 | return (
339 | <>
340 | {item.title}
341 |
342 |
343 |
344 |
345 | Submitted On
346 |
347 |
348 | {rowData => {
349 | const date = new Date(
350 | rowData.submittedOn
351 | ).toLocaleString();
352 |
353 | return {date}
;
354 | }}
355 |
356 |
357 |
358 |
362 |
363 | Reviewed On
364 |
365 |
366 | {rowData => {
367 | const reviewedOn = rowData
368 | .reviewedOn
369 | ? new Date(
370 | rowData.reviewedOn
371 | ).toLocaleString()
372 | : null;
373 |
374 | return rowData
375 | .reviewedOn ? (
376 | {reviewedOn}
377 | ) : (
378 |
382 | -
383 |
384 | );
385 | }}
386 |
387 |
388 |
389 |
390 |
391 | Student Name
392 |
393 |
397 |
398 |
399 |
400 |
401 | Status
402 |
403 |
404 | {rowData => {
405 | return rowData.status ===
406 | 'new' ? (
407 |
408 | Pending Review
409 |
410 | ) : (
411 |
416 | Reviewed
417 |
418 | );
419 | }}
420 |
421 |
422 |
423 |
427 |
428 | Action
429 |
430 |
431 | {rowData => {
432 | return (
433 |
442 | );
443 | }}
444 |
445 |
446 |
447 |
448 | >
449 | );
450 | })}
451 |
452 | );
453 | }
454 | }
455 |
--------------------------------------------------------------------------------
/src/Components/Courses/CoursePage/Assignment/AdminSubmission/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import AnnouncementsPanel from '../../../../Announcements/AnnouncementsPanel';
3 | import mainTemplate from '../../../../Templates/MainTemplate';
4 | import { Breadcrumb } from 'rsuite';
5 | import Body from './Body';
6 | import withCourse from '../../../Context/withCourse';
7 | import { NavLink } from 'react-router-dom';
8 | import { withAuthorization } from '../../../../Session';
9 | import { compose } from 'recompose';
10 |
11 | const Header = (props) => (
12 |
17 | )
18 |
19 | const BreadCrumb = (props) => (
20 |
21 | Dashboard
22 | Student Submissions
23 |
24 | )
25 |
26 | const body = (props) => {
27 | return(
28 |
32 |
33 | )
34 | }
35 |
36 |
37 |
38 | const WrappedComponents = mainTemplate(Header, BreadCrumb, body);
39 | const condition = authUser => authUser && !!(authUser.roles.userRole === 'admin');
40 |
41 | export default compose(withCourse, withAuthorization(condition))(WrappedComponents);
42 |
--------------------------------------------------------------------------------
/src/Components/Courses/CoursePage/Assignment/AssignmentModal.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {
3 | Modal,
4 | Container,
5 | Uploader,
6 | Form,
7 | FormGroup,
8 | FormControl,
9 | ControlLabel,
10 | SelectPicker,
11 | Button,
12 | ButtonToolbar,
13 | Alert,
14 | Progress
15 | } from 'rsuite';
16 |
17 | const { Line } = Progress;
18 |
19 | export default class AssignmentModal extends React.Component {
20 | constructor(props) {
21 | super(props);
22 | this.state = {
23 | curriculum: [],
24 | formValue: {
25 | title: '',
26 | curriculum: '',
27 | type: '',
28 | uploaded: false
29 | },
30 | assFile: null,
31 | uploaderDisabled: false,
32 | uploadProgressShow: false,
33 | uploadProgress: 0
34 | };
35 |
36 | this.onUploadChange = this.onUploadChange.bind(this);
37 | this.getCurriculums = this.getCurriculums.bind(this);
38 | this.formSubmit = this.formSubmit.bind(this);
39 | this.formNext = this.formNext.bind(this);
40 | }
41 |
42 | getCurriculums() {
43 | //Get curriculums
44 | this.props.firebase
45 | .courseCurriculums('-' + this.props.match.params.id)
46 | .once('value')
47 | .then(snapshot => {
48 | const curriculums = snapshot.val();
49 |
50 | if (curriculums) {
51 | const objKeys = Object.keys(curriculums);
52 | let curriculum = [];
53 |
54 | objKeys.forEach(item => {
55 | const obj = {
56 | value: item,
57 | label: curriculums[item].title.trim()
58 | };
59 | curriculum.push(obj);
60 | this.setState({ curriculum });
61 | });
62 | }
63 | });
64 | }
65 |
66 | componentDidMount() {
67 | this.getCurriculums();
68 | }
69 |
70 | onUploadChange(file) {
71 | if (this.state.assFile) {
72 | this.setState({
73 | assFile: null
74 | });
75 | } else {
76 | this.setState({
77 | assFile: file
78 | });
79 | }
80 | }
81 |
82 | formSubmit() {
83 | const { title, curriculum, type } = this.state.formValue;
84 | const courseId = '-' + this.props.match.params.id;
85 | const { assFile } = this.state;
86 |
87 | const timestamp = Date.now();
88 | if (!title || !assFile || !curriculum) {
89 | Alert.error('Please fill in all fields.');
90 | } else {
91 | const upload = this.props.firebase
92 | .assignmentRef()
93 | .child(
94 | `${this.props.courseTitle}/${timestamp}_${assFile[0].blobFile.name}`
95 | )
96 | .put(assFile[0].blobFile);
97 |
98 | upload.on(
99 | 'state_changed',
100 | snapshot => {
101 | let uploadProgress =
102 | (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
103 | this.setState({ uploadProgressShow: true, uploadProgress });
104 | },
105 | () => {
106 | Alert.error('Upload failed...Please try again...');
107 | },
108 |
109 | async () => {
110 | //Uploaded Successfully...
111 | Alert.success('Created successfully');
112 | this.setState({ uploaded: true });
113 | const url = await upload.snapshot.ref
114 | .getDownloadURL()
115 | .then(downloadURL => {
116 | return downloadURL;
117 | });
118 |
119 | //Store to realtime db
120 | this.props.firebase
121 | .course(courseId)
122 | .child('assignments')
123 | .push(
124 | {
125 | curriId: curriculum.substr(1),
126 | title,
127 | type,
128 | attachmentUrl: url
129 | },
130 | err => {
131 | if (err) {
132 | Alert.error(
133 | 'An error occured while creating assignment. Please try again.',
134 | 5000
135 | );
136 | }
137 | }
138 | )
139 | .then(rsp => {
140 | window.location.reload(false);
141 | });
142 | }
143 | );
144 | }
145 | }
146 |
147 | formNext() {
148 | const { title, curriculum, type } = this.state.formValue;
149 | const courseId = '-' + this.props.match.params.id;
150 |
151 | if (!title || !curriculum) {
152 | Alert.error('Please fill in all fields.');
153 | } else {
154 | this.props.firebase
155 | .course(courseId)
156 | .child('assignments')
157 | .push(
158 | {
159 | curriId: curriculum,
160 | title,
161 | type
162 | },
163 | err => {
164 | if (err) {
165 | Alert.error(
166 | 'An error occured while creating assignment. Please try again.',
167 | 5000
168 | );
169 | }
170 | }
171 | )
172 | .then(rsp => {
173 | const assKey = rsp.key.substr(1);
174 |
175 | this.props.history.push(
176 | `/courses/${courseId.substr(1)}/assignment/${assKey}`
177 | );
178 | });
179 | }
180 | }
181 |
182 | render() {
183 | const { curriculum } = this.state;
184 | const { assFile } = this.state;
185 | const uploaderDisabled = assFile ? true : false;
186 |
187 | return (
188 |
189 |
190 | Create an Assignment
191 |
192 |
193 |
194 |
312 |
313 |
314 |
315 |
316 | );
317 | }
318 | }
319 |
320 | const assignmentType = [
321 | {
322 | value: 'file',
323 | label: 'Attachment only'
324 | },
325 | {
326 | value: 'subjective',
327 | label: 'Subjective'
328 | }
329 | ];
330 |
--------------------------------------------------------------------------------
/src/Components/Courses/CoursePage/Assignment/AssignmentPage.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import SunEditor from "suneditor-react";
3 | import { compose } from "recompose";
4 |
5 | import "suneditor/dist/css/suneditor.min.css";
6 |
7 | import {
8 | Container,
9 | Nav,
10 | Navbar,
11 | Icon,
12 | Button,
13 | Alert,
14 | Form,
15 | ControlLabel,
16 | FormGroup,
17 | FormControl,
18 | ButtonGroup,
19 | Modal,
20 | } from "rsuite";
21 | import ReactHtmlParser from "react-html-parser";
22 | import { withAuthentication } from "../../../Session";
23 | import withAssignment from "./withAssignment";
24 |
25 | class AssignmentPage extends React.Component {
26 | constructor(props) {
27 | super(props);
28 |
29 | this.state = {
30 | contentState: "",
31 | assignmentsQ: [],
32 | show: false,
33 | };
34 | }
35 |
36 | componentDidMount() {
37 | const assignmentId = this.props.match.params.id;
38 | const courseId = this.props.match.url
39 | .replace(/\/.*?\//g, "")
40 | .replace(assignmentId, "");
41 |
42 | this.props.firebase
43 | .assignments(`-${courseId}`)
44 | .child(`-${assignmentId}`)
45 | .child("questions")
46 | .on("value", (snapshot) => {
47 | this.setState({
48 | assignmentsQ: snapshot.val() ? snapshot.val() : [],
49 | });
50 | });
51 | }
52 |
53 | studentHasAttempted = (assignmentId) => {
54 | const uid = this.props.authUser? this.props.firebase.auth.currentUser.uid: null;
55 | let submissions = [];
56 | if(uid){
57 | this.props.firebase.user(uid).child('enrolledCourses').on('value', snapshot => {
58 | snapshot.forEach(child => {
59 | const submission = child.val().submissions;
60 | if(submission){
61 | submissions.push(submission)
62 | }
63 | })
64 | })
65 | }
66 |
67 | if(submissions.length){
68 | submissions = submissions.map(item => {
69 | const keys = Object.keys(item);
70 | // return item[key].assId;
71 | return keys.map(key => {
72 | return item[key].assId;
73 | })
74 | })
75 |
76 | submissions = submissions.flat();
77 | }
78 |
79 | return submissions.includes(assignmentId)? true : false;
80 | }
81 |
82 | editorOnChange = (contentState) => {
83 | this.setState({ contentState });
84 | };
85 |
86 | saveQuestions = () => {
87 | const assignmentId = this.props.match.params.id;
88 | const courseId = this.props.match.url
89 | .replace(/\/.*?\//g, "")
90 | .replace(assignmentId, "");
91 | this.props.updateAssignment(
92 | "-" + courseId,
93 | "-" + assignmentId,
94 | this.state.assignmentsQ
95 | );
96 | this.closeModal();
97 | };
98 |
99 | addQuestion = () => {
100 | if (this.state.contentState) {
101 | let assignmentsQ = this.state.assignmentsQ
102 | ? this.state.assignmentsQ
103 | : [];
104 | assignmentsQ.push(this.state.contentState);
105 | this.setState({ assignmentsQ });
106 | } else {
107 | Alert.error("Content cannot be blank.");
108 | }
109 |
110 | this.setState({ contentState: "" });
111 | };
112 |
113 | removeQ = (i) => {
114 | let assignmentsQ = this.state.assignmentsQ;
115 | assignmentsQ = assignmentsQ.filter((ass, index) => {
116 | return index != i;
117 | });
118 |
119 | this.setState({ assignmentsQ });
120 | };
121 |
122 | renderQuestions = (stud = false) => {
123 | const assignmentsQ = this.state.assignmentsQ;
124 | const assignmentId = this.props.match.params.id;
125 |
126 | const attempted = (this.studentHasAttempted(assignmentId));
127 | const disabledAdmin = this.props.authUser? (this.props.authUser.roles.userRole === 'admin'? true : null) : false;
128 |
129 | return (
130 |
131 |
132 |
Assignment
133 |
134 |
135 |
191 |
192 | );
193 | };
194 |
195 | closeModal = () => {
196 | this.setState({ show: false });
197 | };
198 |
199 | openModal = () => {
200 | this.setState({ show: true });
201 | };
202 |
203 | studentSubmitAnswer = () => {
204 | const uid = this.props.firebase.auth.currentUser.uid;
205 | const assId = this.props.match.params.id;
206 | const courseId = this.props.match.url.replace(/\/.*?\//g, '').replace(assId, '');
207 |
208 | const formValue = this.state.formValue;
209 | if(formValue){
210 | const authUser = this.props.authUser;
211 |
212 | let enrolledCourses = authUser.enrolledCourses;
213 | const enrolledCoursesKey = Object.keys(authUser.enrolledCourses);
214 |
215 | enrolledCourses = enrolledCoursesKey.map(key => {
216 | return {
217 | snapshotKey: key,
218 | courseId: enrolledCourses[key].courseId
219 | }
220 | })
221 |
222 | enrolledCourses = enrolledCourses.filter(item =>{
223 | return item.courseId === courseId;
224 | })
225 |
226 | const dateNow = Date.now();
227 |
228 | this.props.firebase.user(uid).child('enrolledCourses').child(enrolledCourses[0].snapshotKey).child('submissions').push({
229 | status: 'new',
230 | assId,
231 | answers: formValue,
232 | submittedOn: dateNow,
233 | }).then(rsp => {
234 | Alert.success('Successfully submitted');
235 |
236 | this.props.firebase.course(`-${courseId}`).child('submissions').push({
237 | status: 'new',
238 | studentId: uid,
239 | studentName: authUser.name,
240 | submittedOn: dateNow,
241 | snapshotKey: rsp.key,
242 | })
243 | })
244 | }
245 | else{
246 | Alert.error('Please answer all questions.');
247 | }
248 | }
249 |
250 | renderForAdmin = () => {
251 | return (
252 | <>
253 |
254 |
264 |
265 |
266 |
273 | {this.renderQuestions()}
274 |
275 |
288 |
289 |
290 |
297 |
305 |
311 |
312 | {" "}
319 | Confirm saving/overwriting changes?
320 |
321 |
322 |
328 |
334 |
335 |
336 |
337 |
338 | >
339 | );
340 | };
341 |
342 | renderForStudent = () => {
343 | return (
344 | <>
345 |
346 |
356 |
357 |
358 |
366 | {this.renderQuestions(true)}
367 |
368 | >
369 | );
370 | };
371 |
372 | render() {
373 | const authUser = this.props.authUser;
374 | const hasEditAuthority = authUser && authUser.roles.userRole === "admin";
375 | let coursesCreated = null;
376 | let isCourseCreator = null;
377 |
378 | if (hasEditAuthority) {
379 | const coursesCreatedKey = authUser.coursesCreated? Object.keys(authUser.coursesCreated) : null;
380 |
381 | if(coursesCreatedKey){
382 | coursesCreated = Object.keys(
383 | authUser.coursesCreated
384 | ).map((key) => ({
385 | ...authUser.coursesCreated[key],
386 | uid: key,
387 | }));
388 | coursesCreated = coursesCreated.map((item) => {
389 | return item.courseId;
390 | });
391 |
392 | const curriId = this.props.match.params.id;
393 | const courseId = this.props.match.url
394 | .replace(/\/.*?\//g, "")
395 | .replace(curriId, "");
396 |
397 | isCourseCreator = coursesCreated.includes(courseId);
398 | }
399 | }
400 |
401 | return (
402 |
403 | {isCourseCreator
404 | ? this.renderForAdmin()
405 | : this.renderForStudent()}
406 |
407 | );
408 | }
409 | }
410 |
411 | export default compose(
412 | withAuthentication,
413 | withAssignment)
414 | (AssignmentPage);
415 |
--------------------------------------------------------------------------------
/src/Components/Courses/CoursePage/Assignment/withAssignment.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { withFirebase } from '../../../Firebase';
3 | import { Alert } from 'rsuite';
4 |
5 | const withAssignment = Component => {
6 | class WithAssignment extends React.Component{
7 | updateAssignment = (courseId, assignmentId, questions) => {
8 | questions = questions? questions: [];
9 | this.props.firebase.assignments(courseId).child(assignmentId).update({questions}).then(()=>{
10 | Alert.success('Successfully saved');
11 | }).catch(err => {Alert.error('Save failed... Please try again.')})
12 | }
13 |
14 | render(){
15 | return(
16 |
20 | )
21 | }
22 | }
23 |
24 | return withFirebase(WithAssignment);
25 | }
26 |
27 | export default withAssignment;
--------------------------------------------------------------------------------
/src/Components/Courses/CoursePage/Body.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {
3 | Container,
4 | Button,
5 | Icon,
6 | Panel,
7 | PanelGroup,
8 | Modal,
9 | Alert,
10 | Loader,
11 | Table
12 | } from 'rsuite';
13 | import CurriculumModal from './Curriculum/CurriculumModal';
14 | import { NavLink, Link } from 'react-router-dom';
15 | import AssignmentModal from './Assignment/AssignmentModal';
16 |
17 | export default class Body extends React.Component {
18 | constructor(props) {
19 | super(props);
20 | this.state = {
21 | course : null,
22 | loading : true,
23 | authUser : null,
24 | panelNavigate : '',
25 | showCurriculumModal : false,
26 | showAssignmentModal : false,
27 | };
28 |
29 | this.panelSelect = this.panelSelect.bind(this);
30 | this.enrollCourse = this.enrollCourse.bind(this);
31 | this.renderCurriculum = this.renderCurriculum.bind(this);
32 | this.retrievePageCourse = this.retrievePageCourse.bind(this);
33 | this.curriculumModalToggle = this.curriculumModalToggle.bind(this);
34 | this.assignmentModalToggle = this.assignmentModalToggle.bind(this);
35 | }
36 | componentWillMount() {
37 | this.retrievePageCourse(this.props.match.params.id);
38 | window.scrollTo(0, 0);
39 | }
40 |
41 | retrievePageCourse(id) {
42 | const coursesList = this.props.coursesList
43 | ? this.props.coursesList
44 | : [];
45 |
46 | if (coursesList) {
47 | let foundCourse = null;
48 |
49 | for (let i = 0; i < coursesList.length; i++) {
50 | if (coursesList[i].uid === `-${id}`) {
51 | foundCourse = coursesList[i];
52 | break;
53 | }
54 | }
55 |
56 | this.setState({ course: foundCourse });
57 | }
58 | }
59 |
60 | panelSelect(key) {
61 | this.setState({ panelNavigate: key });
62 | }
63 |
64 | curriculumModalToggle() {
65 | this.setState({ showCurriculumModal: !this.state.showCurriculumModal });
66 | }
67 |
68 | assignmentModalToggle() {
69 | this.setState({ showAssignmentModal: !this.state.showAssignmentModal });
70 | }
71 |
72 | renderEnrollButton(isCourseCreator, authUser, enrolledToCourse, courseId) {
73 | if (isCourseCreator) {
74 | return (
75 | <>
76 |
85 |
94 |
95 | >
96 | );
97 | } else if (
98 | (authUser && authUser.roles.userRole === 'admin') || enrolledToCourse) {
99 | return (
100 |
109 | );
110 | } else {
111 | return (
112 |
122 | );
123 | }
124 | }
125 |
126 | enrollCourse() {
127 | const authUser = this.state.authUser;
128 |
129 | if (!authUser) {
130 | Alert.error('Login first')
131 | this.props.history.push('/login');
132 | } else {
133 | this.props.firebase
134 | .user(authUser.uid)
135 | .child('enrolledCourses')
136 | .push({ courseId: this.props.match.params.id }, err => {
137 | if (err) {
138 | Alert.error(
139 | 'There was an error registering to courses...'
140 | );
141 | }
142 | })
143 | .then(rsp => {
144 | const date = Date.now();
145 |
146 | Alert.success('Successfully enrolled to course.');
147 | this.props.firebase.course(`-${this.props.match.params.id}`).child('students').push({
148 | studId: authUser.uid,
149 | joinedOn: date,
150 | })
151 | });
152 | }
153 | }
154 |
155 | renderCurriculum( allowPreview, isCourseCreator, item, enrolledToCourse) {
156 | if (allowPreview || isCourseCreator || enrolledToCourse) {
157 | return (
158 |
160 | {item.shortDescription}
161 |
162 | );
163 | } else {
164 | return {item.shortDescription}
;
165 | }
166 | }
167 |
168 | renderAssignments = (assignment) => {
169 | return(
170 |
172 | {assignment.title}
173 |
174 | )
175 | }
176 |
177 | componentDidMount() {
178 | this.listener = this.props.firebase.onAuthUserListener(
179 | authUser => {
180 | return this.setState({ authUser, loading: false });
181 | },
182 | () => {
183 | this.setState({ authUser: null, loading: false });
184 | }
185 | );
186 | }
187 |
188 | render() {
189 | let coursesCreated = null;
190 | let enrolledToCourse = null;
191 | let isCourseCreator = null;
192 | const courseId = this.props.match.params.id;
193 | const authUser = this.state.authUser ? this.state.authUser : null;
194 | console.log(this.state.course)
195 | let {
196 | levelOfStudy,
197 | description,
198 | image,
199 | duration,
200 | prerequisite,
201 | title,
202 | curriculum,
203 | assignments,
204 | qualification
205 | } = this.state.course ? this.state.course : {};
206 |
207 | if (this.state.course) {
208 | if (curriculum) {
209 | curriculum = Object.keys(curriculum).map(key => ({
210 | ...curriculum[key],
211 | curriculumId: key.substr(1)
212 | }));
213 | }
214 |
215 | if(assignments){
216 | assignments = Object.keys(assignments).map(key => ({
217 | ...assignments[key],
218 | assignmentId: key
219 | }));
220 | }
221 |
222 | if (authUser && !!authUser.coursesCreated) {
223 | coursesCreated = Object.keys(authUser.coursesCreated).map(
224 | key => ({
225 | ...authUser.coursesCreated[key],
226 | uid: key
227 | })
228 | );
229 |
230 | coursesCreated = coursesCreated.map(item => {
231 | return item.courseId;
232 | });
233 |
234 | isCourseCreator = coursesCreated.includes(
235 | this.props.match.params.id
236 | );
237 | }
238 | }
239 |
240 | if (authUser && authUser.enrolledCourses) {
241 | enrolledToCourse = authUser.enrolledCourses;
242 | const keys = Object.keys(enrolledToCourse);
243 |
244 | enrolledToCourse = keys.map(key => {
245 | return enrolledToCourse[key];
246 | });
247 |
248 | enrolledToCourse = enrolledToCourse.map(item => {
249 | return item.courseId;
250 | });
251 |
252 | enrolledToCourse = enrolledToCourse.includes(courseId);
253 | }
254 | return (
255 |
256 |
257 |
263 |
264 |
265 |
266 |
272 |
273 |
274 |
275 |
276 |
281 |
286 |
291 | {title}
292 |
297 | {description}
298 |
299 |
300 |
301 |
306 | Duration
307 |
312 | {duration} Hours
313 |
314 |
315 |
316 |
321 | Academic Qualification
322 |
327 | {qualification[0].toUpperCase() + qualification.substr(1)}
328 |
329 |
330 |
331 |
336 |
337 | Requirements
338 |
339 |
344 | {prerequisite}
345 |
346 |
347 |
348 | {this.state.loading ? (
349 |
350 | ) : (
351 | <>
352 |
353 |
358 | Curriculum
359 |
360 |
367 | {curriculum ? (
368 | curriculum.map((item, i) => {
369 | const allowPreview = !!item.allowPreview;
370 | return (
371 |
377 | {this.renderCurriculum(
378 | allowPreview,
379 | isCourseCreator,
380 | item,
381 | enrolledToCourse,
382 | )}
383 |
384 | );
385 | })
386 | ) : (
387 |
388 |
389 |
390 | )}
391 | {authUser && isCourseCreator ? (
392 |
397 | ) : null}
398 |
399 |
400 |
401 |
406 | Assignments
407 |
408 | { (enrolledToCourse || (authUser && authUser.roles.userRole) === 'admin')?
409 |
410 |
411 |
412 | Id
413 |
414 |
415 |
416 |
417 | Assignment Title
418 |
419 |
420 |
421 |
422 | Type
423 |
424 |
425 |
426 |
427 | Action
428 |
429 | {rowData => {
430 | if(rowData.type === 'subjective'){
431 | return(
432 |
433 |
434 |
435 | )
436 | }
437 | else{
438 | return(
439 |
440 |
441 |
442 | )
443 | }
444 | }}
445 |
446 |
447 |
448 | {authUser && isCourseCreator ? (
449 |
456 | ) : null}
457 |
458 | : Enroll to view assignments...
459 | }
460 | {/*
465 | {assignments ? (
466 | assignments.map((item, i) => {
467 | let curi = curriculum.filter(curri => {
468 | return curri.curriculumId === item.curriId.substr(1);
469 | })
470 |
471 | return (
472 |
476 | {this.renderAssignments(item)}
477 |
478 | );
479 | })
480 | ) : (
481 |
482 |
483 |
484 | )}
485 | {authUser && isCourseCreator ? (
486 |
493 | ) : null}
494 | */}
495 |
496 | >
497 | )}
498 |
499 |
500 |
501 |
507 |
508 |

514 |
515 |
516 |
523 |
527 |
528 | {levelOfStudy}
529 |
530 |
531 |
532 | {this.renderEnrollButton(
533 | isCourseCreator,
534 | authUser,
535 | enrolledToCourse,
536 | courseId
537 | )}
538 |
539 |
540 |
541 |
542 |
543 | );
544 | }
545 | }
--------------------------------------------------------------------------------
/src/Components/Courses/CoursePage/Curriculum/CurriculumModal.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import {
3 | Modal,
4 | Form,
5 | FormGroup,
6 | ControlLabel,
7 | FormControl,
8 | Container,
9 | Button,
10 | ButtonToolbar,
11 | Alert,
12 | Toggle,
13 | } from "rsuite";
14 |
15 | export default class CurriculumModal extends React.Component {
16 | constructor(props) {
17 | super(props);
18 | this.state = {
19 | editorContext: null,
20 | content: null,
21 | formValue: {
22 | title: "",
23 | shortDescription: "",
24 | allowPreview: false,
25 | }
26 | };
27 | this.formSubmit = this.formSubmit.bind(this);
28 | }
29 |
30 | close() {
31 | this.setState({ show: false });
32 | }
33 | open(size) {
34 | this.setState({ show: true });
35 | }
36 |
37 | onDanteSaveHandler(editorContext, content) {
38 | this.setState({ editorContext, content });
39 | }
40 |
41 | formSubmit() {
42 | let { title, shortDescription, allowPreview } = this.state.formValue;
43 | const courseId = this.props.match.params.id;
44 | allowPreview = !!(allowPreview);
45 |
46 | if (!title || !shortDescription) {
47 | Alert.error("Title and description cannot be empty.", 5000);
48 | } else {
49 | this.props.firebase
50 | .courseCurriculums(`-${courseId}`)
51 | .push({ title, shortDescription, allowPreview }, err => {
52 | if (err) {
53 | Alert.error(
54 | "An error occurred while creating curriculum. Please try again.",
55 | 5000
56 | );
57 | }
58 | })
59 | .then(rsp => {
60 | const curriculumId = rsp.key.substr(1, rsp.key.length);
61 | this.props.history.push(
62 | `/courses/${courseId}/curriculum/${curriculumId}`
63 | );
64 | });
65 | }
66 | }
67 |
68 | render() {
69 | return (
70 |
71 |
72 | Add Curriculum
73 |
74 |
75 |
76 |
77 |
110 |
111 |
112 |
113 | );
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/src/Components/Courses/CoursePage/Curriculum/CurriculumPage.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import SunEditor from 'suneditor-react';
3 | import { compose } from 'recompose';
4 |
5 | import 'suneditor/dist/css/suneditor.min.css';
6 |
7 | import { Container, Nav, Navbar, Icon } from 'rsuite';
8 | import withCurriculum from './withCurriculum';
9 | import { AuthUserContext } from '../../../Session';
10 |
11 | class CurriculumPage extends React.Component{
12 | constructor(props){
13 | super(props);
14 |
15 | this.state = {
16 | contentState: '',
17 | sunEditor: {
18 | editing: false,
19 | resizingBar: false,
20 | enable: false,
21 | showToolbar: false,
22 | disable: true,
23 | }
24 | }
25 |
26 | this.editorOnChange = this.editorOnChange.bind(this);
27 | this.editorSave = this.editorSave.bind(this);
28 | this.editToggle = this.editToggle.bind(this);
29 | }
30 |
31 | componentDidMount(){
32 | const curriId = this.props.match.params.id;
33 | const courseId = this.props.match.url.replace(/\/.*?\//g, '').replace(curriId, '');
34 | this.props.getCurriculum(courseId, curriId);
35 |
36 | this.props.firebase.courseCurriculums(`-${courseId}`)
37 | .child(`-${curriId}`)
38 | .child('curriculumContent')
39 | .on('value', snapshot=> {
40 | this.setState({contentState: snapshot.val()})
41 | }
42 | )
43 | }
44 |
45 | editToggle(){
46 | this.setState({
47 | sunEditor:{
48 | editing: !this.state.sunEditor.editing,
49 | resizingBar: !this.state.sunEditor.resizingBar,
50 | enable: !this.state.sunEditor.enable,
51 | showToolbar: !this.state.sunEditor.showToolbar,
52 | disable: !this.state.sunEditor.disable,
53 | }
54 | })
55 | }
56 |
57 | editorOnChange(content){
58 | this.setState({contentState: content});
59 | }
60 |
61 | editorSave(){
62 | const curriId = this.props.match.params.id;
63 | const courseId = this.props.match.url.replace(/\/.*?\//g, '').replace(curriId, '');
64 | this.props.updateCurriculum('-'+courseId, '-'+curriId, this.state.contentState);
65 | }
66 |
67 | render(){
68 | return(
69 |
70 | {authUser => {
71 | const hasEditAuthority = authUser && authUser.roles.userRole === 'admin';
72 | let coursesCreated = null;
73 | let isCourseCreator = null;
74 |
75 | if(hasEditAuthority){
76 | coursesCreated = Object.keys(authUser.coursesCreated).map(
77 | key => ({
78 | ...authUser.coursesCreated[key],
79 | uid: key
80 | })
81 | );
82 | coursesCreated = coursesCreated.map(item => {
83 | return item.courseId;
84 | });
85 |
86 | const curriId = this.props.match.params.id;
87 | const courseId = this.props.match.url.replace(/\/.*?\//g, '').replace(curriId, '');
88 |
89 | isCourseCreator = coursesCreated.includes(
90 | courseId
91 | );
92 | }
93 |
94 | return(
95 |
96 |
97 |
100 | {
101 | isCourseCreator?
102 | (this.state.sunEditor.editing?
103 |
104 |
107 |
110 |
111 | :
112 |
113 |
116 |
117 | )
118 | :
119 | (
120 | ''
121 | )
122 |
123 | }
124 |
125 |
126 |
127 |
128 |
159 |
160 |
161 |
162 | )
163 | }}
164 |
165 |
166 |
167 | )
168 | }
169 | }
170 |
171 | // const condition = authUser => !!authUser;
172 |
173 | export default compose(
174 | withCurriculum,
175 | )(CurriculumPage);
176 |
--------------------------------------------------------------------------------
/src/Components/Courses/CoursePage/Curriculum/withCurriculum.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { withFirebase } from '../../../Firebase';
3 | import { Alert } from 'rsuite';
4 |
5 | const withCurriculum = Component => {
6 | class WithCurriculum extends React.Component{
7 | constructor(props){
8 | super(props);
9 |
10 | this.state = {
11 | content: null,
12 | }
13 | this.getCurriculum = this.getCurriculum.bind(this);
14 | this.updateCurriculum = this.updateCurriculum.bind(this);
15 |
16 | }
17 |
18 | getCurriculum(courseId, curriId){
19 | this.props.firebase.courseCurriculums(courseId).child(curriId).child('curriculumContent').once('value').then(snapshot => {
20 | this.setState({content: snapshot.val()});
21 | })
22 | }
23 |
24 | updateCurriculum(courseId, curriId, content){
25 | this.props.firebase.courseCurriculums(courseId).child(curriId).update({curriculumContent: content}).then(()=>{
26 | Alert.success('Successfully saved');
27 | }).catch(err => {Alert.error('Save failed... Please try again.')})
28 | }
29 |
30 | render(){
31 | return(
32 |
37 | )
38 | }
39 | }
40 |
41 | return withFirebase(WithCurriculum);
42 | }
43 |
44 | export default withCurriculum;
--------------------------------------------------------------------------------
/src/Components/Courses/CoursePage/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import AnnouncementsPanel from '../../Announcements/AnnouncementsPanel';
3 | import mainTemplate from '../../Templates/MainTemplate';
4 | import { Breadcrumb } from 'rsuite';
5 | import Body from './Body';
6 | import withCourse from '../Context/withCourse';
7 | import { NavLink } from 'react-router-dom';
8 |
9 | const Header = (props) => (
10 |
15 | )
16 |
17 | const BreadCrumb = (props) => (
18 |
19 | Dashboard
20 | Courses
21 |
22 | )
23 |
24 | const body = (props) => {
25 | return(
26 |
30 |
31 | )
32 | }
33 |
34 |
35 |
36 | const WrappedComponents = mainTemplate(Header, BreadCrumb, body);
37 |
38 | export default withCourse(WrappedComponents);
39 |
--------------------------------------------------------------------------------
/src/Components/Courses/CourseShowcase.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { Panel } from 'rsuite';
3 | import { NavLink } from 'react-router-dom';
4 |
5 | export default class CourseShowcase extends Component{
6 |
7 | render(){
8 | const course = this.props.course;
9 | return(
10 |
11 |
12 |
13 |
18 |
19 |
22 |
23 | {course.description}
24 |
25 |
26 |
27 |
28 |
29 | )
30 | }
31 | }
--------------------------------------------------------------------------------
/src/Components/Courses/EditCourses/Body.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import {
3 | Container,
4 | FormGroup,
5 | Form,
6 | FormControl,
7 | ControlLabel,
8 | SelectPicker,
9 | Button,
10 | ButtonToolbar,
11 | Alert,
12 | Uploader,
13 | InputNumber,
14 | } from "rsuite";
15 |
16 | const qualificationData = [
17 | { label: "Primary School", value: "primary-school" },
18 | { label: "High School", value: "high-school" },
19 | { label: "College", value: "college" },
20 | { label: "Degree", value: "degree" },
21 | { label: "Masters", value: "master" },
22 | { label: "Other", value: "other" }
23 | ];
24 |
25 | const difficultyLevel = [
26 | { label: "Fundamental", value: "fundamental" },
27 | { label: "Intermediate", value: "intermediate" },
28 | { label: "Advanced", value: "advanced" },
29 | { label: "Fundamental-Intermediate", value: "fundamental-intermediate" },
30 | { label: "Fundamental-Advanced", value: "fundamental-advanced" },
31 | { label: "Other", value: "other" }
32 | ];
33 |
34 | class Body extends React.Component {
35 | constructor(props) {
36 | super(props);
37 | this.state = {
38 | course: null,
39 | showError: false,
40 | errorPlacement: "bottomStart",
41 | formValue: {
42 | title: "",
43 | qualification: "",
44 | levelOfStudy: "",
45 | description: "",
46 | prerequisite: ""
47 | },
48 | featureImage: null,
49 | uploading: false,
50 | setFileInfo: null,
51 | };
52 | this.formSubmit = this.formSubmit.bind(this);
53 | this.retrievePageCourse = this.retrievePageCourse.bind(this);
54 | }
55 |
56 | retrievePageCourse(id) {
57 | const coursesList = this.props.coursesList? this.props.coursesList : [];
58 | if(coursesList){
59 | let foundCourse = null;
60 | for (let i = 0; i < coursesList.length; i++) {
61 | if (coursesList[i].uid === `-${id}`) {
62 | foundCourse = coursesList[i];
63 | break;
64 | }
65 | }
66 | this.setState({ course: foundCourse });
67 | }
68 | }
69 |
70 | formSubmit() {
71 | const formValue = this.state.formValue;
72 | const course = this.state.course;
73 |
74 | const formValueKeys = Object.keys(formValue);
75 |
76 | formValueKeys.forEach(key=> {
77 | if(formValue[key]){
78 | course[key] = formValue[key]
79 | }
80 | })
81 |
82 | this.props.firebase
83 | .course(`-${this.props.match.params.id}`)
84 | .update(
85 | {
86 | title: course.title,
87 | qualification: course.qualification,
88 | levelOfStudy: course.levelOfStudy,
89 | description: course.description,
90 | prerequisite: course.prerequisite,
91 | image: this.state.setFileInfo? this.state.setFileInfo : course.image
92 | },
93 | err => {
94 | if (err) {
95 | Alert.error(
96 | "An error occurred while creating course. Please try again.",
97 | 5000
98 | );
99 | }
100 | }
101 | )
102 | .then(rsp => {
103 | this.props.history.push(`/courses/${this.props.match.params.id}`);
104 | });
105 |
106 | }
107 |
108 | previewFile(file, callback) {
109 | const reader = new FileReader();
110 | reader.onloadend = () => {
111 | callback(reader.result);
112 | };
113 | reader.readAsDataURL(file);
114 | }
115 |
116 | componentDidMount(){
117 | this.retrievePageCourse(this.props.match.params.id);
118 | }
119 |
120 | render() {
121 | const course = this.state.course? this.state.course: null;
122 |
123 | return (
124 |
125 | {course?
126 |
127 |
128 | {
134 | this.setState({ uploading: true });
135 | this.previewFile(file.blobFile, value => {
136 | this.setState({ setFileInfo: value});
137 | });
138 | }}
139 | >
140 |
152 |
153 |
154 |
155 |
156 |
220 |
221 |
222 | :
Loading...
223 | }
224 |
225 | );
226 | }
227 | }
228 |
229 | export default Body;
--------------------------------------------------------------------------------
/src/Components/Courses/EditCourses/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {compose} from 'recompose';
3 | import mainTemplate from '../../Templates/MainTemplate'
4 | import {withFirebase} from '../../Firebase';
5 | import {withAuthorization} from '../../Session'
6 | import {NavLink} from 'react-router-dom';
7 | import AnnouncementsPanel from '../../Announcements/AnnouncementsPanel';
8 | import { Breadcrumb } from 'rsuite';
9 | import Body from './Body';
10 | import withCourse from '../Context/withCourse';
11 |
12 | const Header = (props) => (
13 |
18 | )
19 |
20 | const BreadCrumb = () => (
21 |
22 | Dashboard
23 | Courses
24 | Edit Course
25 |
26 | )
27 |
28 | const condition = authUser => authUser && !!(authUser.roles['userRole'] === 'admin');
29 |
30 | let EditCoursePage = mainTemplate(Header, BreadCrumb, Body);
31 |
32 | export default compose(
33 | withAuthorization(condition),
34 | withCourse,
35 | withFirebase,
36 | )(EditCoursePage);
37 |
38 |
--------------------------------------------------------------------------------
/src/Components/Dashboard/MainPalette/AdminCoursePalette.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { withFirebase } from '../../Firebase';
3 | import { FlexboxGrid, List, Button } from 'rsuite';
4 | import { NavLink, Link } from 'react-router-dom';
5 |
6 | class AdminCoursePalette extends React.Component{
7 | constructor(props){
8 | super(props);
9 |
10 | this.state = {
11 | courses: [],
12 | }
13 | }
14 | async componentDidMount(){
15 | const createdCourses = this.props.authUser.coursesCreated;
16 | let coursesList = [];
17 | let courses = null;
18 |
19 | if(createdCourses){
20 | coursesList = Object.keys(createdCourses).map(key =>
21 | createdCourses[key].courseId
22 | );
23 |
24 | coursesList = coursesList.map(id => {
25 | return new Promise(rs => {
26 | this.props.firebase.course(`-${id}`)
27 | .once('value')
28 | .then(snapshot => {
29 | const course = snapshot.val();
30 | rs({course, id});
31 | })
32 | })
33 | })
34 |
35 | courses = await Promise.all(coursesList).then(courses =>{
36 | return courses;
37 | })
38 |
39 | this.setState({courses});
40 | }
41 |
42 |
43 | }
44 |
45 | render(){
46 | const courses = this.state.courses ? this.state.courses: [];
47 | return(
48 |
49 |
50 |
My Created Courses
51 |
52 |
53 | {
54 | courses?
55 |
56 | {
57 | courses.map((course, i)=> {
58 | let curriculum = course.course.curriculum || {};
59 | curriculum = curriculum? Object.keys(curriculum).length: 0;
60 | let students = course.course.students? Object.keys(course.course.students).length : 0;
61 | return(
62 | <>
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
76 |
77 | {course.course.title}
78 |
79 |
80 |
81 |
82 |
83 |
Students
84 |
{students}
85 |
86 |
87 |
88 |
89 |
90 |
Curriculums
91 |
{curriculum}
92 |
93 |
94 |
95 |
98 |
104 |
107 |
108 |
109 |
110 |
111 |
112 | >
113 | )
114 | })
115 | }
116 |
117 | :
No courses created...
118 | }
119 |
120 |
121 | )
122 | }
123 | }
124 |
125 | const styleCenter = {
126 | display: 'flex',
127 | justifyContent: 'center',
128 | alignItems: 'center',
129 | height: '60px'
130 | };
131 |
132 | const slimText = {
133 | fontSize: '0.666em',
134 | color: '#97969B',
135 | fontWeight: 'lighter',
136 | paddingBottom: 5
137 | };
138 |
139 | const titleStyle = {
140 | padding: 5,
141 | whiteSpace: 'nowrap',
142 | fontWeight: 500,
143 | fontSize: '14px'
144 | };
145 |
146 | const dataStyle = {
147 | fontSize: '1.2em',
148 | fontWeight: 500
149 | };
150 | export default withFirebase(AdminCoursePalette);
--------------------------------------------------------------------------------
/src/Components/Dashboard/MainPalette/CoursePanel.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import AdminCoursePalette from './AdminCoursePalette';
3 | import StudentCoursePalette from './StudentCoursePalette';
4 |
5 | class CoursePanel extends React.Component{
6 | render(){
7 | const authUser = this.props.authUser;
8 | return(
9 | authUser.roles.userRole === 'admin'?
10 |
11 | :
12 | )
13 | }
14 | }
15 |
16 | export default CoursePanel;
--------------------------------------------------------------------------------
/src/Components/Dashboard/MainPalette/StudentCoursePalette.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { withFirebase } from '../../Firebase';
3 | import { Button, Panel, Table, Modal } from 'rsuite';
4 | import { compose } from 'recompose';
5 | import withCourse from '../../Courses/Context/withCourse';
6 |
7 | class StudentCoursePalette extends React.Component {
8 | constructor(props) {
9 | super(props);
10 |
11 | this.state = {
12 | courses: [],
13 | show: false
14 | };
15 | }
16 | async componentDidMount() {
17 | const enrolledCourses = this.props.authUser.enrolledCourses;
18 | const coursesList = this.props.coursesList;
19 |
20 | let submissions = [];
21 |
22 | if (enrolledCourses) {
23 | submissions = Object.keys(enrolledCourses).map(key => {
24 | const courseId = enrolledCourses[key].courseId;
25 |
26 | const course = coursesList.filter(item => {
27 | return item.uid === `-${courseId}`;
28 | });
29 |
30 | return {
31 | courseId: enrolledCourses[key].courseId,
32 | courseTitle: course[0].title,
33 | submissions: enrolledCourses[key].submissions
34 | };
35 | });
36 |
37 | //Extract submissions innerkey
38 | submissions = submissions.map(item => {
39 | if (item.submissions) {
40 | const key = Object.keys(item.submissions);
41 |
42 | const submission = key.map(key => {
43 | return item.submissions[key];
44 | });
45 |
46 | return {
47 | courseId: item.courseId,
48 | courseTitle: item.courseTitle,
49 | submissions: submission
50 | };
51 | }
52 |
53 | return {
54 | courseId: item.courseId,
55 | courseTitle: item.courseTitle
56 | };
57 | });
58 | this.setState({ submissions });
59 |
60 | submissions = submissions
61 | .map(item => {
62 | if (item.submissions) {
63 | item.submissions = item.submissions.map(inner => {
64 | const assId = '-' + inner.assId;
65 |
66 | const course = coursesList.filter(course => {
67 | return course.uid === `-${item.courseId}`;
68 | });
69 |
70 | let assignment = course[0].assignments;
71 | const keys = Object.keys(assignment);
72 |
73 | assignment = keys.map(key => {
74 | return {
75 | key,
76 | data: assignment[key]
77 | };
78 | });
79 |
80 | assignment = assignment.filter(ass => {
81 | return ass.key === assId;
82 | });
83 |
84 | assignment = assignment[0].data;
85 |
86 | return {
87 | ...inner,
88 | assTitle: assignment.title
89 | };
90 | });
91 | return;
92 | }
93 | })
94 | .flat();
95 | }
96 | }
97 |
98 | open = () => {
99 | this.setState({ show: true });
100 | };
101 |
102 | close = () => {
103 | this.setState({ show: false });
104 | };
105 |
106 | render() {
107 | const submissions = this.state.submissions
108 | ? this.state.submissions
109 | : [];
110 |
111 | return (
112 |
113 |
114 |
My Submissions
115 |
116 |
117 | {submissions
118 | ? submissions.map(submission => {
119 | return (
120 |
123 |
126 |
127 |
128 | Submitted On
129 |
130 |
131 | {rowData => {
132 | const date = new Date(
133 | rowData.submittedOn
134 | ).toLocaleString();
135 |
136 | return {date}
;
137 | }}
138 |
139 |
140 |
141 |
142 | Assignment Title
143 |
144 |
148 |
149 |
150 |
151 | Status
152 |
153 |
154 | {rowData => {
155 | return rowData.status ===
156 | 'new' ? (
157 |
161 | Pending Review
162 |
163 | ) : (
164 |
168 | Reviewed
169 |
170 | );
171 | }}
172 |
173 |
174 |
178 |
179 | Reviewed On
180 |
181 |
182 | {rowData => {
183 | const reviewedOn = rowData.reviewedOn
184 | ? new Date(
185 | rowData.reviewedOn
186 | ).toLocaleString()
187 | : null;
188 |
189 | return rowData.reviewedOn ? (
190 | {reviewedOn}
191 | ) : (
192 |
196 | -
197 |
198 | );
199 | }}
200 |
201 |
202 |
206 |
207 | Marks
208 |
209 |
210 | {rowData => {
211 | return rowData.marks ? (
212 |
217 | {rowData.marks}%
218 |
219 | ) : (
220 |
224 | -
225 |
226 | );
227 | }}
228 |
229 |
230 |
234 |
235 | Instructor Comments
236 |
237 |
238 | {rowData => {
239 | return rowData.comments ? (
240 | <>
241 |
248 |
258 |
259 | <>
260 |
261 | Instructor
262 | comments
263 |
264 | {
265 | rowData.comments
266 | }
267 | >
268 |
269 |
270 |
278 |
279 |
280 | >
281 | ) : (
282 |
286 | -
287 |
288 | );
289 | }}
290 |
291 | {' '}
292 | */}
293 |
294 |
295 | );
296 | })
297 | : null}
298 |
299 |
300 | );
301 | }
302 | }
303 |
304 | export default compose(withFirebase, withCourse)(StudentCoursePalette);
305 |
--------------------------------------------------------------------------------
/src/Components/Dashboard/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { withAuthorization } from '../Session';
3 |
4 | import AnnouncementsPanel from '../Announcements/AnnouncementsPanel';
5 | import mainTemplate from '../Templates/MainTemplate';
6 | import { Breadcrumb } from 'rsuite';
7 | import CoursePanel from './MainPalette/CoursePanel';
8 |
9 | const Header = (props) => (
10 |
15 | )
16 |
17 | const BreadCrumb = () => (
18 |
19 | Dashboard
20 |
21 | )
22 |
23 | const Body = (props) => (
24 |
29 | )
30 |
31 | const condition = authUser => !!authUser;
32 |
33 | const WrappedComponents = mainTemplate(Header, BreadCrumb, Body);
34 |
35 | export default withAuthorization(condition)(WrappedComponents);
--------------------------------------------------------------------------------
/src/Components/Firebase/context.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const FirebaseContext = React.createContext(null);
4 |
5 | export const withFirebase = Component => props => (
6 |
7 | {firebase => }
8 |
9 | );
10 |
11 | export default FirebaseContext;
12 |
--------------------------------------------------------------------------------
/src/Components/Firebase/firebase.js:
--------------------------------------------------------------------------------
1 | import app from "firebase/app";
2 | import "firebase/auth";
3 | import "firebase/database";
4 | import 'firebase/storage';
5 | import { Alert } from "rsuite";
6 |
7 | const config = {
8 | apiKey: process.env.REACT_APP_API_KEY,
9 | authDomain: process.env.REACT_APP_AUTH_DOMAIN,
10 | databaseURL: process.env.REACT_APP_DATABASE_URL,
11 | projectId: process.env.REACT_APP_PROJECT_ID,
12 | storageBucket: process.env.REACT_APP_STORAGE_BUCKET,
13 | messagingSenderId: process.env.REACT_APP_MESSAGING_SENDER_ID,
14 | appId: process.env.REACT_APP_APP_ID
15 | };
16 |
17 | class Firebase {
18 | constructor() {
19 | app.initializeApp(config);
20 | this.auth = app.auth();
21 | this.db = app.database();
22 | this.storage = app.storage();
23 | }
24 |
25 | doCreateUserWithEmailAndPassword = (email, password) => {
26 | return this.auth.createUserWithEmailAndPassword(email, password);
27 | };
28 | doSignInWithEmailAndPassword = (email, password) => {
29 | return this.auth.signInWithEmailAndPassword(email, password);
30 | };
31 |
32 | doSignOut = () => {
33 | this.auth.signOut();
34 | Alert.success('Successfully signed out...')
35 | };
36 |
37 | doPasswordReset = email => {
38 | return this.auth.sendPasswordResetEmail(email);
39 | };
40 |
41 | doPasswordUpdate = password => {
42 | return this.auth.currentUser.updatePassword(password);
43 | };
44 |
45 | user = uid => this.db.ref(`users/${uid}`);
46 | users = () => this.db.ref("users");
47 |
48 | course = cid => this.db.ref(`courses/${cid}`);
49 | courses = () => this.db.ref("courses");
50 | courseCurriculums = cid => this.db.ref(`courses/${cid}/curriculum`);
51 |
52 | assignments = cid => this.db.ref(`courses/${cid}/assignments`)
53 | // Database ref
54 | assignmentRef = () => this.storage.ref().child('assignments');
55 |
56 | onAuthUserListener = (next, fallback) =>{
57 | this.auth.onAuthStateChanged(authUser => {
58 | if (authUser) {
59 | this.user(authUser.uid).on('value', snapshot => {
60 | const dbUser = snapshot.val();
61 | if (dbUser) {
62 | if (!dbUser.roles) {
63 | dbUser.roles = {};
64 | }
65 |
66 | authUser = {
67 | uid: authUser.uid,
68 | email: authUser.email,
69 | ...dbUser
70 | };
71 |
72 | next(authUser);
73 | }
74 | });
75 | }
76 | else {
77 | fallback();
78 | }
79 | });
80 | }
81 |
82 | onAuthCourseListener = (next, fallback) => {
83 | this.auth.onAuthStateChanged(() => {
84 | this.courses().on("value", (snapshot, prevChildKey) => {
85 | const coursesObject = snapshot.val();
86 |
87 | if(coursesObject){
88 | const coursesList = Object.keys(coursesObject).map(key => ({
89 | ...coursesObject[key],
90 | uid: key,
91 | }));
92 | next(coursesList);
93 | }
94 | else{
95 | fallback();
96 | }
97 | });
98 | });
99 | };
100 | }
101 |
102 | export default Firebase;
103 |
--------------------------------------------------------------------------------
/src/Components/Firebase/index.js:
--------------------------------------------------------------------------------
1 | import FirebaseContext, { withFirebase } from './context';
2 | import Firebase from './firebase';
3 |
4 | export default Firebase;
5 | export { withFirebase, FirebaseContext };
--------------------------------------------------------------------------------
/src/Components/LandingPage/HomePage.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import TopNavBar from '../Navbar/TopNavBar';
3 | import { Container, Button } from 'rsuite';
4 | import headerImg from '../../assets/lp.png'
5 | import CourseShowcase from '../Courses/CourseShowcase';
6 | import withCourse from '../Courses/Context/withCourse';
7 | import { NavLink } from 'react-router-dom';
8 | class HomePage extends Component {
9 | render(){
10 | const coursesList = this.props.coursesList;
11 |
12 | return(
13 |
14 |
15 |
16 |
17 | The Most Trusted
Simplified LMS
18 | The #1 featured product on ProductHunt and a featured LMS on People's Choice Magazine.
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | Recently Added
29 |
30 |
31 |
32 | {
33 | coursesList && coursesList.length?
34 | Array.from(coursesList).reverse().map((course, i) => {
35 | if(i < 4){
36 | return (
37 |
38 | )
39 | }else{
40 | return null;
41 | }
42 | })
43 | :null
44 | }
45 |
46 |
47 |
48 | )
49 | }
50 | }
51 |
52 | export default withCourse(HomePage);
53 |
54 |
--------------------------------------------------------------------------------
/src/Components/Login/LoginForm.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import {
3 | Form,
4 | FormGroup,
5 | FormControl,
6 | ControlLabel,
7 | Button,
8 | ButtonToolbar,
9 | Alert,
10 | Message
11 | } from 'rsuite';
12 |
13 | const INITIAL_STATE = {
14 | email: '',
15 | password: '',
16 | error: '',
17 | };
18 |
19 | export default class LoginForm extends Component{
20 | constructor(props){
21 | super(props);
22 |
23 | this.state = {
24 | errorPlacement: 'bottomStart',
25 | showEmailError: false,
26 | showPasswordError: false,
27 | isFormSubmit: false,
28 |
29 | ...INITIAL_STATE,
30 | }
31 | this.formSubmit = this.formSubmit.bind(this);
32 | }
33 |
34 | formSubmit(e) {
35 | const { email, password } = this.state;
36 |
37 | this.props.firebase.doSignInWithEmailAndPassword(email, password).then(() => {
38 | this.setState({ ...INITIAL_STATE });
39 | Alert.success('Successfully logged in. Redirecting...', 5000)
40 | this.props.history.push('/dashboard');
41 | }).catch(error => {
42 | let message = error.message;
43 | this.setState({ error: message });
44 | });
45 |
46 | e.preventDefault();
47 | };
48 |
49 | render(){
50 | const { showEmailError, showPasswordError, errorPlacement } = this.state;
51 | const emailErrorMessage = showEmailError ? 'This field is required' : null;
52 | const passwordErrorMessage = showPasswordError ? 'This field is required' : null;
53 |
54 | const isInvalid =
55 | !this.state.email ||
56 | !this.state.password
57 |
58 | return(
59 |
105 | )
106 | }
107 | }
--------------------------------------------------------------------------------
/src/Components/Login/index.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import { withFirebase } from '../Firebase';
3 | import { withRouter } from 'react-router-dom';
4 | import LoginForm from './LoginForm';
5 | import TopNavBar from '../Navbar/TopNavBar';
6 | import {Container, Content, FlexboxGrid, Panel, Header, Footer} from 'rsuite';
7 | import { compose } from 'recompose';
8 | import { AuthUserContext } from '../Session';
9 |
10 | class LoginPage extends Component {
11 | componentDidMount(){
12 | this.listener = this.props.firebase.auth.onAuthStateChanged(authUser => {
13 | if(authUser){
14 | this.props.history.push('/dashboard');
15 | }
16 | })
17 | }
18 |
19 | componentWillUnmount(){
20 | this.listener();
21 | }
22 |
23 | render(){
24 | return(
25 | {authUser => {
26 | const View = !authUser? (
27 |
28 |
29 |
32 |
33 |
34 |
35 | Login} bordered>
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | ): ''
45 |
46 | return View;
47 | }}
48 |
49 |
50 |
51 | )
52 | }
53 | }
54 |
55 | const Login = compose(
56 | withRouter,
57 | withFirebase,
58 | )(LoginPage);
59 |
60 | export default (Login);
--------------------------------------------------------------------------------
/src/Components/Navbar/SideNavigation.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { Sidenav, Icon, Nav, Avatar } from 'rsuite';
3 | import SignOutButton from '../Signout/SignOutButton';
4 | import { NavLink } from 'react-router-dom';
5 |
6 | export default class SideNavigation extends Component {
7 | constructor() {
8 | super();
9 | this.state = {
10 | expanded: true
11 | };
12 | }
13 |
14 | render() {
15 | const { expanded } = this.state;
16 | let role = this.props.roles.userRole;
17 | role = role[0].toUpperCase() + role.substr(1);
18 |
19 | return (
20 |
21 |
33 |
34 |
41 |
47 |
51 | Webcademy
52 |
53 |
54 |
55 |
56 |
57 |
68 |
77 | {role}
78 |
79 |
80 |
108 |
109 | {role === 'Admin' ? (
110 |
137 | ) : null}
138 |
139 |
147 |
148 |
149 |
150 | );
151 | }
152 | }
153 |
154 | const NavLinkStyle = {
155 | textDecoration: 'none',
156 | color: '#8e8e93'
157 | };
158 |
--------------------------------------------------------------------------------
/src/Components/Navbar/TopNavBar.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import {Icon, Navbar, Nav} from 'rsuite';
3 | import {NavLink} from 'react-router-dom';
4 | import AuthUserContext from '../Session/context';
5 | import { withFirebase } from '../Firebase';
6 |
7 | class TopNavBar extends Component {
8 | render(){
9 | return(
10 |
11 | {
12 | authUser => {
13 | return(
14 |
15 |
16 |
17 |
18 |
19 | Webcademy
20 |
21 |
22 |
23 |
46 |
47 |
48 | )
49 | }
50 | }
51 |
52 | )
53 | }
54 | }
55 |
56 | export default withFirebase(TopNavBar);
--------------------------------------------------------------------------------
/src/Components/Session/context.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const AuthUserContext = React.createContext(null);
4 |
5 | export default AuthUserContext;
--------------------------------------------------------------------------------
/src/Components/Session/index.js:
--------------------------------------------------------------------------------
1 | import AuthUserContext from './context';
2 | import withAuthentication from './withAuthentication';
3 | import withAuthorization from './withAuthorization';
4 |
5 | export {AuthUserContext, withAuthentication, withAuthorization};
--------------------------------------------------------------------------------
/src/Components/Session/withAuthentication.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import AuthUserContext from './context';
3 | import { withFirebase } from '../Firebase';
4 |
5 | const withAuthentication = Component => {
6 | class WithAuthentication extends React.Component {
7 | constructor(props) {
8 | super(props);
9 | this.state = {
10 | authUser: null
11 | };
12 | }
13 |
14 | componentDidMount() {
15 | this.listener = this.props.firebase.onAuthUserListener(
16 | authUser => {
17 | this.setState({ authUser });
18 | },
19 | () => {
20 | this.setState({ authUser: null });
21 | }
22 | );
23 | }
24 |
25 | render() {
26 | return (
27 |
28 | {this.state.authUser? : }
29 |
30 |
31 | );
32 | }
33 | }
34 | return withFirebase(WithAuthentication);
35 | };
36 | export default withAuthentication;
37 |
--------------------------------------------------------------------------------
/src/Components/Session/withAuthorization.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { compose } from 'recompose';
3 | import { withFirebase } from '../Firebase';
4 | import { withRouter } from 'react-router-dom';
5 | import AuthUserContext from './context';
6 | import { Loader } from 'rsuite';
7 |
8 | const withAuthorization = condition => Component => {
9 | class WithAuthorization extends React.Component {
10 | componentDidMount() {
11 | this.listener = this.props.firebase.onAuthUserListener(
12 | authUser => {
13 | if (!condition(authUser)) {
14 | this.props.history.push('/login');
15 | }
16 | },
17 | () => this.props.history.push('/login')
18 | );
19 | }
20 |
21 | render() {
22 | return (
23 |
24 | {authUser =>
25 | condition(authUser) ? (
26 |
27 | ) :
28 | }
29 |
30 | );
31 | }
32 | }
33 | return compose(withRouter, withFirebase)(WithAuthorization);
34 | };
35 |
36 | export default withAuthorization;
37 |
--------------------------------------------------------------------------------
/src/Components/Signout/SignOutButton.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { withFirebase } from '../Firebase';
3 | import {Nav, Icon} from 'rsuite';
4 |
5 | const SignOutButton = ({ firebase }) => (
6 | }>
7 | Sign out
8 |
9 | );
10 |
11 | export default withFirebase(SignOutButton);
--------------------------------------------------------------------------------
/src/Components/Signup/SignupForm.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import {
3 | Form,
4 | FormGroup,
5 | FormControl,
6 | ControlLabel,
7 | Button,
8 | ButtonToolbar,
9 | Message,
10 | Alert,
11 | } from 'rsuite';
12 |
13 | const INITIAL_STATE = {
14 | name: '',
15 | email: '',
16 | password: '',
17 | isAdmin: false,
18 | error: '',
19 | };
20 |
21 | export default class SignupForm extends Component{
22 | constructor(props){
23 | super(props);
24 |
25 | this.state = {
26 | errorPlacement: 'bottomStart',
27 | showEmailError: false,
28 | isFormSubmit: false,
29 | referrer: '',
30 | ...INITIAL_STATE
31 | }
32 |
33 | this.formSubmit = this.formSubmit.bind(this);
34 | }
35 |
36 | formSubmit(e) {
37 | let { name, email, password, isAdmin, referrer } = this.state;
38 | const roles = {};
39 |
40 | isAdmin = referrer === 'INSTR' ? true : false;
41 |
42 | if(isAdmin){
43 | roles['userRole'] = 'admin';
44 | }
45 | else{
46 | roles['userRole'] = 'student';
47 | }
48 |
49 | this.props.firebase.doCreateUserWithEmailAndPassword(email, password).then(authUser => {
50 | return this.props.firebase.user(authUser.user.uid).set({name, email, roles})
51 | }).then(() => {
52 | this.setState({ ...INITIAL_STATE });
53 | Alert.success('Successfully registered. Redirecting...', 5000)
54 | this.props.history.push('/dashboard');
55 |
56 | }).catch(error => {
57 | let message = error.message;
58 | this.setState({error: message});
59 | });
60 |
61 | e.preventDefault();
62 | };
63 |
64 | render(){
65 | const { showEmailError, errorPlacement } = this.state;
66 | const emailErrorMessage = showEmailError ? 'This field is required' : null;
67 | const password = this.state.password;
68 | const passwordErrorMessage = (password && password.length > 2 && password.length < 10) ? 'Password must contain at least 10 characters' : '';
69 |
70 | const isInvalid =
71 | !this.state.name ||
72 | !this.state.email ||
73 | !(this.state.password && this.state.password.length >= 10);
74 |
75 | return(
76 |
138 | )
139 | }
140 | }
--------------------------------------------------------------------------------
/src/Components/Signup/index.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import { withFirebase } from '../Firebase';
3 | import SignupForm from './SignupForm';
4 | import TopNavBar from '../Navbar/TopNavBar';
5 | import { withRouter } from 'react-router-dom';
6 |
7 | import {Container, Content, FlexboxGrid, Panel, Header, Footer} from 'rsuite';
8 | import { compose } from 'recompose';
9 | import { AuthUserContext } from '../Session';
10 |
11 | class SignUpPage extends Component {
12 | componentDidMount(){
13 | this.listener = this.props.firebase.auth.onAuthStateChanged(authUser => {
14 | if(authUser){
15 | this.props.history.push('/dashboard');
16 | }
17 | })
18 | }
19 |
20 | componentWillUnmount(){
21 | this.listener();
22 | }
23 |
24 | render(){
25 | return(
26 | {authUser => {
27 | const View = !authUser? (
28 |
29 |
30 |
33 |
34 |
35 |
36 | Signup} bordered>
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | ):
46 | ''
47 | return View
48 | }
49 | }
50 |
51 |
52 |
53 |
54 | )
55 | }
56 | }
57 |
58 | const SignUp = compose(
59 | withRouter,
60 | withFirebase,
61 | )(SignUpPage);
62 |
63 | export default (SignUp);
64 |
--------------------------------------------------------------------------------
/src/Components/Templates/MainTemplate.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import SideNavigation from '../Navbar/SideNavigation';
3 | import TopNavBar from '../Navbar/TopNavBar'
4 | import { Container} from 'rsuite';
5 | import { AuthUserContext, } from '../Session';
6 |
7 | const mainTemplate = (Header, BreadCrumb, Body) =>{
8 | class MainTemplate extends React.Component{
9 |
10 | render(){
11 | return(
12 |
13 | {authUser => {
14 | const flexDir = authUser? 'row': 'column';
15 |
16 | return(
17 |
18 | {
19 | authUser?
20 |
21 | :
22 | }
23 |
24 |
25 | {authUser?
26 |
27 |
29 | : null
30 | }
31 |
32 |
33 |
34 | {
35 | authUser?
36 |
37 |
38 |
39 | : null
40 | }
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | )
49 | }}
50 |
51 | )
52 | }
53 | }
54 | return MainTemplate;
55 | }
56 |
57 | export default mainTemplate;
--------------------------------------------------------------------------------
/src/Components/TextField.js:
--------------------------------------------------------------------------------
1 | import React, {Component, PureComponent} from 'react';
2 | import {FormGroup, ControlLabel, FormControl} from 'rsuite';
3 |
4 | export default class TextField extends PureComponent {
5 | render() {
6 | const { name, label, accepter, ...props } = this.props;
7 | return (
8 |
9 | {label}
10 |
11 |
12 | );
13 | }
14 | }
--------------------------------------------------------------------------------
/src/assets/006023-Free-vector_-Macbook-Ipad-and-Iphone-by-Frexy-Dribbble-removebg-preview (1).png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cheezeburger/React-Learning-Management-System/4f2fdc4a222a445af1854d0d606d780ce5e92b88/src/assets/006023-Free-vector_-Macbook-Ipad-and-Iphone-by-Frexy-Dribbble-removebg-preview (1).png
--------------------------------------------------------------------------------
/src/assets/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/lp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cheezeburger/React-Learning-Management-System/4f2fdc4a222a445af1854d0d606d780ce5e92b88/src/assets/lp.png
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 | import * as serviceWorker from './serviceWorker';
5 | import Firebase, { FirebaseContext } from './Components/Firebase';
6 |
7 | ReactDOM.render(
8 |
9 |
10 | ,
11 | document.getElementById('root'),
12 | );
13 | // If you want your app to work offline and load faster, you can change
14 | // unregister() to register() below. Note this comes with some pitfalls.
15 | // Learn more about service workers: https://bit.ly/CRA-PWA
16 | serviceWorker.unregister();
17 |
--------------------------------------------------------------------------------
/src/serviceWorker.js:
--------------------------------------------------------------------------------
1 | // This optional code is used to register a service worker.
2 | // register() is not called by default.
3 |
4 | // This lets the app load faster on subsequent visits in production, and gives
5 | // it offline capabilities. However, it also means that developers (and users)
6 | // will only see deployed updates on subsequent visits to a page, after all the
7 | // existing tabs open on the page have been closed, since previously cached
8 | // resources are updated in the background.
9 |
10 | // To learn more about the benefits of this model and instructions on how to
11 | // opt-in, read https://bit.ly/CRA-PWA
12 |
13 | const isLocalhost = Boolean(
14 | window.location.hostname === 'localhost' ||
15 | // [::1] is the IPv6 localhost address.
16 | window.location.hostname === '[::1]' ||
17 | // 127.0.0.0/8 are considered localhost for IPv4.
18 | window.location.hostname.match(
19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
20 | )
21 | );
22 |
23 | export function register(config) {
24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
25 | // The URL constructor is available in all browsers that support SW.
26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
27 | if (publicUrl.origin !== window.location.origin) {
28 | // Our service worker won't work if PUBLIC_URL is on a different origin
29 | // from what our page is served on. This might happen if a CDN is used to
30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374
31 | return;
32 | }
33 |
34 | window.addEventListener('load', () => {
35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
36 |
37 | if (isLocalhost) {
38 | // This is running on localhost. Let's check if a service worker still exists or not.
39 | checkValidServiceWorker(swUrl, config);
40 |
41 | // Add some additional logging to localhost, pointing developers to the
42 | // service worker/PWA documentation.
43 | navigator.serviceWorker.ready.then(() => {
44 | console.log(
45 | 'This web app is being served cache-first by a service ' +
46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA'
47 | );
48 | });
49 | } else {
50 | // Is not localhost. Just register service worker
51 | registerValidSW(swUrl, config);
52 | }
53 | });
54 | }
55 | }
56 |
57 | function registerValidSW(swUrl, config) {
58 | navigator.serviceWorker
59 | .register(swUrl)
60 | .then(registration => {
61 | registration.onupdatefound = () => {
62 | const installingWorker = registration.installing;
63 | if (installingWorker == null) {
64 | return;
65 | }
66 | installingWorker.onstatechange = () => {
67 | if (installingWorker.state === 'installed') {
68 | if (navigator.serviceWorker.controller) {
69 | // At this point, the updated precached content has been fetched,
70 | // but the previous service worker will still serve the older
71 | // content until all client tabs are closed.
72 | console.log(
73 | 'New content is available and will be used when all ' +
74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
75 | );
76 |
77 | // Execute callback
78 | if (config && config.onUpdate) {
79 | config.onUpdate(registration);
80 | }
81 | } else {
82 | // At this point, everything has been precached.
83 | // It's the perfect time to display a
84 | // "Content is cached for offline use." message.
85 | console.log('Content is cached for offline use.');
86 |
87 | // Execute callback
88 | if (config && config.onSuccess) {
89 | config.onSuccess(registration);
90 | }
91 | }
92 | }
93 | };
94 | };
95 | })
96 | .catch(error => {
97 | console.error('Error during service worker registration:', error);
98 | });
99 | }
100 |
101 | function checkValidServiceWorker(swUrl, config) {
102 | // Check if the service worker can be found. If it can't reload the page.
103 | fetch(swUrl, {
104 | headers: { 'Service-Worker': 'script' }
105 | })
106 | .then(response => {
107 | // Ensure service worker exists, and that we really are getting a JS file.
108 | const contentType = response.headers.get('content-type');
109 | if (
110 | response.status === 404 ||
111 | (contentType != null && contentType.indexOf('javascript') === -1)
112 | ) {
113 | // No service worker found. Probably a different app. Reload the page.
114 | navigator.serviceWorker.ready.then(registration => {
115 | registration.unregister().then(() => {
116 | window.location.reload();
117 | });
118 | });
119 | } else {
120 | // Service worker found. Proceed as normal.
121 | registerValidSW(swUrl, config);
122 | }
123 | })
124 | .catch(() => {
125 | console.log(
126 | 'No internet connection found. App is running in offline mode.'
127 | );
128 | });
129 | }
130 |
131 | export function unregister() {
132 | if ('serviceWorker' in navigator) {
133 | navigator.serviceWorker.ready.then(registration => {
134 | registration.unregister();
135 | });
136 | }
137 | }
138 |
--------------------------------------------------------------------------------