├── .eslintignore ├── amplify ├── backend │ ├── storage │ │ └── lmsBucket │ │ │ ├── storage-params.json │ │ │ └── parameters.json │ ├── api │ │ └── lmsappamplify │ │ │ ├── parameters.json │ │ │ ├── transform.conf.json │ │ │ ├── resolvers │ │ │ └── README.md │ │ │ ├── stacks │ │ │ └── CustomResources.json │ │ │ └── schema.graphql │ ├── tags.json │ ├── hosting │ │ └── amplifyhosting │ │ │ └── amplifyhosting-template.json │ ├── backend-config.json │ └── auth │ │ └── lmsappamplify77733d32 │ │ └── parameters.json ├── README.md ├── .config │ └── project-config.json └── team-provider-info.json ├── src ├── learn.png ├── assests │ ├── lms.png │ ├── lms2.png │ └── hero.svg ├── models │ ├── schema.d.ts │ ├── index.js │ └── index.d.ts ├── context │ ├── UserContext.js │ └── CourseContext.js ├── routes │ ├── isLoggedIn.js │ ├── PrivateRoute.js │ ├── ProtectedRoute.js │ └── Routes.js ├── index.js ├── components │ ├── account │ │ ├── Label.js │ │ ├── Title.js │ │ ├── MessageAlert.js │ │ ├── Account.js │ │ ├── Profile.js │ │ ├── UserDetails.js │ │ ├── UneditableDetails.js │ │ └── IsEducatorSwitch.js │ ├── landingPage │ │ ├── LandingPage.js │ │ ├── Logo.js │ │ ├── Features.js │ │ ├── Benefit.js │ │ ├── Hero.js │ │ └── Footer.js │ ├── course │ │ ├── ContinueButton.js │ │ ├── Courses.js │ │ ├── CourseStatus.js │ │ ├── EditButton.js │ │ ├── PublishButton.js │ │ ├── DeleteButton.js │ │ ├── EditCourse.js │ │ ├── ShareLinkModal.js │ │ ├── CourseCard.js │ │ ├── CourseList.js │ │ ├── EnrollButton.js │ │ ├── Navlinks.js │ │ ├── NewCourse.js │ │ ├── CourseDetails.js │ │ └── CourseIntro.js │ ├── IsEducatorChip.js │ ├── calendar │ │ ├── CalendarView.js │ │ ├── appointments │ │ │ └── appointments.js │ │ └── Calendar.js │ ├── Connect.js │ ├── SignoutButton.js │ ├── Breadcrumb.js │ ├── Welcome.js │ ├── dashboard │ │ ├── Dashboard.js │ │ ├── StudentDashboard.js │ │ ├── EducatorDashboard.js │ │ ├── Drafts.js │ │ ├── Published.js │ │ └── AllCourses.js │ ├── Home.js │ ├── Error404.js │ ├── Navigation.js │ └── Details.js ├── theme.js ├── pages │ ├── discussions │ │ ├── CommentForm.js │ │ ├── AddComment.js │ │ ├── DisplayComments.js │ │ ├── DisplayDiscussions.js │ │ ├── Comment.js │ │ ├── Discussions.js │ │ ├── NewDiscussion.js │ │ └── Discussion.js │ ├── students │ │ ├── StudentCard.js │ │ ├── StudentDetails.js │ │ └── Students.js │ ├── assignments │ │ ├── Assignment.js │ │ ├── DisplayAssignments.js │ │ ├── Assignments.js │ │ └── AddAssignment.js │ ├── syllabus │ │ ├── PreviewSyllabus.js │ │ ├── DisplaySyllabus.js │ │ ├── Syllabus.js │ │ └── NewSyllabus.js │ ├── announcements │ │ ├── EditAnnouncement.js │ │ ├── Announcements.js │ │ ├── DisplayAnnouncements.js │ │ ├── Edit.js │ │ └── NewAnnouncement.js │ └── lessons │ │ ├── Lessons.js │ │ ├── LessonsList.js │ │ └── NewLesson.js ├── customHook │ └── useCourses.js ├── App.js ├── utils │ └── useStyles.js ├── index.css └── auth │ └── SignIn.js ├── public ├── logo.png ├── robots.txt ├── favicon.ico ├── manifest.json ├── index.html └── hero.svg ├── .vscode └── settings.json ├── .graphqlconfig.yml ├── .gitignore ├── package.json └── CodeSnippets.md /.eslintignore: -------------------------------------------------------------------------------- 1 | src\models -------------------------------------------------------------------------------- /amplify/backend/storage/lmsBucket/storage-params.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /src/learn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/palakgupta2712/lms-app-amplify/HEAD/src/learn.png -------------------------------------------------------------------------------- /public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/palakgupta2712/lms-app-amplify/HEAD/public/logo.png -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/palakgupta2712/lms-app-amplify/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /src/assests/lms.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/palakgupta2712/lms-app-amplify/HEAD/src/assests/lms.png -------------------------------------------------------------------------------- /src/assests/lms2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/palakgupta2712/lms-app-amplify/HEAD/src/assests/lms2.png -------------------------------------------------------------------------------- /src/models/schema.d.ts: -------------------------------------------------------------------------------- 1 | import { Schema } from '@aws-amplify/datastore'; 2 | 3 | export declare const schema: Schema; -------------------------------------------------------------------------------- /src/context/UserContext.js: -------------------------------------------------------------------------------- 1 | import { createContext } from "react"; 2 | 3 | export const UserContext = createContext(null); 4 | -------------------------------------------------------------------------------- /src/context/CourseContext.js: -------------------------------------------------------------------------------- 1 | import { createContext } from "react"; 2 | 3 | export const CourseContext = createContext(null); 4 | -------------------------------------------------------------------------------- /src/routes/isLoggedIn.js: -------------------------------------------------------------------------------- 1 | export const isLoggedIn = () => { 2 | if (localStorage.getItem("auth")) return true; 3 | return false; 4 | }; 5 | -------------------------------------------------------------------------------- /amplify/backend/api/lmsappamplify/parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "AppSyncApiName": "lmsappamplify", 3 | "DynamoDBBillingMode": "PAY_PER_REQUEST", 4 | "DynamoDBEnableServerSideEncryption": false 5 | } -------------------------------------------------------------------------------- /amplify/backend/tags.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Key": "user:Stack", 4 | "Value": "{project-env}" 5 | }, 6 | { 7 | "Key": "user:Application", 8 | "Value": "{project-name}" 9 | } 10 | ] -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import "./index.css"; 4 | import App from "./App"; 5 | 6 | ReactDOM.render(, document.getElementById("root")); 7 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "EduCouch LMS", 3 | "name": "EduCouch Learning Management System", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "theme_color": "#000000", 7 | "background_color": "#ffffff" 8 | } 9 | -------------------------------------------------------------------------------- /amplify/backend/api/lmsappamplify/transform.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": 5, 3 | "ElasticsearchWarning": true, 4 | "ResolverConfig": { 5 | "project": { 6 | "ConflictHandler": "AUTOMERGE", 7 | "ConflictDetection": "VERSION" 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /amplify/backend/api/lmsappamplify/resolvers/README.md: -------------------------------------------------------------------------------- 1 | Any resolvers that you add in this directory will override the ones automatically generated by Amplify CLI and will be directly copied to the cloud. 2 | For more information, visit [https://docs.amplify.aws/cli/graphql-transformer/resolvers](https://docs.amplify.aws/cli/graphql-transformer/resolvers) -------------------------------------------------------------------------------- /src/components/account/Label.js: -------------------------------------------------------------------------------- 1 | import { Box } from "@material-ui/core"; 2 | import React from "react"; 3 | 4 | function Label({ text }) { 5 | return ( 6 | 7 | 8 | {text} 9 | 10 | 11 | ); 12 | } 13 | 14 | export default Label; 15 | -------------------------------------------------------------------------------- /src/theme.js: -------------------------------------------------------------------------------- 1 | import { createMuiTheme } from "@material-ui/core/styles"; 2 | 3 | const theme = createMuiTheme({ 4 | palette: { 5 | primary: { 6 | main: "#12355B", 7 | contrastText: "#fff", 8 | }, 9 | secondary: { 10 | main: "#DDB92A", 11 | contrastText: "#000", 12 | }, 13 | }, 14 | }); 15 | export default theme; 16 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.exclude": { 3 | "amplify/.config": true, 4 | "amplify/**/*-parameters.json": true, 5 | "amplify/**/amplify.state": true, 6 | "amplify/**/transform.conf.json": true, 7 | "amplify/#current-cloud-backend": true, 8 | "amplify/backend/amplify-meta.json": true, 9 | "amplify/backend/awscloudformation": true 10 | } 11 | } -------------------------------------------------------------------------------- /amplify/README.md: -------------------------------------------------------------------------------- 1 | # Getting Started with Amplify CLI 2 | This directory was generated by [Amplify CLI](https://docs.amplify.aws/cli). 3 | 4 | Helpful resources: 5 | - Amplify documentation: https://docs.amplify.aws 6 | - Amplify CLI documentation: https://docs.amplify.aws/cli 7 | - More details on this folder & generated files: https://docs.amplify.aws/cli/reference/files 8 | - Join Amplify's community: https://amplify.aws/community/ 9 | -------------------------------------------------------------------------------- /src/components/account/Title.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Typography } from "@material-ui/core"; 3 | 4 | function Title() { 5 | return ( 6 | 7 | 11 | My Account 12 | 13 | 14 | ); 15 | } 16 | 17 | export default Title; 18 | -------------------------------------------------------------------------------- /amplify/.config/project-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "projectName": "lmsappamplify", 3 | "version": "3.1", 4 | "frontend": "javascript", 5 | "javascript": { 6 | "framework": "react", 7 | "config": { 8 | "SourceDir": "src", 9 | "DistributionDir": "build", 10 | "BuildCommand": "npm.cmd run-script build", 11 | "StartCommand": "npm.cmd run-script start" 12 | } 13 | }, 14 | "providers": [ 15 | "awscloudformation" 16 | ] 17 | } -------------------------------------------------------------------------------- /src/components/landingPage/LandingPage.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Logo from "./Logo"; 3 | import Hero from "./Hero"; 4 | import Benefit from "./Benefit"; 5 | import Features from "./Features"; 6 | import Footer from "./Footer"; 7 | 8 | function LandingPage() { 9 | return ( 10 |
11 | 12 | 13 | 14 | 15 |
16 |
17 | ); 18 | } 19 | 20 | export default LandingPage; 21 | -------------------------------------------------------------------------------- /.graphqlconfig.yml: -------------------------------------------------------------------------------- 1 | projects: 2 | lmsappamplify: 3 | schemaPath: amplify/backend/api/lmsappamplify/build/schema.graphql 4 | includes: 5 | - src/graphql/**/*.js 6 | excludes: 7 | - ./amplify/** 8 | extensions: 9 | amplify: 10 | codeGenTarget: javascript 11 | generatedFileName: '' 12 | docsFilePath: src/graphql 13 | region: ap-south-1 14 | apiId: null 15 | maxDepth: 2 16 | extensions: 17 | amplify: 18 | version: 3 19 | -------------------------------------------------------------------------------- /src/pages/discussions/CommentForm.js: -------------------------------------------------------------------------------- 1 | import { Button, TextField } from "@material-ui/core"; 2 | import React from "react"; 3 | 4 | function CommentForm() { 5 | return ( 6 | <> 7 |
8 | 14 | 15 |
16 | 17 | ); 18 | } 19 | 20 | export default CommentForm; 21 | -------------------------------------------------------------------------------- /src/customHook/useCourses.js: -------------------------------------------------------------------------------- 1 | import { DataStore } from "aws-amplify"; 2 | import { useEffect, useState } from "react"; 3 | import { Course } from "../models"; 4 | 5 | function useCourses(id) { 6 | const [courses, setCourses] = useState([]); 7 | useEffect(() => { 8 | async function getCourses() { 9 | const models = await DataStore.query(Course, id); 10 | setCourses(models); 11 | } 12 | getCourses(); 13 | }, [id]); 14 | 15 | return courses; 16 | } 17 | 18 | export default useCourses; 19 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { CssBaseline, ThemeProvider } from "@material-ui/core"; 3 | import { BrowserRouter as Router } from "react-router-dom"; 4 | import theme from "./theme"; 5 | import Routes from "./routes/Routes"; 6 | export default function App() { 7 | return ( 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /src/routes/PrivateRoute.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Route, Redirect } from "react-router-dom"; 3 | import { isLoggedIn } from "./isLoggedIn"; 4 | 5 | const PrivateRoute = ({ component: Component, ...rest }) => ( 6 | // Show the component only when the user is logged in 7 | // Otherwise, redirect the user to /signin page 8 | 11 | isLoggedIn() ? : 12 | } 13 | /> 14 | ); 15 | 16 | export default PrivateRoute; 17 | -------------------------------------------------------------------------------- /src/models/index.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | import { initSchema } from '@aws-amplify/datastore'; 3 | import { schema } from './schema'; 4 | 5 | const CourseStatus = { 6 | "PUBLISHED": "PUBLISHED", 7 | "DRAFT": "DRAFT" 8 | }; 9 | 10 | const { AssignmentModel, SyllabusModel, CommentModel, User, CourseUser, Course, Lesson, AnnouncementsModel, PostModel } = initSchema(schema); 11 | 12 | export { 13 | AssignmentModel, 14 | SyllabusModel, 15 | CommentModel, 16 | User, 17 | CourseUser, 18 | Course, 19 | Lesson, 20 | AnnouncementsModel, 21 | PostModel, 22 | CourseStatus 23 | }; -------------------------------------------------------------------------------- /src/components/account/MessageAlert.js: -------------------------------------------------------------------------------- 1 | import { Snackbar } from "@material-ui/core"; 2 | import { Alert } from "@material-ui/lab"; 3 | import React from "react"; 4 | 5 | function MessageAlert({ open, handleClose }) { 6 | return ( 7 |
8 |
9 | 15 | 16 | Successfully updated 17 | 18 | 19 |
20 | ; 21 |
22 | ); 23 | } 24 | 25 | export default MessageAlert; 26 | -------------------------------------------------------------------------------- /src/pages/students/StudentCard.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Card, CardHeader } from "@material-ui/core"; 3 | 4 | import Avatar from "boring-avatars"; 5 | 6 | function StudentCard({ user }) { 7 | return ( 8 |
9 | 10 | 18 | } 19 | title={user.name} 20 | fontWeight="900" 21 | subheader={"@" + user.username} 22 | /> 23 | 24 |
25 | ); 26 | } 27 | 28 | export default StudentCard; 29 | -------------------------------------------------------------------------------- /src/components/course/ContinueButton.js: -------------------------------------------------------------------------------- 1 | import { Button } from "@material-ui/core"; 2 | import React from "react"; 3 | import { Link } from "react-router-dom"; 4 | 5 | function ContinueButton({ course }) { 6 | return ( 7 | 8 | 25 | 26 | ); 27 | } 28 | 29 | export default ContinueButton; 30 | -------------------------------------------------------------------------------- /src/components/landingPage/Logo.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { makeStyles } from "@material-ui/core"; 3 | 4 | function Logo() { 5 | const classes = useStyles(); 6 | 7 | return ( 8 |
9 |
EduCouch
10 |
11 | ); 12 | } 13 | 14 | export default Logo; 15 | 16 | const useStyles = makeStyles((theme) => ({ 17 | container: { 18 | display: "flex", 19 | justifyContent: "center", 20 | alignItems: "center", 21 | width: "100%", 22 | backgroundColor: theme.palette.primary.main, 23 | color: "white", 24 | fontSize: "18px", 25 | height: "80px", 26 | flexWrap: "wrap", 27 | }, 28 | logo: { 29 | border: " 2px solid #DDB92A", 30 | padding: "10px", 31 | letterSpacing: "6px", 32 | }, 33 | })); 34 | -------------------------------------------------------------------------------- /src/components/IsEducatorChip.js: -------------------------------------------------------------------------------- 1 | import { Chip } from "@material-ui/core"; 2 | import React, { useContext } from "react"; 3 | import { UserContext } from "../context/UserContext"; 4 | 5 | function IsEducatorChip() { 6 | const user = useContext(UserContext); 7 | return ( 8 | 9 | {user.isEducator ? ( 10 | 17 | ) : ( 18 | 25 | )} 26 | 27 | ); 28 | } 29 | 30 | export default IsEducatorChip; 31 | -------------------------------------------------------------------------------- /src/utils/useStyles.js: -------------------------------------------------------------------------------- 1 | import { makeStyles } from "@material-ui/core"; 2 | 3 | export const useStyles = makeStyles((theme) => ({ 4 | sidebar: { 5 | height: "100%", 6 | background: theme.palette.primary.main, 7 | [theme.breakpoints.up("md")]: { 8 | height: "100vh", 9 | position: "fixed", 10 | }, 11 | }, 12 | menuButton: { 13 | color: "white", 14 | position: "fixed", 15 | background: theme.palette.primary.main, 16 | padding: "10px", 17 | width: "100%", 18 | [theme.breakpoints.up("sm")]: { 19 | display: "none", 20 | }, 21 | }, 22 | nav: { 23 | [theme.breakpoints.down("sm")]: { 24 | marginTop: "50px", 25 | }, 26 | }, 27 | root: { 28 | width: "100% ", 29 | [theme.breakpoints.up("md")]: { 30 | marginLeft: "100px", 31 | }, 32 | }, 33 | })); 34 | -------------------------------------------------------------------------------- /src/components/calendar/CalendarView.js: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import Paper from "@material-ui/core/Paper"; 3 | import { ViewState } from "@devexpress/dx-react-scheduler"; 4 | import { 5 | Scheduler, 6 | MonthView, 7 | Toolbar, 8 | DateNavigator, 9 | Appointments, 10 | TodayButton, 11 | } from "@devexpress/dx-react-scheduler-material-ui"; 12 | import { appointments } from "./appointments/appointments"; 13 | 14 | export default function Demo() { 15 | const currentDate = new Date(); 16 | 17 | return ( 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /src/components/Connect.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { makeStyles } from "@material-ui/core/styles"; 3 | import Grid from "@material-ui/core/Grid"; 4 | import Navigation from "./Navigation"; 5 | import Details from "./Details"; 6 | 7 | export default function Connect() { 8 | const classes = useStyles(); 9 | 10 | return ( 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | ); 22 | } 23 | 24 | const useStyles = makeStyles((theme) => ({ 25 | sidebar: { 26 | height: "100%", 27 | background: theme.palette.primary.main, 28 | [theme.breakpoints.up("md")]: { 29 | height: "100vh", 30 | }, 31 | }, 32 | })); 33 | -------------------------------------------------------------------------------- /src/components/account/Account.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { makeStyles } from "@material-ui/core/styles"; 3 | import Grid from "@material-ui/core/Grid"; 4 | import Navigation from "../Navigation"; 5 | import Profile from "./Profile"; 6 | 7 | export default function Account() { 8 | const classes = useStyles(); 9 | 10 | return ( 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | ); 22 | } 23 | 24 | const useStyles = makeStyles((theme) => ({ 25 | sidebar: { 26 | height: "100%", 27 | background: theme.palette.primary.main, 28 | [theme.breakpoints.up("md")]: { 29 | height: "100vh", 30 | }, 31 | }, 32 | })); 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | 25 | #amplify 26 | amplify/\#current-cloud-backend 27 | amplify/.config/local-* 28 | amplify/logs 29 | amplify/mock-data 30 | amplify/backend/amplify-meta.json 31 | amplify/backend/awscloudformation 32 | amplify/backend/.temp 33 | amplify/team-provider-info.json 34 | build/ 35 | dist/ 36 | node_modules/ 37 | aws-exports.js 38 | awsconfiguration.json 39 | amplifyconfiguration.json 40 | amplifyconfiguration.dart 41 | amplify-build-config.json 42 | amplify-gradle-config.json 43 | amplifytools.xcconfig 44 | .secret-* -------------------------------------------------------------------------------- /src/components/SignoutButton.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Auth from "@aws-amplify/auth"; 3 | import { Box, Hidden, Typography } from "@material-ui/core"; 4 | import ExitToAppIcon from "@material-ui/icons/ExitToApp"; 5 | import { useHistory } from "react-router"; 6 | 7 | function SignoutButton() { 8 | let history = useHistory(); 9 | 10 | return ( 11 |
12 | { 16 | Auth.signOut(); 17 | window.location.reload(); 18 | localStorage.removeItem("auth"); 19 | window.location.href = "/"; 20 | }} 21 | style={{ cursor: "pointer" }} 22 | > 23 | 24 | 25 | 26 | Logout 27 | 28 |
29 | ); 30 | } 31 | 32 | export default SignoutButton; 33 | -------------------------------------------------------------------------------- /src/components/Breadcrumb.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Breadcrumbs } from "@material-ui/core"; 3 | import { useParams } from "react-router"; 4 | import { Link } from "react-router-dom"; 5 | import useCourses from "../customHook/useCourses"; 6 | 7 | function Breadcrumb() { 8 | let { id } = useParams(); 9 | const course = useCourses(id); 10 | let pathCapitalized = ""; 11 | 12 | const pathArray = window.location.pathname.split("/"); 13 | if (pathArray.length === 4) { 14 | pathCapitalized = 15 | pathArray[3].charAt(0).toUpperCase() + pathArray[3].slice(1); 16 | } 17 | 18 | return ( 19 |
20 | 21 | Courses 22 | {course.title} 23 | {pathCapitalized} 24 | 25 |
26 | ); 27 | } 28 | 29 | export default Breadcrumb; 30 | -------------------------------------------------------------------------------- /src/components/account/Profile.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { makeStyles, Container, Grid } from "@material-ui/core"; 3 | import Title from "./Title"; 4 | import UserDetails from "./UserDetails"; 5 | import UneditableDetails from "./UneditableDetails"; 6 | import IsEducatorSwitch from "./IsEducatorSwitch"; 7 | 8 | export default function Profile() { 9 | const classes = useStyles(); 10 | return ( 11 | 12 | 13 | 14 | 15 | <UserDetails /> 16 | <UneditableDetails /> 17 | <IsEducatorSwitch /> 18 | </Container> 19 | </Grid> 20 | </React.Fragment> 21 | ); 22 | } 23 | 24 | const useStyles = makeStyles((theme) => ({ 25 | container: { 26 | backgroundColor: "white", 27 | height: "100vh", 28 | display: "flex", 29 | flexDirection: "column", 30 | alignItems: "center", 31 | }, 32 | })); 33 | -------------------------------------------------------------------------------- /src/components/calendar/appointments/appointments.js: -------------------------------------------------------------------------------- 1 | export const appointments = [ 2 | { 3 | title: "Customer Workshop", 4 | startDate: new Date(2021, 5, 17, 10, 23), 5 | endDate: new Date(), 6 | }, 7 | { 8 | title: "Prepare Test - 1", 9 | startDate: new Date(2021, 5, 26, 11, 0), 10 | endDate: new Date(2021, 5, 26, 13, 30), 11 | }, 12 | 13 | { 14 | title: "Submit assignment 1", 15 | startDate: new Date(2021, 5, 16, 12, 0), 16 | endDate: new Date(2018, 5, 16, 13, 0), 17 | }, 18 | { 19 | title: "Complete CourseWork - Javascript", 20 | startDate: new Date(2021, 5, 17, 15, 45), 21 | endDate: new Date(2021, 5, 18, 12, 15), 22 | }, 23 | { 24 | title: "Prepare Test - 2", 25 | startDate: new Date(2021, 6, 16, 11, 0), 26 | endDate: new Date(2021, 6, 16, 13, 30), 27 | }, 28 | { 29 | title: "Prepare Test - 3", 30 | startDate: new Date(2021, 6, 25, 11, 0), 31 | endDate: new Date(2021, 6, 25, 13, 30), 32 | }, 33 | ]; 34 | -------------------------------------------------------------------------------- /amplify/team-provider-info.json: -------------------------------------------------------------------------------- 1 | { 2 | "dev": { 3 | "awscloudformation": { 4 | "AuthRoleName": "amplify-lmsappamplify-dev-224500-authRole", 5 | "UnauthRoleArn": "arn:aws:iam::920079206663:role/amplify-lmsappamplify-dev-224500-unauthRole", 6 | "AuthRoleArn": "arn:aws:iam::920079206663:role/amplify-lmsappamplify-dev-224500-authRole", 7 | "Region": "ap-southeast-1", 8 | "DeploymentBucketName": "amplify-lmsappamplify-dev-224500-deployment", 9 | "UnauthRoleName": "amplify-lmsappamplify-dev-224500-unauthRole", 10 | "StackName": "amplify-lmsappamplify-dev-224500", 11 | "StackId": "arn:aws:cloudformation:ap-southeast-1:920079206663:stack/amplify-lmsappamplify-dev-224500/f7e3f390-18a3-11ec-b29e-0a4a11a5d598", 12 | "AmplifyAppId": "d33qe5i8dfgdw8" 13 | }, 14 | "categories": { 15 | "auth": { 16 | "lmsappamplify77733d32": {} 17 | }, 18 | "hosting": { 19 | "amplifyhosting": { 20 | "appId": "d33qe5i8dfgdw8", 21 | "type": "manual" 22 | } 23 | } 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /amplify/backend/hosting/amplifyhosting/amplifyhosting-template.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Description": "Branch stack creation for AWS Amplify Console", 4 | "Parameters": { 5 | "env": { 6 | "Type": "String" 7 | }, 8 | "appId": { 9 | "Type": "String" 10 | }, 11 | "type": { 12 | "Type": "String" 13 | } 14 | }, 15 | "Conditions": { 16 | "isManual": { 17 | "Fn::Equals": [ 18 | { 19 | "Ref": "type" 20 | }, 21 | "manual" 22 | ] 23 | } 24 | }, 25 | "Resources": { 26 | "AmplifyBranch": { 27 | "Condition": "isManual", 28 | "Type": "AWS::Amplify::Branch", 29 | "Properties": { 30 | "BranchName": { 31 | "Ref": "env" 32 | }, 33 | "AppId": { 34 | "Ref": "appId" 35 | } 36 | } 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /src/components/course/Courses.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { makeStyles } from "@material-ui/core/styles"; 3 | import Grid from "@material-ui/core/Grid"; 4 | import Navigation from "../Navigation"; 5 | import CourseList from "./CourseList"; 6 | 7 | export default function Courses() { 8 | const classes = useStyles(); 9 | return ( 10 | <React.Fragment> 11 | <Grid container> 12 | <Grid item xs={12} md={1} className={classes.sidebar}> 13 | <Navigation /> 14 | </Grid> 15 | <Grid item xs={12} md={12}> 16 | <Grid item> 17 | <CourseList /> 18 | </Grid> 19 | </Grid> 20 | </Grid> 21 | </React.Fragment> 22 | ); 23 | } 24 | 25 | const useStyles = makeStyles((theme) => ({ 26 | sidebar: { 27 | height: "100%", 28 | 29 | background: theme.palette.primary.main, 30 | [theme.breakpoints.up("md")]: { 31 | height: "100vh", 32 | position: "fixed", 33 | }, 34 | }, 35 | grid: { 36 | [theme.breakpoints.up("md")]: { 37 | marginLeft: "100px", 38 | }, 39 | }, 40 | })); 41 | -------------------------------------------------------------------------------- /src/components/course/CourseStatus.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import LabelIcon from "@material-ui/icons/Label"; 3 | import { Chip } from "@material-ui/core"; 4 | 5 | import CheckCircleIcon from "@material-ui/icons/CheckCircle"; 6 | 7 | function CourseStatus({ course }) { 8 | return ( 9 | <div> 10 | {course.status === "PUBLISHED" ? ( 11 | <Chip 12 | icon={<CheckCircleIcon style={{ color: "rgba(34, 208, 36)" }} />} 13 | label={course.status} 14 | variant="outlined" 15 | style={{ 16 | border: "1px solid rgba(34, 208, 36)", 17 | background: "rgba(34, 208, 36, 0.2)", 18 | }} 19 | /> 20 | ) : ( 21 | <Chip 22 | icon={<LabelIcon style={{ color: "rgb(0, 123, 255)" }} />} 23 | label={course.status} 24 | variant="outlined" 25 | style={{ 26 | border: "1px solid rgb(0, 123, 255)", 27 | background: "rgb(0, 123, 255, 0.2)", 28 | }} 29 | /> 30 | )} 31 | </div> 32 | ); 33 | } 34 | 35 | export default CourseStatus; 36 | -------------------------------------------------------------------------------- /src/components/Welcome.js: -------------------------------------------------------------------------------- 1 | import React, { useContext } from "react"; 2 | import { Typography } from "@material-ui/core"; 3 | import Avatar from "boring-avatars"; 4 | 5 | import { UserContext } from "../context/UserContext"; 6 | 7 | function Welcome() { 8 | const user = useContext(UserContext); 9 | 10 | return ( 11 | <div> 12 | <div 13 | style={{ 14 | backgroundImage: "url(/hero.svg)", 15 | color: "white", 16 | display: "flex", 17 | justifyContent: "flex-end", 18 | alignItems: "baseline", 19 | padding: "20px", 20 | height: "20vh", 21 | }} 22 | > 23 | <Typography 24 | variant="h4" 25 | style={{ display: "flex", alignItems: "center" }} 26 | > 27 | Welcome! {user.name} 28 | <Avatar 29 | size={60} 30 | name={user.username} 31 | variant="beam" 32 | colors={["#A70267", "#F10C49", "#FB6B41", "#F6D86B", "#339194"]} 33 | style={{ marginLeft: "20px" }} 34 | />{" "} 35 | </Typography> 36 | </div> 37 | </div> 38 | ); 39 | } 40 | 41 | export default Welcome; 42 | -------------------------------------------------------------------------------- /amplify/backend/backend-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "auth": { 3 | "lmsappamplify77733d32": { 4 | "service": "Cognito", 5 | "providerPlugin": "awscloudformation", 6 | "dependsOn": [], 7 | "customAuth": false 8 | } 9 | }, 10 | "api": { 11 | "lmsappamplify": { 12 | "service": "AppSync", 13 | "providerPlugin": "awscloudformation", 14 | "output": { 15 | "authConfig": { 16 | "defaultAuthentication": { 17 | "authenticationType": "API_KEY", 18 | "apiKeyConfig": { 19 | "apiKeyExpirationDays": 30, 20 | "description": "api key description" 21 | } 22 | }, 23 | "additionalAuthenticationProviders": [ 24 | { 25 | "authenticationType": "AWS_IAM" 26 | } 27 | ] 28 | } 29 | } 30 | } 31 | }, 32 | "storage": { 33 | "lmsBucket": { 34 | "service": "S3", 35 | "providerPlugin": "awscloudformation" 36 | } 37 | }, 38 | "hosting": { 39 | "amplifyhosting": { 40 | "service": "amplifyhosting", 41 | "providerPlugin": "awscloudformation", 42 | "type": "manual" 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /src/components/calendar/Calendar.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { makeStyles } from "@material-ui/core/styles"; 3 | import Grid from "@material-ui/core/Grid"; 4 | import Navigation from "../Navigation"; 5 | import { Container } from "@material-ui/core"; 6 | import CalendarView from "./CalendarView"; 7 | 8 | export default function Calendar() { 9 | const classes = useStyles(); 10 | 11 | return ( 12 | <React.Fragment> 13 | <Grid container> 14 | <Grid item xs={12} md={1} className={classes.sidebar}> 15 | <Navigation /> 16 | </Grid> 17 | <Grid item xs={12} md={11} className={classes.grid}> 18 | <Container align="center"> 19 | <CalendarView /> 20 | </Container> 21 | </Grid> 22 | </Grid> 23 | </React.Fragment> 24 | ); 25 | } 26 | 27 | const useStyles = makeStyles((theme) => ({ 28 | sidebar: { 29 | height: "100%", 30 | 31 | background: theme.palette.primary.main, 32 | [theme.breakpoints.up("md")]: { 33 | height: "100vh", 34 | position: "fixed", 35 | }, 36 | }, 37 | grid: { 38 | [theme.breakpoints.up("md")]: { 39 | marginLeft: "100px", 40 | }, 41 | }, 42 | })); 43 | -------------------------------------------------------------------------------- /src/routes/ProtectedRoute.js: -------------------------------------------------------------------------------- 1 | import Auth from "@aws-amplify/auth"; 2 | import { DataStore } from "@aws-amplify/datastore"; 3 | import React, { useEffect, useState } from "react"; 4 | import { Redirect, Route } from "react-router-dom"; 5 | import Error404 from "../components/Error404"; 6 | import { User } from "../models"; 7 | import { isLoggedIn } from "./isLoggedIn"; 8 | 9 | function ProtectedRoute({ component: Component, ...rest }) { 10 | const [currentUser, setCurrentUser] = useState([]); 11 | useEffect(() => { 12 | getUser(); 13 | }, []); 14 | async function getUser() { 15 | const user = await Auth.currentAuthenticatedUser(); 16 | const currentUser = (await DataStore.query(User)).filter( 17 | (c) => c.username === user.username 18 | ); 19 | setCurrentUser(currentUser); 20 | } 21 | 22 | return isLoggedIn() ? ( 23 | <> 24 | {currentUser.map((user, index) => ( 25 | <div key={index}> 26 | <Route 27 | {...rest} 28 | render={(props) => 29 | user.isEducator ? <Component {...props} /> : <Error404 /> 30 | } 31 | /> 32 | </div> 33 | ))} 34 | </> 35 | ) : ( 36 | <Redirect to="/signin" /> 37 | ); 38 | } 39 | export default ProtectedRoute; 40 | -------------------------------------------------------------------------------- /src/pages/assignments/Assignment.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import DescriptionIcon from "@material-ui/icons/Description"; 3 | import { Link } from "@material-ui/core"; 4 | import GetAppIcon from "@material-ui/icons/GetApp"; 5 | import { Storage } from "aws-amplify"; 6 | 7 | function Assignment({ assignment }) { 8 | const [url, setUrl] = useState(); 9 | 10 | useEffect(() => { 11 | loadFileUrl(assignment); 12 | }, [assignment]); 13 | 14 | const loadFileUrl = async (assignment) => { 15 | await Storage.get(assignment.S3Key).then((res) => { 16 | setUrl(res); 17 | console.log(res); 18 | }); 19 | }; 20 | return ( 21 | <div 22 | style={{ 23 | display: "flex", 24 | flexDirection: "column", 25 | justifyContent: "center", 26 | alignItems: "center", 27 | }} 28 | > 29 | <DescriptionIcon style={{ fontSize: "50px" }} /> 30 | <div style={{ display: "flex", marginTop: "10px" }}> 31 | {assignment.S3Key.split("/")[1]} 32 | <Link href={url}> 33 | <GetAppIcon 34 | style={{ 35 | marginLeft: "5px", 36 | }} 37 | /> 38 | </Link> 39 | </div> 40 | </div> 41 | ); 42 | } 43 | 44 | export default Assignment; 45 | -------------------------------------------------------------------------------- /src/components/account/UserDetails.js: -------------------------------------------------------------------------------- 1 | import React, { useContext } from "react"; 2 | import Avatar from "boring-avatars"; 3 | import { UserContext } from "../../context/UserContext"; 4 | import { Box, Typography } from "@material-ui/core"; 5 | import IsEducatorChip from "../IsEducatorChip"; 6 | 7 | function UserDetails() { 8 | const user = useContext(UserContext); 9 | 10 | return ( 11 | <React.Fragment> 12 | <Box 13 | style={{ 14 | display: "flex", 15 | padding: "10px", 16 | justifyContent: "space-evenly", 17 | }} 18 | > 19 | <Box 20 | style={{ display: "flex", flexDirection: "column", padding: "14px" }} 21 | > 22 | <Avatar 23 | size={60} 24 | name={user.username} 25 | variant="beam" 26 | colors={["#A70267", "#F10C49", "#FB6B41", "#F6D86B", "#339194"]} 27 | /> 28 | </Box> 29 | 30 | <Box 31 | style={{ display: "flex", flexDirection: "column", padding: "10px" }} 32 | > 33 | <Typography style={{ fontWeight: 700 }}>{user.name}</Typography> 34 | <Typography variant="subtitle2">@{user.username}</Typography> 35 | <IsEducatorChip /> 36 | </Box> 37 | </Box> 38 | </React.Fragment> 39 | ); 40 | } 41 | 42 | export default UserDetails; 43 | -------------------------------------------------------------------------------- /src/components/account/UneditableDetails.js: -------------------------------------------------------------------------------- 1 | import React, { useContext } from "react"; 2 | import { Grid, TextField, makeStyles } from "@material-ui/core"; 3 | import { UserContext } from "../../context/UserContext"; 4 | import Label from "./Label"; 5 | 6 | function UneditableDetails() { 7 | const user = useContext(UserContext); 8 | const classes = useStyles(); 9 | 10 | return ( 11 | <React.Fragment> 12 | <Grid container className={classes.grid}> 13 | <Grid item xs={12}> 14 | <Label text="Display Name" /> 15 | <TextField 16 | defaultValue={user.name} 17 | variant="outlined" 18 | size="small" 19 | className={classes.textField} 20 | disabled 21 | /> 22 | </Grid> 23 | </Grid> 24 | <Grid container className={classes.grid}> 25 | <Grid item xs={12}> 26 | <Label text="Email" /> 27 | <TextField 28 | defaultValue={user.email} 29 | variant="outlined" 30 | size="small" 31 | disabled 32 | className={classes.textField} 33 | /> 34 | </Grid> 35 | </Grid> 36 | </React.Fragment> 37 | ); 38 | } 39 | 40 | export default UneditableDetails; 41 | const useStyles = makeStyles((theme) => ({ 42 | grid: { marginBottom: "10px", padding: "5px" }, 43 | textField: { 44 | width: "100% ", 45 | [theme.breakpoints.up("sm")]: { 46 | width: "75% ", 47 | }, 48 | }, 49 | })); 50 | -------------------------------------------------------------------------------- /amplify/backend/storage/lmsBucket/parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "bucketName": "lmsbucket", 3 | "authPolicyName": "s3_amplify_1f6d5d10", 4 | "unauthPolicyName": "s3_amplify_1f6d5d10", 5 | "authRoleName": { 6 | "Ref": "AuthRoleName" 7 | }, 8 | "unauthRoleName": { 9 | "Ref": "UnauthRoleName" 10 | }, 11 | "selectedGuestPermissions": [ 12 | "s3:PutObject", 13 | "s3:GetObject", 14 | "s3:ListBucket", 15 | "s3:DeleteObject" 16 | ], 17 | "selectedAuthenticatedPermissions": [ 18 | "s3:PutObject", 19 | "s3:GetObject", 20 | "s3:ListBucket", 21 | "s3:DeleteObject" 22 | ], 23 | "s3PermissionsAuthenticatedPublic": "s3:PutObject,s3:GetObject,s3:DeleteObject", 24 | "s3PublicPolicy": "Public_policy_66272107", 25 | "s3PermissionsAuthenticatedUploads": "s3:PutObject", 26 | "s3UploadsPolicy": "Uploads_policy_66272107", 27 | "s3PermissionsAuthenticatedProtected": "s3:PutObject,s3:GetObject,s3:DeleteObject", 28 | "s3ProtectedPolicy": "Protected_policy_662520b3", 29 | "s3PermissionsAuthenticatedPrivate": "s3:PutObject,s3:GetObject,s3:DeleteObject", 30 | "s3PrivatePolicy": "Private_policy_662520b3", 31 | "AuthenticatedAllowList": "ALLOW", 32 | "s3ReadPolicy": "read_policy_66272107", 33 | "s3PermissionsGuestPublic": "s3:PutObject,s3:GetObject,s3:DeleteObject", 34 | "s3PermissionsGuestUploads": "s3:PutObject", 35 | "GuestAllowList": "ALLOW", 36 | "triggerFunction": "NONE" 37 | } -------------------------------------------------------------------------------- /src/components/dashboard/Dashboard.js: -------------------------------------------------------------------------------- 1 | import React, { useContext } from "react"; 2 | import { makeStyles } from "@material-ui/core/styles"; 3 | import Grid from "@material-ui/core/Grid"; 4 | import Navigation from "../Navigation"; 5 | import Welcome from "../Welcome"; 6 | import EducatorDashboard from "./EducatorDashboard"; 7 | import StudentDashboard from "./StudentDashboard"; 8 | import { UserContext } from "../../context/UserContext"; 9 | import NewCourse from "../course/NewCourse"; 10 | 11 | export default function Dashboard() { 12 | const user = useContext(UserContext); 13 | const classes = useStyles(); 14 | 15 | return ( 16 | <React.Fragment> 17 | <Grid container> 18 | <Grid item xs={12} md={1} className={classes.sidebar}> 19 | <Navigation /> 20 | </Grid> 21 | <Grid item xs={12} md={12} className={classes.grid}> 22 | <Welcome /> 23 | {/* New Course button removed from courses */} 24 | {user.isEducator && <NewCourse />} 25 | {user.isEducator ? <EducatorDashboard /> : <StudentDashboard />} 26 | </Grid> 27 | </Grid> 28 | </React.Fragment> 29 | ); 30 | } 31 | 32 | const useStyles = makeStyles((theme) => ({ 33 | sidebar: { 34 | height: "100%", 35 | 36 | background: theme.palette.primary.main, 37 | [theme.breakpoints.up("md")]: { 38 | height: "100vh", 39 | position: "fixed", 40 | }, 41 | }, 42 | grid: { 43 | [theme.breakpoints.up("md")]: { 44 | marginLeft: "100px", 45 | }, 46 | }, 47 | })); 48 | -------------------------------------------------------------------------------- /src/components/Home.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Button, Grid, makeStyles } from "@material-ui/core"; 3 | import Navigation from "./Navigation"; 4 | import LandingPage from "./landingPage/LandingPage"; 5 | import { isLoggedIn } from "../routes/isLoggedIn"; 6 | import home from "../assests/home.svg"; 7 | import { Link } from "react-router-dom"; 8 | 9 | export default function Account() { 10 | const classes = useStyles(); 11 | 12 | return !isLoggedIn() ? ( 13 | <LandingPage /> 14 | ) : ( 15 | <React.Fragment> 16 | <Grid container> 17 | <Grid item xs={12} md={1} className={classes.sidebar}> 18 | <Navigation /> 19 | </Grid> 20 | <Grid item xs={12} md={10}> 21 | <div 22 | style={{ 23 | display: "flex", 24 | flexDirection: "column", 25 | justifyContent: "center", 26 | alignItems: "center", 27 | }} 28 | > 29 | <img src={home} alt="home-page" height="70%" width="50%" /> 30 | <Link to="/courses" style={{ textDecoration: "none" }}> 31 | <Button variant="contained" color="primary"> 32 | EXPLORE 33 | </Button> 34 | </Link> 35 | </div> 36 | </Grid> 37 | </Grid> 38 | </React.Fragment> 39 | ); 40 | } 41 | 42 | const useStyles = makeStyles((theme) => ({ 43 | sidebar: { 44 | height: "100%", 45 | background: theme.palette.primary.main, 46 | [theme.breakpoints.up("md")]: { 47 | height: "100vh", 48 | }, 49 | }, 50 | })); 51 | -------------------------------------------------------------------------------- /src/components/course/EditButton.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { Button, Dialog, DialogActions, DialogTitle } from "@material-ui/core"; 3 | import CreateIcon from "@material-ui/icons/Create"; 4 | import { Link, useParams } from "react-router-dom"; 5 | 6 | export default function EditButton() { 7 | const [open, setOpen] = useState(false); 8 | const { id } = useParams(); 9 | const handleOpen = () => { 10 | setOpen(true); 11 | }; 12 | 13 | const handleCancel = () => { 14 | setOpen(false); 15 | }; 16 | 17 | return ( 18 | <div> 19 | <Button color="primary" variant="contained" onClick={handleOpen}> 20 | <CreateIcon style={{ fontSize: "18px" }} /> Edit 21 | </Button> 22 | <Dialog 23 | open={open} 24 | onClose={handleCancel} 25 | aria-labelledby="alert-dialog-title" 26 | aria-describedby="alert-dialog-description" 27 | > 28 | <DialogTitle id="alert-dialog-title"> 29 | {"Are you sure you want to edit this course?"} 30 | </DialogTitle> 31 | <DialogActions> 32 | <Button onClick={handleCancel} color="primary" variant="outlined"> 33 | Cancel 34 | </Button> 35 | <Link to={`/course/${id}/edit`} style={{ textDecoration: "none" }}> 36 | <Button 37 | variant="contained" 38 | style={{ background: "#12355B", color: "white" }} 39 | > 40 | Edit 41 | </Button> 42 | </Link> 43 | </DialogActions> 44 | </Dialog> 45 | </div> 46 | ); 47 | } 48 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lms-app-amplify", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@aws-amplify/ui-react": "^1.2.1", 7 | "@devexpress/dx-react-core": "^2.7.5", 8 | "@devexpress/dx-react-scheduler": "^2.7.5", 9 | "@devexpress/dx-react-scheduler-material-ui": "^2.7.5", 10 | "@material-ui/core": "^4.11.4", 11 | "@material-ui/icons": "^4.11.2", 12 | "@material-ui/lab": "*", 13 | "@testing-library/jest-dom": "^5.12.0", 14 | "@testing-library/react": "^11.2.7", 15 | "@testing-library/user-event": "^12.8.3", 16 | "aws-amplify": "^3.4.3", 17 | "boring-avatars": "^1.5.5", 18 | "draft-js": "^0.11.7", 19 | "draftjs-to-html": "^0.9.1", 20 | "immutable": "^3.8.2", 21 | "react": "^16.0.0", 22 | "react-dom": "^16.0.0", 23 | "react-draft-wysiwyg": "^1.14.7", 24 | "react-html-parser": "^2.0.2", 25 | "react-pdf": "^5.3.0", 26 | "react-player": "^2.9.0", 27 | "react-router-dom": "^5.2.0", 28 | "react-scripts": "4.0.3", 29 | "uuid": "^8.3.2", 30 | "web-vitals": "^1.1.2" 31 | }, 32 | "scripts": { 33 | "start": "react-scripts start", 34 | "build": "react-scripts build", 35 | "test": "react-scripts test", 36 | "eject": "react-scripts eject" 37 | }, 38 | "eslintConfig": { 39 | "extends": [ 40 | "react-app", 41 | "react-app/jest" 42 | ] 43 | }, 44 | "browserslist": { 45 | "production": [ 46 | ">0.2%", 47 | "not dead", 48 | "not op_mini all" 49 | ], 50 | "development": [ 51 | "last 1 chrome version", 52 | "last 1 firefox version", 53 | "last 1 safari version" 54 | ] 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/pages/discussions/AddComment.js: -------------------------------------------------------------------------------- 1 | import React, { useContext, useState } from "react"; 2 | import { IconButton, TextField } from "@material-ui/core"; 3 | import Avatar from "boring-avatars"; 4 | import { UserContext } from "../../context/UserContext"; 5 | import SendIcon from "@material-ui/icons/Send"; 6 | import { DataStore } from "@aws-amplify/datastore"; 7 | import { CommentModel } from "../../models"; 8 | 9 | function CommentForm({ discussionID }) { 10 | const user = useContext(UserContext); 11 | const [comment, setComment] = useState(); 12 | 13 | async function handleSubmit(e) { 14 | await DataStore.save( 15 | new CommentModel({ 16 | comment: comment, 17 | createdBy: user.username, 18 | createdAt: new Date().toLocaleString(), 19 | postmodelID: discussionID, 20 | User: user, 21 | }) 22 | ); 23 | setComment(""); 24 | } 25 | return ( 26 | <> 27 | <div style={{ display: "flex", margin: "10px", alignItems: "center" }}> 28 | <Avatar 29 | name={user.username} 30 | variant="beam" 31 | colors={["#A70267", "#F10C49", "#FB6B41", "#F6D86B", "#339194"]} 32 | /> 33 | <TextField 34 | id="standard-required" 35 | variant="outlined" 36 | size="small" 37 | label="Add Comment" 38 | fullWidth 39 | style={{ margin: "0px 10px" }} 40 | value={comment} 41 | onChange={(e) => setComment(e.target.value)} 42 | /> 43 | <IconButton onClick={handleSubmit}> 44 | <SendIcon /> 45 | </IconButton> 46 | </div> 47 | </> 48 | ); 49 | } 50 | 51 | export default CommentForm; 52 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", 4 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", 12 | monospace; 13 | } 14 | 15 | * { 16 | margin: 0; 17 | padding: 0; 18 | } 19 | 20 | .home-editor { 21 | border: 1px solid #403f4f !important; 22 | padding: 10px !important; 23 | border-radius: 5px !important; 24 | height: 25vh !important; 25 | } 26 | .rdw-editor-main { 27 | height: 100%; 28 | overflow: auto; 29 | box-sizing: border-box; 30 | } 31 | .wrapper-class { 32 | box-sizing: content-box; 33 | } 34 | 35 | body { 36 | margin: 0; 37 | background-color: #525659; 38 | font-family: Segoe UI, Tahoma, sans-serif; 39 | } 40 | 41 | .Example__container { 42 | display: flex; 43 | flex-direction: column; 44 | align-items: center; 45 | margin: 10px 0; 46 | padding: 10px; 47 | z-index: -1; 48 | } 49 | .Example__container__load { 50 | margin-top: 1em; 51 | color: white; 52 | } 53 | .Example__container__document { 54 | margin: 1em 0; 55 | } 56 | .Example__container__document .react-pdf__Document { 57 | display: flex; 58 | flex-direction: column; 59 | align-items: center; 60 | z-index: -1; 61 | } 62 | .Example__container__document .react-pdf__Page { 63 | max-width: calc(100% - 2em); 64 | box-shadow: 0 0 8px rgba(0, 0, 0, 0.5); 65 | margin: 1em; 66 | z-index: -1; 67 | } 68 | .Example__container__document .react-pdf__Page canvas { 69 | max-width: 100%; 70 | height: auto !important; 71 | } 72 | -------------------------------------------------------------------------------- /amplify/backend/api/lmsappamplify/stacks/CustomResources.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Description": "An auto-generated nested stack.", 4 | "Metadata": {}, 5 | "Parameters": { 6 | "AppSyncApiId": { 7 | "Type": "String", 8 | "Description": "The id of the AppSync API associated with this project." 9 | }, 10 | "AppSyncApiName": { 11 | "Type": "String", 12 | "Description": "The name of the AppSync API", 13 | "Default": "AppSyncSimpleTransform" 14 | }, 15 | "env": { 16 | "Type": "String", 17 | "Description": "The environment name. e.g. Dev, Test, or Production", 18 | "Default": "NONE" 19 | }, 20 | "S3DeploymentBucket": { 21 | "Type": "String", 22 | "Description": "The S3 bucket containing all deployment assets for the project." 23 | }, 24 | "S3DeploymentRootKey": { 25 | "Type": "String", 26 | "Description": "An S3 key relative to the S3DeploymentBucket that points to the root\nof the deployment directory." 27 | } 28 | }, 29 | "Resources": { 30 | "EmptyResource": { 31 | "Type": "Custom::EmptyResource", 32 | "Condition": "AlwaysFalse" 33 | } 34 | }, 35 | "Conditions": { 36 | "HasEnvironmentParameter": { 37 | "Fn::Not": [ 38 | { 39 | "Fn::Equals": [ 40 | { 41 | "Ref": "env" 42 | }, 43 | "NONE" 44 | ] 45 | } 46 | ] 47 | }, 48 | "AlwaysFalse": { 49 | "Fn::Equals": ["true", "false"] 50 | } 51 | }, 52 | "Outputs": { 53 | "EmptyOutput": { 54 | "Description": "An empty output. You may delete this if you have at least one resource above.", 55 | "Value": "" 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/pages/students/StudentDetails.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { Grid, Typography } from "@material-ui/core"; 3 | import { Container } from "@material-ui/core"; 4 | import { DataStore } from "aws-amplify"; 5 | import { CourseUser } from "../../models"; 6 | import StudentCard from "./StudentCard"; 7 | import noUser from "../../assests/no-user.svg"; 8 | import { useParams } from "react-router-dom"; 9 | 10 | function StudentDetails() { 11 | const { id } = useParams(); 12 | const [enrolledStudents, setEnrolledStudents] = useState([]); 13 | useEffect(() => { 14 | async function getStudents() { 15 | const enrolledStudents = (await DataStore.query(CourseUser)) 16 | .filter((pe) => pe.course.id === id) 17 | .map((pe) => pe.user); 18 | setEnrolledStudents(enrolledStudents); 19 | } 20 | getStudents(); 21 | }, [id]); 22 | 23 | return ( 24 | <React.Fragment> 25 | <Container maxWidth="md" style={{ marginTop: "50px", display: "flex" }}> 26 | <Grid container spacing={2}> 27 | {enrolledStudents.length > 0 ? ( 28 | <> 29 | {enrolledStudents.map((user, index) => ( 30 | <Grid item xs={12} md={4} key={user.id}> 31 | <StudentCard key={index} user={user} /> 32 | </Grid> 33 | ))} 34 | </> 35 | ) : ( 36 | <Container align="center" maxWidth="md"> 37 | <img src={noUser} alt="no-course" height="80%" width="80%" /> 38 | <Typography> No Users Enrolled!</Typography> 39 | </Container> 40 | )} 41 | </Grid> 42 | </Container> 43 | </React.Fragment> 44 | ); 45 | } 46 | 47 | export default StudentDetails; 48 | -------------------------------------------------------------------------------- /src/components/dashboard/StudentDashboard.js: -------------------------------------------------------------------------------- 1 | import { Grid, Typography } from "@material-ui/core"; 2 | import { Container } from "@material-ui/core"; 3 | import { DataStore } from "aws-amplify"; 4 | import React, { useContext, useEffect, useState } from "react"; 5 | import { UserContext } from "../../context/UserContext"; 6 | import { CourseUser } from "../../models"; 7 | import CourseCard from "../course/CourseCard"; 8 | import nodata from "../../assests/no-data.svg"; 9 | 10 | function StudentDashboard() { 11 | const user = useContext(UserContext); 12 | const [enrolledCourses, setEnrolledCourses] = useState([]); 13 | useEffect(() => { 14 | async function getCourses() { 15 | const enrolledCourses = (await DataStore.query(CourseUser)) 16 | .filter((c) => c.user.id === user.id) 17 | .map((c) => c.course); 18 | setEnrolledCourses(enrolledCourses); 19 | } 20 | getCourses(); 21 | }, [user.id]); 22 | 23 | return ( 24 | <React.Fragment> 25 | <Container maxWidth="md" style={{ marginTop: "50px", display: "flex" }}> 26 | <Grid container spacing={2}> 27 | {enrolledCourses.length > 0 ? ( 28 | <> 29 | {enrolledCourses.map((course, index) => ( 30 | <Grid item xs={12} md={4} key={course.id}> 31 | <CourseCard key={index} course={course} /> 32 | </Grid> 33 | ))} 34 | </> 35 | ) : ( 36 | <Container align="center" maxWidth="md"> 37 | <img src={nodata} alt="no-course" height="80%" width="80%" /> 38 | <Typography> No Course!</Typography> 39 | </Container> 40 | )} 41 | </Grid> 42 | </Container> 43 | </React.Fragment> 44 | ); 45 | } 46 | 47 | export default StudentDashboard; 48 | -------------------------------------------------------------------------------- /amplify/backend/auth/lmsappamplify77733d32/parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "identityPoolName": "lmsappamplify77733d32_identitypool_77733d32", 3 | "allowUnauthenticatedIdentities": true, 4 | "resourceNameTruncated": "lmsapp77733d32", 5 | "userPoolName": "lmsappamplify77733d32_userpool_77733d32", 6 | "autoVerifiedAttributes": [ 7 | "email" 8 | ], 9 | "mfaConfiguration": "OFF", 10 | "mfaTypes": [ 11 | "SMS Text Message" 12 | ], 13 | "smsAuthenticationMessage": "Your authentication code is {####}", 14 | "smsVerificationMessage": "Your verification code is {####}", 15 | "emailVerificationSubject": "Your verification code", 16 | "emailVerificationMessage": "Your verification code is {####}", 17 | "defaultPasswordPolicy": false, 18 | "passwordPolicyMinLength": 8, 19 | "passwordPolicyCharacters": [], 20 | "requiredAttributes": [ 21 | "email" 22 | ], 23 | "userpoolClientGenerateSecret": false, 24 | "userpoolClientRefreshTokenValidity": 30, 25 | "userpoolClientWriteAttributes": [ 26 | "email" 27 | ], 28 | "userpoolClientReadAttributes": [ 29 | "email" 30 | ], 31 | "userpoolClientLambdaRole": "lmsapp77733d32_userpoolclient_lambda_role", 32 | "userpoolClientSetAttributes": false, 33 | "sharedId": "77733d32", 34 | "resourceName": "lmsappamplify77733d32", 35 | "authSelections": "identityPoolAndUserPool", 36 | "authRoleArn": { 37 | "Fn::GetAtt": [ 38 | "AuthRole", 39 | "Arn" 40 | ] 41 | }, 42 | "unauthRoleArn": { 43 | "Fn::GetAtt": [ 44 | "UnauthRole", 45 | "Arn" 46 | ] 47 | }, 48 | "useDefault": "default", 49 | "userPoolGroupList": [], 50 | "serviceName": "Cognito", 51 | "usernameCaseSensitive": false, 52 | "dependsOn": [] 53 | } -------------------------------------------------------------------------------- /src/components/landingPage/Features.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Grid, makeStyles } from "@material-ui/core"; 3 | import lms from "../../assests/lms.png"; 4 | import lms2 from "../../assests/lms2.png"; 5 | 6 | import { Typography } from "@material-ui/core"; 7 | 8 | function Features() { 9 | const classes = useStyles(); 10 | 11 | return ( 12 | <Grid container align="center" className={classes.container}> 13 | <Grid item xs={12} md={12}> 14 | <Typography variant="h3">What you'll get</Typography> 15 | </Grid> 16 | <Grid item xs={12} md={12}> 17 | <Typography variant="subtitle2" gutterBottom> 18 | subtitle2. Lorem ipsum dolor sit amet, consectetur adipisicing elit. 19 | Quos blanditiis tenetur 20 | </Typography> 21 | </Grid> 22 | <Grid item xs={12} md={12} className={classes.img}> 23 | <img 24 | src={lms} 25 | alt="lms" 26 | height="60%" 27 | width="40%" 28 | className={classes.front} 29 | /> 30 | <img 31 | src={lms2} 32 | alt="lms" 33 | height="60%" 34 | width="40%" 35 | className={classes.behind} 36 | /> 37 | </Grid> 38 | </Grid> 39 | ); 40 | } 41 | 42 | export default Features; 43 | 44 | const useStyles = makeStyles((theme) => ({ 45 | container: { 46 | [theme.breakpoints.up("sm")]: { 47 | height: "100vh", 48 | }, 49 | padding: "30px", 50 | backgroundColor: theme.palette.primary.main, 51 | color: "white", 52 | }, 53 | img: { marginTop: "auto", marginBottom: "10px" }, 54 | behind: { 55 | position: "relative", 56 | right: 50, 57 | top: 30, 58 | zIndex: 2, 59 | borderRadius: "10px", 60 | }, 61 | front: { 62 | position: "relative", 63 | left: 50, 64 | zIndex: 1, 65 | borderRadius: "10px", 66 | }, 67 | })); 68 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | <!DOCTYPE html> 2 | <html lang="en"> 3 | <head> 4 | <meta charset="utf-8" /> 5 | <link rel="icon" href="%PUBLIC_URL%/logo.png" /> 6 | <meta name="viewport" content="width=device-width, initial-scale=1" /> 7 | <meta name="theme-color" content="#000000" /> 8 | <meta 9 | name="description" 10 | content="A cloud-based learning management system intended for educational institutions." 11 | /> 12 | <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" /> 13 | <!-- 14 | manifest.json provides metadata used when your web app is installed on a 15 | user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/ 16 | --> 17 | <link rel="manifest" href="%PUBLIC_URL%/manifest.json" /> 18 | <!-- 19 | Notice the use of %PUBLIC_URL% in the tags above. 20 | It will be replaced with the URL of the `public` folder during the build. 21 | Only files inside the `public` folder can be referenced from the HTML. 22 | Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will 23 | work correctly both with client-side routing and a non-root public URL. 24 | Learn how to configure a non-root public URL by running `npm run build`. 25 | --> 26 | <title>EduCouch LMS 27 | 28 | 29 | 30 |
31 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/components/Error404.js: -------------------------------------------------------------------------------- 1 | import { Button, makeStyles, Typography } from "@material-ui/core"; 2 | import React from "react"; 3 | import { Link } from "react-router-dom"; 4 | 5 | const useStyles = makeStyles((theme) => ({ 6 | container: { 7 | display: "flex", 8 | flexDirection: "column", 9 | alignItems: "center", 10 | justifyContent: "center", 11 | height: "100vh", 12 | backgroundColor: "black", 13 | color: "white", 14 | }, 15 | heading: { 16 | fontSize: "80px", 17 | background: "linear-gradient(to right, #C33764 0%, #1D2671 100%)", 18 | backgroundClip: "text", 19 | WebkitBackgroundClip: "text", 20 | WebkitTextFillColor: "transparent", 21 | }, 22 | link: { 23 | textDecoration: "none", 24 | color: "white", 25 | }, 26 | })); 27 | function Error404() { 28 | const classes = useStyles(); 29 | 30 | return ( 31 | 32 |
33 | 34 | OOPS! 35 | 36 | 404 - Page not found 37 | 38 | 39 | Sorry, the page you're looking for doesn't exist. 40 | 41 | Or maybe you doesn't have access to the page. 42 | 47 | - O R - 48 | 57 |
58 |
59 | ); 60 | } 61 | 62 | export default Error404; 63 | -------------------------------------------------------------------------------- /src/components/course/PublishButton.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { Button, Dialog, DialogActions, DialogTitle } from "@material-ui/core"; 3 | import PublishIcon from "@material-ui/icons/Publish"; 4 | import { useHistory, useParams } from "react-router-dom"; 5 | import { DataStore } from "@aws-amplify/datastore"; 6 | import { Course, CourseStatus } from "../../models"; 7 | 8 | export default function PublishButton() { 9 | const [open, setOpen] = useState(false); 10 | const { id } = useParams(); 11 | let history = useHistory(); 12 | const handleOpen = () => { 13 | setOpen(true); 14 | }; 15 | 16 | const handleCancel = () => { 17 | setOpen(false); 18 | }; 19 | 20 | async function handlePublish() { 21 | const original = await DataStore.query(Course, id); 22 | 23 | await DataStore.save( 24 | Course.copyOf(original, (updated) => { 25 | updated.status = CourseStatus.PUBLISHED; 26 | }) 27 | ); 28 | history.push("/courses"); 29 | } 30 | return ( 31 |
32 | 35 | 41 | 42 | {"Are you sure you want to publish this course?"} 43 | 44 | 45 | 48 | 49 | 56 | 57 | 58 |
59 | ); 60 | } 61 | -------------------------------------------------------------------------------- /src/components/course/DeleteButton.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { 3 | Button, 4 | Dialog, 5 | DialogActions, 6 | DialogContent, 7 | DialogTitle, 8 | } from "@material-ui/core"; 9 | import DeleteIcon from "@material-ui/icons/Delete"; 10 | import { DataStore } from "@aws-amplify/datastore"; 11 | import { Course } from "../../models"; 12 | import { useHistory, useParams } from "react-router"; 13 | 14 | export default function DeleteButton() { 15 | const [open, setOpen] = useState(false); 16 | let history = useHistory(); 17 | const { id } = useParams(); 18 | 19 | const handleOpen = () => { 20 | setOpen(true); 21 | }; 22 | 23 | const handleCancel = () => { 24 | setOpen(false); 25 | }; 26 | 27 | async function handleDelete() { 28 | const todelete = await DataStore.query(Course, id); 29 | DataStore.delete(todelete); 30 | setOpen(false); 31 | history.goBack(); 32 | } 33 | 34 | return ( 35 |
36 | 44 | 50 | 51 | {"Are you sure you want to delete this course?"} 52 | 53 | You cannot undo this action! 54 | 55 | 58 | 65 | 66 | 67 |
68 | ); 69 | } 70 | -------------------------------------------------------------------------------- /src/pages/syllabus/PreviewSyllabus.js: -------------------------------------------------------------------------------- 1 | import React, { useContext, useEffect, useState } from "react"; 2 | import { Button } from "@material-ui/core"; 3 | import { Storage } from "aws-amplify"; 4 | import { Document, Page } from "react-pdf/dist/esm/entry.webpack"; 5 | import { Container } from "@material-ui/core"; 6 | import { UserContext } from "../../context/UserContext"; 7 | 8 | const options = { 9 | cMapUrl: "cmaps/", 10 | cMapPacked: true, 11 | }; 12 | 13 | function PreviewSyllabus({ syllabus, handleDelete }) { 14 | const user = useContext(UserContext); 15 | const [url, setUrl] = useState(); 16 | const [numPages, setNumPages] = useState(null); 17 | function onDocumentLoadSuccess({ numPages: nextNumPages }) { 18 | setNumPages(nextNumPages); 19 | } 20 | useEffect(() => { 21 | loadFileUrl(syllabus); 22 | }, [syllabus]); 23 | 24 | const loadFileUrl = async (syllabus) => { 25 | await Storage.get(syllabus.S3Key).then((res) => { 26 | setUrl(res); 27 | }); 28 | }; 29 | return ( 30 | <> 31 | {user.username === syllabus.uploadedBy && ( 32 | 42 | )} 43 | 44 |
45 | 50 | {Array.from(new Array(numPages), (el, index) => ( 51 |
52 | {" "} 53 | 54 |

55 | Page {index + 1} of {numPages} 56 |

57 |
58 | ))} 59 |
60 |
61 | 62 | ); 63 | } 64 | 65 | export default PreviewSyllabus; 66 | -------------------------------------------------------------------------------- /src/pages/discussions/DisplayComments.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { DataStore } from "@aws-amplify/datastore"; 3 | import { CommentModel } from "../../models"; 4 | import Comment from "./Comment"; 5 | import SmsIcon from "@material-ui/icons/Sms"; 6 | import { Box } from "@material-ui/core"; 7 | 8 | function DisplayComments({ discussionID }) { 9 | const [comments, setComments] = useState([]); 10 | const [open, setOpen] = useState(false); 11 | const [text, setText] = useState("View"); 12 | 13 | useEffect(() => { 14 | async function getComments() { 15 | const models = (await DataStore.query(CommentModel)).filter( 16 | (c) => c.postmodelID === discussionID 17 | ); 18 | setComments(models); 19 | } 20 | getComments(); 21 | const subscription = DataStore.observe(CommentModel).subscribe((msg) => { 22 | getComments(); 23 | }); 24 | return () => subscription.unsubscribe(); 25 | }, [discussionID]); 26 | 27 | async function handleDelete(id) { 28 | const todelete = await DataStore.query(CommentModel, id); 29 | DataStore.delete(todelete); 30 | } 31 | return ( 32 | 33 | { 35 | setOpen(!open); 36 | if (!open) setText("Hide"); 37 | else setText("View"); 38 | }} 39 | > 40 | {comments.length > 0 && ( 41 |
49 | 50 | {text} {comments.length} comment(s) 51 |
52 | )} 53 |
54 | {open && ( 55 |
56 | {comments.map((comment) => ( 57 | <> 58 | 59 | 60 | ))} 61 |
62 | )} 63 |
64 | ); 65 | } 66 | 67 | export default DisplayComments; 68 | -------------------------------------------------------------------------------- /src/pages/discussions/DisplayDiscussions.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { DataStore, Predicates, SortDirection } from "@aws-amplify/datastore"; 3 | import { PostModel } from "../../models"; 4 | import { useParams } from "react-router"; 5 | import { Box, Typography } from "@material-ui/core"; 6 | import NewDiscussion from "./NewDiscussion"; 7 | import Discussion from "./Discussion"; 8 | import noDiscussionImg from "../../assests/noDiscussionImg.svg"; 9 | 10 | function DisplayDiscussions() { 11 | const { id } = useParams(); 12 | const [discussions, setDiscussions] = useState([]); 13 | useEffect(() => { 14 | async function getDiscussions() { 15 | const models = ( 16 | await DataStore.query(PostModel, Predicates.ALL, { 17 | sort: (s) => s.createdAt(SortDirection.DESCENDING), 18 | }) 19 | ).filter((c) => c.courseID === id); 20 | 21 | setDiscussions(models); 22 | } 23 | getDiscussions(); 24 | const subscription = DataStore.observe(PostModel).subscribe((msg) => { 25 | getDiscussions(); 26 | }); 27 | 28 | return () => subscription.unsubscribe(); 29 | }, [id]); 30 | 31 | async function handleDelete(id) { 32 | const todelete = await DataStore.query(PostModel, id); 33 | DataStore.delete(todelete); 34 | } 35 | return ( 36 |
37 | 38 | {discussions.length > 0 ? ( 39 | <> 40 | {discussions.map((discussion, index) => ( 41 | 46 | ))} 47 | 48 | ) : ( 49 | 50 | no-discussion 56 | No Discussions! 57 | 58 | )} 59 |
60 | ); 61 | } 62 | 63 | export default DisplayDiscussions; 64 | -------------------------------------------------------------------------------- /src/components/landingPage/Benefit.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Grid, makeStyles } from "@material-ui/core"; 3 | import org from "../../assests/org.svg"; 4 | import student from "../../assests/student.svg"; 5 | import teacher from "../../assests/teacher.svg"; 6 | import { Typography } from "@material-ui/core"; 7 | 8 | function Benefit() { 9 | const classes = useStyles(); 10 | 11 | return ( 12 | 13 | 14 | Who will benefit 15 | 16 | 17 | student 18 | Student 19 | 20 | subtitle2. Lorem ipsum dolor sit amet, consectetur adipisicing elit. 21 | Quos blanditiis tenetur 22 | 23 | 24 | 25 | teacher 26 | Teacher 27 | 28 | subtitle2. Lorem ipsum dolor sit amet, consectetur adipisicing elit. 29 | Quos blanditiis tenetur 30 | 31 | 32 | 33 | org 34 | University/School 35 | 36 | subtitle2. Lorem ipsum dolor sit amet, consectetur adipisicing elit. 37 | Quos blanditiis tenetur 38 | 39 | 40 | 41 | ); 42 | } 43 | 44 | export default Benefit; 45 | 46 | const useStyles = makeStyles((theme) => ({ 47 | container: { 48 | [theme.breakpoints.up("sm")]: { 49 | height: "100vh", 50 | }, 51 | height: "120vh", 52 | background: "white", 53 | padding: "30px", 54 | }, 55 | grid: {}, 56 | })); 57 | -------------------------------------------------------------------------------- /src/pages/discussions/Comment.js: -------------------------------------------------------------------------------- 1 | import React, { useContext } from "react"; 2 | import { Box, Divider, Paper, Typography } from "@material-ui/core"; 3 | import Avatar from "boring-avatars"; 4 | import ClearIcon from "@material-ui/icons/Clear"; 5 | import { UserContext } from "../../context/UserContext"; 6 | 7 | function Comment({ comment, handleDelete }) { 8 | const user = useContext(UserContext); 9 | return ( 10 | 11 | 12 | 13 | 19 | 26 | 27 | {comment.User.name} 28 | 29 | 33 | {comment.createdAt} 34 | 35 | 36 | 37 | 38 | 39 | {comment.comment} 40 | 41 | {user.username === comment.User.username && ( 42 |
handleDelete(comment.id)} 49 | > 50 | 51 |
52 | )} 53 |
54 | 55 |
56 |
57 | ); 58 | } 59 | 60 | export default Comment; 61 | -------------------------------------------------------------------------------- /src/pages/assignments/DisplayAssignments.js: -------------------------------------------------------------------------------- 1 | import React, { useContext, useEffect, useState } from "react"; 2 | import { Box, Container } from "@material-ui/core"; 3 | import nodata from "../../assests/nodata.svg"; 4 | import { DataStore } from "@aws-amplify/datastore"; 5 | import { AssignmentModel } from "../../models"; 6 | import AddAssignment from "./AddAssignment"; 7 | import Assignment from "./Assignment"; 8 | import { useParams } from "react-router"; 9 | import { UserContext } from "../../context/UserContext"; 10 | import useCourses from "../../customHook/useCourses"; 11 | 12 | function DisplayAssignments() { 13 | const { id } = useParams(); 14 | const user = useContext(UserContext); 15 | const course = useCourses(id); 16 | 17 | const [assignments, setAssignments] = useState([]); 18 | 19 | useEffect(() => { 20 | async function getAssignments() { 21 | const models = (await DataStore.query(AssignmentModel)).filter( 22 | (c) => c.courseID === id 23 | ); 24 | setAssignments(models); 25 | } 26 | getAssignments(); 27 | const subscription = DataStore.observe(AssignmentModel).subscribe((msg) => { 28 | getAssignments(); 29 | }); 30 | return () => subscription.unsubscribe(); 31 | }, [id]); 32 | 33 | return ( 34 | 35 | 36 | {user.username === course.createdBy && user.isEducator && ( 37 | 38 | 39 | 40 | )} 41 | 42 | {assignments.length > 0 ? ( 43 |
50 | {assignments.map((assignment) => ( 51 |
52 | 53 |
54 | ))} 55 |
56 | ) : ( 57 | no-syllabus 58 | )} 59 |
60 |
61 | ); 62 | } 63 | 64 | export default DisplayAssignments; 65 | -------------------------------------------------------------------------------- /src/components/course/EditCourse.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useTheme } from "@material-ui/core/styles"; 3 | import { useStyles } from "../../utils/useStyles"; 4 | import { Box, Drawer, Grid, Hidden } from "@material-ui/core"; 5 | import MenuIcon from "@material-ui/icons/Menu"; 6 | import Navigation from "../../components/Navigation"; 7 | import Navlinks from "../../components/course/Navlinks"; 8 | import Edit from "./Edit"; 9 | 10 | export default function EditCourse() { 11 | const classes = useStyles(); 12 | const theme = useTheme(); 13 | const [mobileOpen, setMobileOpen] = React.useState(false); 14 | 15 | const handleDrawerToggle = () => { 16 | setMobileOpen(!mobileOpen); 17 | }; 18 | 19 | const drawer = ( 20 |
21 | 22 |
23 | ); 24 | return ( 25 | 26 | 27 | 28 | 35 | 36 | 37 | 38 | {" "} 39 | 40 | 41 | 42 | 43 | 44 | 56 | {drawer} 57 | 58 | 59 | 60 | {drawer} 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | ); 71 | } 72 | -------------------------------------------------------------------------------- /src/components/account/IsEducatorSwitch.js: -------------------------------------------------------------------------------- 1 | import React, { useContext } from "react"; 2 | import { Button, Grid, Switch } from "@material-ui/core"; 3 | import { UserContext } from "../../context/UserContext"; 4 | import { DataStore } from "@aws-amplify/datastore"; 5 | import { User } from "../../models"; 6 | import Label from "./Label"; 7 | import MessageAlert from "./MessageAlert"; 8 | 9 | function IsEducatorSwitch() { 10 | const user = useContext(UserContext); 11 | const [open, setOpen] = React.useState(false); 12 | 13 | const handleClose = (event, reason) => { 14 | if (reason === "clickaway") { 15 | return; 16 | } 17 | setOpen(false); 18 | }; 19 | 20 | const [state, setState] = React.useState({ 21 | checkedA: user.isEducator, 22 | }); 23 | const handleChange = (event) => { 24 | setState({ ...state, [event.target.name]: event.target.checked }); 25 | }; 26 | 27 | async function handleSubmit(e) { 28 | e.preventDefault(); 29 | const original = await DataStore.query(User, user.id); 30 | const data = await DataStore.save( 31 | User.copyOf(original, (updated) => { 32 | updated.isEducator = state.checkedA; 33 | }) 34 | ); 35 | if (data) setOpen(true); 36 | } 37 | return ( 38 | 39 | 40 | 41 | 43 | 44 | 45 | 51 | 52 | 53 | {user.isEducator === state.checkedA ? ( 54 | 62 | ) : ( 63 | 66 | )} 67 | 68 | 69 | ); 70 | } 71 | 72 | export default IsEducatorSwitch; 73 | -------------------------------------------------------------------------------- /src/pages/announcements/EditAnnouncement.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useTheme } from "@material-ui/core/styles"; 3 | import { useStyles } from "../../utils/useStyles"; 4 | import { Box, Drawer, Grid, Hidden } from "@material-ui/core"; 5 | import MenuIcon from "@material-ui/icons/Menu"; 6 | import Navigation from "../../components/Navigation"; 7 | import Navlinks from "../../components/course/Navlinks"; 8 | import Edit from "./Edit"; 9 | 10 | export default function EditAnnouncement() { 11 | const classes = useStyles(); 12 | const theme = useTheme(); 13 | const [mobileOpen, setMobileOpen] = React.useState(false); 14 | 15 | const handleDrawerToggle = () => { 16 | setMobileOpen(!mobileOpen); 17 | }; 18 | 19 | const drawer = ( 20 |
21 | 22 |
23 | ); 24 | return ( 25 | 26 | 27 | 28 | 35 | 36 | 37 | 38 | {" "} 39 | 40 | 41 | 42 | 43 | 44 | 56 | {drawer} 57 | 58 | 59 | 60 | {drawer} 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | ); 71 | } 72 | -------------------------------------------------------------------------------- /src/pages/syllabus/DisplaySyllabus.js: -------------------------------------------------------------------------------- 1 | import React, { useContext, useEffect, useState } from "react"; 2 | import { Container } from "@material-ui/core"; 3 | import nodata from "../../assests/nodata.svg"; 4 | import { DataStore } from "@aws-amplify/datastore"; 5 | import { SyllabusModel } from "../../models"; 6 | import { useParams } from "react-router"; 7 | import NewSyllabus from "./NewSyllabus"; 8 | import PreviewSyllabus from "./PreviewSyllabus"; 9 | import { UserContext } from "../../context/UserContext"; 10 | import useCourses from "../../customHook/useCourses"; 11 | 12 | function DisplaySyllabus() { 13 | const { id } = useParams(); 14 | const course = useCourses(id); 15 | const user = useContext(UserContext); 16 | 17 | const [syllabus, setSyllabus] = useState([]); 18 | useEffect(() => { 19 | async function getSyllabus() { 20 | const models = (await DataStore.query(SyllabusModel)).filter( 21 | (c) => c.courseID === id 22 | ); 23 | setSyllabus(models); 24 | } 25 | 26 | getSyllabus(); 27 | const subscription = DataStore.observe(SyllabusModel).subscribe((msg) => { 28 | getSyllabus(); 29 | }); 30 | return () => subscription.unsubscribe(); 31 | }, [id]); 32 | 33 | async function handleDelete(id) { 34 | const todelete = await DataStore.query(SyllabusModel, id); 35 | DataStore.delete(todelete); 36 | } 37 | return ( 38 | 39 | 40 | {syllabus.length > 0 ? ( 41 |
48 | {syllabus.map((item, index) => ( 49 |
50 | 51 |
52 | ))} 53 |
54 | ) : ( 55 |
56 | {user.username === course.createdBy && user.isEducator && ( 57 | 58 | )} 59 | no-syllabus 60 |
61 | )} 62 |
63 |
64 | ); 65 | } 66 | 67 | export default DisplaySyllabus; 68 | -------------------------------------------------------------------------------- /src/components/course/ShareLinkModal.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { makeStyles } from "@material-ui/core/styles"; 3 | import Modal from "@material-ui/core/Modal"; 4 | import { Button, TextField } from "@material-ui/core"; 5 | 6 | const useStyles = makeStyles((theme) => ({ 7 | paper: { 8 | position: "absolute", 9 | width: 350, 10 | backgroundColor: theme.palette.background.paper, 11 | boxShadow: theme.shadows[5], 12 | padding: theme.spacing(2, 4, 3), 13 | borderRadius: "10px", 14 | }, 15 | })); 16 | 17 | export default function SimpleModal({ id }) { 18 | const classes = useStyles(); 19 | const [open, setOpen] = React.useState(false); 20 | const [buttonText, setButtonText] = React.useState("Copy Link"); 21 | const url = window.location.protocol + window.location.host + "/course/" + id; 22 | const handleOpen = () => { 23 | setOpen(true); 24 | }; 25 | 26 | const handleClose = () => { 27 | setButtonText("Copy Link"); 28 | setOpen(false); 29 | }; 30 | 31 | const body = ( 32 |
40 |

Share link with your friends!

41 | 49 | 50 | 64 |
65 | ); 66 | 67 | return ( 68 |
69 | 72 | 73 | 79 | {body} 80 | 81 |
82 | ); 83 | } 84 | -------------------------------------------------------------------------------- /src/components/course/CourseCard.js: -------------------------------------------------------------------------------- 1 | import React, { useContext } from "react"; 2 | import { 3 | Button, 4 | Card, 5 | CardActions, 6 | CardContent, 7 | CardHeader, 8 | Typography, 9 | } from "@material-ui/core"; 10 | import FavoriteBorderRoundedIcon from "@material-ui/icons/FavoriteBorderRounded"; 11 | import ShareLinkModal from "./ShareLinkModal"; 12 | import ReactHtmlParser from "react-html-parser"; 13 | import Avatar from "boring-avatars"; 14 | import { UserContext } from "../../context/UserContext"; 15 | import ContinueButton from "./ContinueButton"; 16 | import EnrollButton from "./EnrollButton"; 17 | 18 | function CourseCard({ course, handleUpdate }) { 19 | const user = useContext(UserContext); 20 | 21 | return ( 22 |
23 | 24 | 32 | } 33 | title={course.title} 34 | fontWeight="900" 35 | subheader={"By " + course.createdBy} 36 | /> 37 | 38 | 39 | Created on : {course.createdAt.split(",")[0]} 40 | {ReactHtmlParser(course.desc.length) > 80 ? ( 41 |
{ReactHtmlParser(course.desc.substring(0, 80))}..
42 | ) : ( 43 |
{ReactHtmlParser(course.desc)}
44 | )} 45 |
46 |
47 | 54 | 57 | 58 | 59 | {course.enrolledStudents.includes(user.id) ? ( 60 | 61 | ) : ( 62 | <> 63 | {!user.isEducator && ( 64 | 65 | )} 66 | 67 | )} 68 | 69 |
70 |
71 | ); 72 | } 73 | 74 | export default CourseCard; 75 | -------------------------------------------------------------------------------- /src/pages/lessons/Lessons.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useTheme } from "@material-ui/core/styles"; 3 | import { useStyles } from "../../utils/useStyles"; 4 | import { Box, Drawer, Grid, Hidden } from "@material-ui/core"; 5 | import MenuIcon from "@material-ui/icons/Menu"; 6 | import Navigation from "../../components/Navigation"; 7 | import Navlinks from "../../components/course/Navlinks"; 8 | import LessonsList from "./LessonsList"; 9 | import Breadcrumb from "../../components/Breadcrumb"; 10 | 11 | export default function Lessons() { 12 | const classes = useStyles(); 13 | const theme = useTheme(); 14 | const [mobileOpen, setMobileOpen] = React.useState(false); 15 | 16 | const handleDrawerToggle = () => { 17 | setMobileOpen(!mobileOpen); 18 | }; 19 | 20 | const drawer = ( 21 |
22 | 23 |
24 | ); 25 | return ( 26 | 27 | 28 | 29 |
36 | 37 |
38 | 39 | {" "} 40 | 41 | 42 |
43 | 44 | 45 | 57 | {drawer} 58 | 59 | 60 | 61 | {drawer} 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 |
73 |
74 | ); 75 | } 76 | -------------------------------------------------------------------------------- /src/components/landingPage/Hero.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Button, makeStyles, Typography } from "@material-ui/core"; 3 | import { Container, Grid } from "@material-ui/core"; 4 | import { Link } from "react-router-dom"; 5 | import hero from "../../assests/hero.svg"; 6 | import ArrowForwardIosIcon from "@material-ui/icons/ArrowForwardIos"; 7 | 8 | function Hero() { 9 | const classes = useStyles(); 10 | return ( 11 |
12 | 17 | 18 | 19 | 20 | Everything you need to manage your educational institution! 21 | 22 | 27 | A cloud-based learning management system is a platform intended 28 | for educational institution for managing their educational 29 | content. 30 | 31 | 32 | 53 | 54 | 55 | 56 | hero 57 | 58 | 59 |
60 | ); 61 | } 62 | 63 | export default Hero; 64 | 65 | const useStyles = makeStyles((theme) => ({ 66 | button: { 67 | margin: "50px 10px 0px 10px", 68 | padding: "13px 18px", 69 | }, 70 | subtitle: { 71 | [theme.breakpoints.up("md")]: { 72 | padding: "0 120px", 73 | }, 74 | }, 75 | })); 76 | -------------------------------------------------------------------------------- /src/pages/students/Students.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useTheme } from "@material-ui/core/styles"; 3 | import { useStyles } from "../../utils/useStyles"; 4 | import { Box, Drawer, Grid, Hidden } from "@material-ui/core"; 5 | import MenuIcon from "@material-ui/icons/Menu"; 6 | import Navigation from "../../components/Navigation"; 7 | import Navlinks from "../../components/course/Navlinks"; 8 | import Breadcrumb from "../../components/Breadcrumb"; 9 | import StudentDetails from "./StudentDetails"; 10 | 11 | export default function Discussions() { 12 | const classes = useStyles(); 13 | const theme = useTheme(); 14 | const [mobileOpen, setMobileOpen] = React.useState(false); 15 | 16 | const handleDrawerToggle = () => { 17 | setMobileOpen(!mobileOpen); 18 | }; 19 | 20 | const drawer = ( 21 |
22 | 23 |
24 | ); 25 | return ( 26 | 27 | 28 | 29 | 36 | 37 | 38 | 39 | {" "} 40 | 41 | 42 | 43 | 44 | 45 | 57 | {drawer} 58 | 59 | 60 | 61 | {drawer} 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | ); 75 | } 76 | -------------------------------------------------------------------------------- /src/pages/syllabus/Syllabus.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useTheme } from "@material-ui/core/styles"; 3 | import { useStyles } from "../../utils/useStyles"; 4 | import { Box, Drawer, Grid, Hidden } from "@material-ui/core"; 5 | import MenuIcon from "@material-ui/icons/Menu"; 6 | import Navigation from "../../components/Navigation"; 7 | import Navlinks from "../../components/course/Navlinks"; 8 | import Breadcrumb from "../../components/Breadcrumb"; 9 | import DisplaySyllabus from "./DisplaySyllabus"; 10 | 11 | export default function Syllabus() { 12 | const classes = useStyles(); 13 | const theme = useTheme(); 14 | const [mobileOpen, setMobileOpen] = React.useState(false); 15 | 16 | const handleDrawerToggle = () => { 17 | setMobileOpen(!mobileOpen); 18 | }; 19 | 20 | const drawer = ( 21 |
22 | 23 |
24 | ); 25 | return ( 26 | 27 | 28 | 29 | 36 | 37 | 38 | 39 | {" "} 40 | 41 | 42 | 43 | 44 | 45 | 57 | {drawer} 58 | 59 | 60 | 61 | {drawer} 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | ); 75 | } 76 | -------------------------------------------------------------------------------- /src/pages/discussions/Discussions.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useTheme } from "@material-ui/core/styles"; 3 | import { useStyles } from "../../utils/useStyles"; 4 | import { Box, Drawer, Grid, Hidden } from "@material-ui/core"; 5 | import MenuIcon from "@material-ui/icons/Menu"; 6 | import Navigation from "../../components/Navigation"; 7 | import Navlinks from "../../components/course/Navlinks"; 8 | import Breadcrumb from "../../components/Breadcrumb"; 9 | import DisplayDiscussions from "./DisplayDiscussions"; 10 | 11 | export default function Discussions() { 12 | const classes = useStyles(); 13 | const theme = useTheme(); 14 | const [mobileOpen, setMobileOpen] = React.useState(false); 15 | 16 | const handleDrawerToggle = () => { 17 | setMobileOpen(!mobileOpen); 18 | }; 19 | 20 | const drawer = ( 21 |
22 | 23 |
24 | ); 25 | return ( 26 | 27 | 28 | 29 | 36 | 37 | 38 | 39 | {" "} 40 | 41 | 42 | 43 | 44 | 45 | 57 | {drawer} 58 | 59 | 60 | 61 | {drawer} 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | ); 75 | } 76 | -------------------------------------------------------------------------------- /src/pages/assignments/Assignments.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useTheme } from "@material-ui/core/styles"; 3 | import { useStyles } from "../../utils/useStyles"; 4 | import { Box, Drawer, Grid, Hidden } from "@material-ui/core"; 5 | import MenuIcon from "@material-ui/icons/Menu"; 6 | import Navigation from "../../components/Navigation"; 7 | import Navlinks from "../../components/course/Navlinks"; 8 | import Breadcrumb from "../../components/Breadcrumb"; 9 | import DisplayAssignments from "./DisplayAssignments"; 10 | 11 | export default function Assignments() { 12 | const classes = useStyles(); 13 | const theme = useTheme(); 14 | const [mobileOpen, setMobileOpen] = React.useState(false); 15 | 16 | const handleDrawerToggle = () => { 17 | setMobileOpen(!mobileOpen); 18 | }; 19 | 20 | const drawer = ( 21 |
22 | 23 |
24 | ); 25 | 26 | return ( 27 | 28 | 29 | 30 | 37 | 38 | 39 | 40 | {" "} 41 | 42 | 43 | 44 | 45 | 46 | 58 | {drawer} 59 | 60 | 61 | 62 | {drawer} 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | ); 76 | } 77 | -------------------------------------------------------------------------------- /src/pages/announcements/Announcements.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useTheme } from "@material-ui/core/styles"; 3 | import { useStyles } from "../../utils/useStyles"; 4 | import { Box, Drawer, Grid, Hidden } from "@material-ui/core"; 5 | import MenuIcon from "@material-ui/icons/Menu"; 6 | import Navigation from "../../components/Navigation"; 7 | import Navlinks from "../../components/course/Navlinks"; 8 | import Breadcrumb from "../../components/Breadcrumb"; 9 | import DisplayAnnouncements from "./DisplayAnnouncements"; 10 | 11 | export default function Announcements() { 12 | const classes = useStyles(); 13 | const theme = useTheme(); 14 | const [mobileOpen, setMobileOpen] = React.useState(false); 15 | 16 | const handleDrawerToggle = () => { 17 | setMobileOpen(!mobileOpen); 18 | }; 19 | 20 | const drawer = ( 21 |
22 | 23 |
24 | ); 25 | return ( 26 | 27 | 28 | 29 | 36 | 37 | 38 | 39 | {" "} 40 | 41 | 42 | 43 | 44 | 45 | 57 | {drawer} 58 | 59 | 60 | 61 | {drawer} 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | ); 75 | } 76 | -------------------------------------------------------------------------------- /src/pages/announcements/DisplayAnnouncements.js: -------------------------------------------------------------------------------- 1 | import React, { useContext, useEffect, useState } from "react"; 2 | import Announcement from "./Announcement"; 3 | import NewAnnouncement from "./NewAnnouncement"; 4 | import { DataStore, Predicates, SortDirection } from "@aws-amplify/datastore"; 5 | import { AnnouncementsModel } from "../../models"; 6 | import { useParams } from "react-router"; 7 | import { UserContext } from "../../context/UserContext"; 8 | import { Box, Typography } from "@material-ui/core"; 9 | import announcementImg from "../../assests/announcementImg.svg"; 10 | import useCourses from "../../customHook/useCourses"; 11 | 12 | function DisplayAnnouncement() { 13 | const { id } = useParams(); 14 | const user = useContext(UserContext); 15 | const course = useCourses(id); 16 | const [announcements, setAnnouncements] = useState([]); 17 | 18 | useEffect(() => { 19 | async function getAnnouncements() { 20 | const models = ( 21 | await DataStore.query(AnnouncementsModel, Predicates.ALL, { 22 | sort: (s) => s.createdAt(SortDirection.DESCENDING), 23 | }) 24 | ).filter((c) => c.courseID === id); 25 | 26 | setAnnouncements(models); 27 | } 28 | getAnnouncements(); 29 | const subscription = DataStore.observe(AnnouncementsModel).subscribe( 30 | (msg) => { 31 | getAnnouncements(); 32 | } 33 | ); 34 | 35 | return () => subscription.unsubscribe(); 36 | }, [id]); 37 | 38 | async function handleDelete(id) { 39 | const todelete = await DataStore.query(AnnouncementsModel, id); 40 | DataStore.delete(todelete); 41 | } 42 | return ( 43 |
44 | {course.createdBy === user.username && user.isEducator && ( 45 | 46 | )} 47 | {announcements.length > 0 ? ( 48 | <> 49 | {announcements.map((announcement, index) => ( 50 | 55 | ))} 56 | 57 | ) : ( 58 | 59 | no-discussion 65 | No Announcement posted! 66 | 67 | )} 68 |
69 | ); 70 | } 71 | 72 | export default DisplayAnnouncement; 73 | -------------------------------------------------------------------------------- /src/components/course/CourseList.js: -------------------------------------------------------------------------------- 1 | import React, { useContext, useEffect, useState } from "react"; 2 | import { DataStore, SortDirection } from "@aws-amplify/datastore"; 3 | import { Course, CourseStatus, CourseUser } from "../../models"; 4 | import { API } from "aws-amplify"; 5 | import * as mutations from "../../graphql/mutations"; 6 | import { Container, Grid, Typography } from "@material-ui/core"; 7 | import { UserContext } from "../../context/UserContext"; 8 | import CourseCard from "./CourseCard"; 9 | import nodata from "../../assests/no-data.svg"; 10 | 11 | function CourseList() { 12 | const [courses, setCourses] = useState([]); 13 | const user = useContext(UserContext); 14 | 15 | useEffect(() => { 16 | getCourses(); 17 | const subscription = DataStore.observe(Course).subscribe((msg) => { 18 | getCourses(); 19 | }); 20 | return () => subscription.unsubscribe(); 21 | }, []); 22 | 23 | async function getCourses() { 24 | const models = await DataStore.query( 25 | Course, 26 | (c) => c.status("eq", CourseStatus.PUBLISHED), 27 | { 28 | sort: (s) => s.createdAt(SortDirection.DESCENDING), 29 | } 30 | ); 31 | setCourses(models); 32 | } 33 | async function handleUpdate(courseID) { 34 | const models = await DataStore.query(Course, courseID); 35 | 36 | await DataStore.save( 37 | new CourseUser({ 38 | course: models, 39 | user: user, 40 | }) 41 | ); 42 | const updateCourseDetails = { 43 | id: courseID, 44 | enrolledStudents: user.id, 45 | }; 46 | await API.graphql({ 47 | query: mutations.updateCourse, 48 | variables: { input: updateCourseDetails }, 49 | }); 50 | } 51 | return ( 52 |
53 | 54 | 55 | {courses.length > 0 ? ( 56 | <> 57 | {courses.map((course, index) => ( 58 | 59 | 64 | 65 | ))} 66 | 67 | ) : ( 68 | 69 | no-course 70 | No Course! 71 | 72 | )} 73 | 74 | 75 |
76 | ); 77 | } 78 | 79 | export default CourseList; 80 | -------------------------------------------------------------------------------- /src/pages/lessons/LessonsList.js: -------------------------------------------------------------------------------- 1 | import React, { useContext, useEffect, useState } from "react"; 2 | import ReactPlayer from "react-player/youtube"; 3 | import NewLesson from "./NewLesson"; 4 | import { DataStore } from "@aws-amplify/datastore"; 5 | import { Lesson } from "../../models"; 6 | import { useParams } from "react-router"; 7 | import { Avatar, Card, CardHeader, Grid, IconButton } from "@material-ui/core"; 8 | import { Delete, Edit } from "@material-ui/icons"; 9 | import { UserContext } from "../../context/UserContext"; 10 | import useCourses from "../../customHook/useCourses"; 11 | 12 | function Lessons() { 13 | let { id } = useParams(); 14 | const user = useContext(UserContext); 15 | const [lessons, setLessons] = useState([]); 16 | const course = useCourses(id); 17 | 18 | useEffect(() => { 19 | async function getLessons() { 20 | const models = (await DataStore.query(Lesson)).filter( 21 | (c) => c.courseID === id 22 | ); 23 | setLessons(models); 24 | } 25 | 26 | getLessons(); 27 | const subscription = DataStore.observe(Lesson).subscribe((msg) => { 28 | getLessons(); 29 | }); 30 | return () => subscription.unsubscribe(); 31 | }, [id]); 32 | 33 | async function handleDelete(id) { 34 | const modelToDelete = await DataStore.query(Lesson, id); 35 | DataStore.delete(modelToDelete); 36 | } 37 | return ( 38 | 39 | {course.createdBy === user.username && user.isEducator && } 40 | 41 | {lessons.map((lesson, index) => ( 42 | 48 | 49 | 55 | 56 | 57 | 58 | { 60 | handleDelete(lesson.id); 61 | }} 62 | > 63 | 64 | 65 | 66 | ) 67 | } 68 | avatar={{index + 1}} 69 | /> 70 | 75 | 76 | 77 | ))} 78 | 79 | 80 | ); 81 | } 82 | 83 | export default Lessons; 84 | -------------------------------------------------------------------------------- /CodeSnippets.md: -------------------------------------------------------------------------------- 1 | # Code Snippets for Data Store 2 | 3 | ## Table of Contents 4 | - [Store Data ](#storing-data) 5 | - [Query Data](#query-data) 6 | - [Fetch single data](#case-1--) 7 | - [Fetch all the data](#case-2--) 8 | - [Fetch filtered data](#case-3--) 9 | - [Fetch data in ascending/descending order](#case-4--) 10 | - [Real Time Data](#real-time-data) 11 | - [Delete Data](#delete-data) 12 | 13 | ## Storing data 14 | > To save data in the data model 15 | 16 | ```javascript 17 | async function handleSubmit(e){ 18 | e.preventDefault(); 19 | await DataStore.save( 20 | new Comment({ 21 | "postID": postID, // postID received as props 22 | "createdBy": createdBy, // 23 | "content": comment, 24 | "createdAt":new Date().toLocaleString() 25 | }) 26 | ); 27 | console.log(comment, postID, createdBy) 28 | } 29 | ``` 30 | 31 | ## Query Data 32 | 33 | ### Case 1 - 34 | > To fetch all the data from data model 35 | 36 | ```javascript 37 | async function fetchComments(){ 38 | const comment = await DataStore.query(Comment); 39 | } 40 | ``` 41 | 42 | ### Case 2 - 43 | > To fetch single data from data model 44 | 45 | ```javascript 46 | async function fetchComments(id){ 47 | const comment = await DataStore.query(Comment, id); 48 | console.log(comment); 49 | } 50 | ``` 51 | 52 | ### Case 3 - 53 | > To fetch filtered data from data model 54 | 55 | ```javascript 56 | async function fetchComments() { 57 | const comment = (await DataStore.query(Comment)).filter( 58 | (c) => c.postID === postID //postID received as props 59 | ); 60 | setDisplayComments(comment); 61 | } 62 | ``` 63 | 64 | ### Case 4 - 65 | > To fetch and display data in particular sorted order 66 | 67 | ```javascript 68 | async function fetchComments() { 69 | const comments = await DataStore.query(Comment, Predicates.ALL, { 70 | sort: (s) => s.createdAt(SortDirection.DESCENDING) 71 | }); 72 | /* const posts = await DataStore.query(Comment, Predicates.ALL, { 73 | sort: (s) => s.createdAt(SortDirection.ASCENDING) 74 | }); */ 75 | console.log(comments); 76 | setDisplayComments(comments); 77 | } 78 | ``` 79 | 80 | ## Real Time data 81 | > To display real-time data 82 | 83 | ```javascript 84 | useEffect(() => { 85 | const subscription = DataStore.observe(Comment).subscribe((msg) => { 86 | fetchComments(); 87 | }); 88 | 89 | return () => subscription.unsubscribe(); 90 | }, []); 91 | ``` 92 | 93 | ## Delete Data 94 | > To delete the particular data item from Data model 95 | 96 | ```javascript 97 | async function handleDelete(id) { 98 | 99 | const todelete = await DataStore.query(Comment, id); 100 | DataStore.delete(todelete); 101 | } 102 | ``` 103 | 104 | ## Reference 105 | https://docs.amplify.aws/lib/datastore/getting-started/q/platform/js 106 | -------------------------------------------------------------------------------- /src/pages/assignments/AddAssignment.js: -------------------------------------------------------------------------------- 1 | import React, { useContext, useState } from "react"; 2 | import { Box, Button, Typography } from "@material-ui/core"; 3 | import CheckCircleIcon from "@material-ui/icons/CheckCircle"; 4 | import { useParams } from "react-router"; 5 | import { DataStore } from "@aws-amplify/datastore"; 6 | import { AssignmentModel } from "../../models"; 7 | import { UserContext } from "../../context/UserContext"; 8 | import Amplify, { Storage } from "aws-amplify"; 9 | import awsconfig from "../../aws-exports"; 10 | 11 | Amplify.configure(awsconfig); 12 | 13 | function AddAssignment() { 14 | const { id } = useParams(); 15 | const user = useContext(UserContext); 16 | const [file, setFile] = useState(); 17 | const hiddenFileInput = React.useRef(null); 18 | 19 | async function uploadAssignment() { 20 | const filename = file.name.split("."); 21 | const updatedFilename = 22 | filename[0] + Math.round(new Date().getTime() / 1000) + "." + filename[1]; 23 | const KEY = `${id}/${updatedFilename}`; 24 | 25 | await Storage.put(KEY, file, { 26 | progressCallback(progress) { 27 | if (progress.loaded === progress.total) { 28 | } 29 | }, 30 | }); 31 | await DataStore.save( 32 | new AssignmentModel({ 33 | title: "title", 34 | content: "content", 35 | S3Key: KEY, 36 | courseID: id, 37 | uploadedBy: user.username, 38 | uploadedAt: new Date().toLocaleString(), 39 | }) 40 | ); 41 | setFile(""); 42 | } 43 | 44 | function handleChange(event) { 45 | setFile(event.target.files[0]); 46 | } 47 | const handleClick = (event) => { 48 | hiddenFileInput.current.click(); 49 | }; 50 | return ( 51 | 52 | 59 | 60 | 63 | 70 | 71 | 72 | 73 | {file && ( 74 |
75 | 76 | {file.name} 77 | 85 |
86 | )} 87 |
88 |
89 |
90 | ); 91 | } 92 | 93 | export default AddAssignment; 94 | -------------------------------------------------------------------------------- /src/components/course/EnrollButton.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { Box, Button, Modal, TextField } from "@material-ui/core"; 3 | import { makeStyles } from "@material-ui/core/styles"; 4 | import useCourses from "../../customHook/useCourses"; 5 | 6 | function EnrollButton({ course, handleUpdate }) { 7 | const classes = useStyles(); 8 | const [open, setOpen] = React.useState(false); 9 | const [coursePin, setCoursePin] = useState(); 10 | const courses = useCourses(course.id); 11 | 12 | const handleOpen = () => { 13 | setOpen(true); 14 | }; 15 | 16 | const handleClose = () => { 17 | setOpen(false); 18 | }; 19 | const body = ( 20 | 28 |

Enter the course pin

29 | setCoursePin(event.target.value)} 38 | /> 39 | 40 | 48 | 63 | 64 |
65 | ); 66 | return ( 67 | 68 | 78 | 84 | {body} 85 | 86 | 87 | ); 88 | } 89 | 90 | export default EnrollButton; 91 | 92 | const useStyles = makeStyles((theme) => ({ 93 | paper: { 94 | position: "absolute", 95 | width: 350, 96 | backgroundColor: theme.palette.background.paper, 97 | boxShadow: theme.shadows[5], 98 | padding: theme.spacing(2, 4, 3), 99 | borderRadius: "10px", 100 | }, 101 | })); 102 | -------------------------------------------------------------------------------- /amplify/backend/api/lmsappamplify/schema.graphql: -------------------------------------------------------------------------------- 1 | type AssignmentModel @model @auth(rules: [{allow: public}]) @key(name: "byCourse", fields: ["courseID"]) { 2 | id: ID! 3 | title: String 4 | content: String 5 | S3Key: String 6 | courseID: ID 7 | uploadedBy: String 8 | uploadedAt: String 9 | } 10 | 11 | type SyllabusModel @model @auth(rules: [{allow: public}]) @key(name: "byCourse", fields: ["courseID"]) { 12 | id: ID! 13 | title: String 14 | S3Key: String 15 | courseID: ID 16 | uploadedBy: String 17 | uploadedAt: String 18 | } 19 | 20 | enum CourseStatus { 21 | PUBLISHED 22 | DRAFT 23 | } 24 | 25 | type CommentModel @model @auth(rules: [{allow: public}]) @key(name: "byPostModel", fields: ["postmodelID"]) { 26 | id: ID! 27 | comment: String 28 | createdBy: String 29 | createdAt: String 30 | postmodelID: ID 31 | User: User @connection 32 | } 33 | 34 | type PostModel @model @auth(rules: [{allow: public}]) @key(name: "byCourse", fields: ["courseID"]) { 35 | id: ID! 36 | content: String 37 | createdBy: String 38 | createdAt: String 39 | CommentModels: [CommentModel] @connection(keyName: "byPostModel", fields: ["id"]) 40 | courseID: ID 41 | User: User @connection 42 | } 43 | 44 | type AnnouncementsModel @model @auth(rules: [{allow: public}]) @key(name: "byCourse", fields: ["courseID"]) { 45 | id: ID! 46 | title: String 47 | content: String 48 | createdAt: String 49 | courseID: ID 50 | User: User @connection 51 | } 52 | 53 | type Lesson @model @auth(rules: [{allow: public}]) @key(name: "byCourse", fields: ["courseID"]) { 54 | id: ID! 55 | title: String 56 | summary: String 57 | videoURL: String 58 | courseID: ID 59 | createdBy: String 60 | } 61 | 62 | type Course @model @auth(rules: [{allow: public}]) { 63 | id: ID! 64 | title: String 65 | desc: String 66 | introduction: String 67 | createdBy: String 68 | Lessons: [Lesson] @connection(keyName: "byCourse", fields: ["id"]) 69 | AnnouncementsModels: [AnnouncementsModel] @connection(keyName: "byCourse", fields: ["id"]) 70 | createdAt: String 71 | User: User @connection 72 | status: CourseStatus 73 | PostModels: [PostModel] @connection(keyName: "byCourse", fields: ["id"]) 74 | SyllabusModels: [SyllabusModel] @connection(keyName: "byCourse", fields: ["id"]) 75 | AssignmentModels: [AssignmentModel] @connection(keyName: "byCourse", fields: ["id"]) 76 | enrolledStudents: [String] 77 | CourseUsers: [CourseUser] @connection(keyName: "byCourse", fields: ["id"]) 78 | coursePin: String 79 | } 80 | 81 | type User @model @auth(rules: [{allow: public}]) { 82 | id: ID! 83 | name: String 84 | email: String 85 | username: String 86 | isEducator: Boolean 87 | courses: [CourseUser] @connection(keyName: "byUser", fields: ["id"]) 88 | } 89 | 90 | type CourseUser @model(queries: null) @key(name: "byCourse", fields: ["courseID", "userID"]) @key(name: "byUser", fields: ["userID", "courseID"]) @auth(rules: [{allow: public}, {allow: public}]) { 91 | id: ID! 92 | courseID: ID! 93 | userID: ID! 94 | course: Course! @connection(fields: ["courseID"]) 95 | user: User! @connection(fields: ["userID"]) 96 | } 97 | -------------------------------------------------------------------------------- /src/components/course/Navlinks.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { CssBaseline, List, ListItem, makeStyles } from "@material-ui/core"; 3 | import { useParams } from "react-router"; 4 | import { NavLink } from "react-router-dom"; 5 | import { UserContext } from "../../context/UserContext"; 6 | 7 | function Navlinks() { 8 | let classes = useStyles(); 9 | let { id } = useParams(); 10 | const user = React.useContext(UserContext); 11 | return ( 12 | 13 | 14 | 15 | 16 | 17 | Home 18 | 19 | 20 | 21 | 26 | Announcements 27 | 28 | 29 | 30 | 35 | Assignments 36 | 37 | 38 | 39 | 44 | Lessons 45 | 46 | 47 | 48 | 53 | Discussion 54 | 55 | 56 | 57 | 62 | Syllabus 63 | 64 | 65 | {user.isEducator && ( 66 | 67 | 72 | Students 73 | 74 | 75 | )} 76 | 77 | 78 | ); 79 | } 80 | 81 | export default Navlinks; 82 | 83 | const useStyles = makeStyles((theme) => ({ 84 | root: { 85 | [theme.breakpoints.up("md")]: { 86 | position: "fixed", 87 | left: 100, 88 | padding: "20px", 89 | }, 90 | }, 91 | links: { 92 | padding: "5px", 93 | "&:hover": { 94 | fontWeight: "bold", 95 | }, 96 | }, 97 | activeLinks: { 98 | fontWeight: "bold", 99 | color: "black", 100 | borderLeft: "3px solid black", 101 | padding: "5px", 102 | }, 103 | })); 104 | -------------------------------------------------------------------------------- /src/pages/syllabus/NewSyllabus.js: -------------------------------------------------------------------------------- 1 | import React, { useContext, useState } from "react"; 2 | import { Box, Button, Typography } from "@material-ui/core"; 3 | import CheckCircleIcon from "@material-ui/icons/CheckCircle"; 4 | import { useParams } from "react-router"; 5 | import { DataStore } from "@aws-amplify/datastore"; 6 | import { SyllabusModel } from "../../models"; 7 | import { UserContext } from "../../context/UserContext"; 8 | import Amplify, { Storage } from "aws-amplify"; 9 | import awsconfig from "../../aws-exports"; 10 | 11 | Amplify.configure(awsconfig); 12 | 13 | function NewSyllabus() { 14 | const { id } = useParams(); 15 | const user = useContext(UserContext); 16 | const [file, setFile] = useState(); 17 | const hiddenFileInput = React.useRef(null); 18 | 19 | async function uploadSyllabus() { 20 | const filename = file.name.split("."); 21 | const updatedFilename = 22 | filename[0] + Math.round(new Date().getTime() / 1000) + "." + filename[1]; 23 | const KEY = `${id}/syllabus/${updatedFilename}`; 24 | 25 | await Storage.put(KEY, file, { 26 | progressCallback(progress) { 27 | if (progress.loaded === progress.total) { 28 | } 29 | }, 30 | }).then( 31 | await DataStore.save( 32 | new SyllabusModel({ 33 | title: "title", 34 | S3Key: KEY, 35 | courseID: id, 36 | uploadedBy: user.username, 37 | uploadedAt: new Date().toLocaleString(), 38 | }) 39 | ) 40 | ); 41 | setFile(""); 42 | } 43 | 44 | function handleChange(event) { 45 | setFile(event.target.files[0]); 46 | } 47 | const handleClick = (event) => { 48 | hiddenFileInput.current.click(); 49 | }; 50 | return ( 51 | 52 | 59 | 60 | 63 | 70 | 71 | 72 | {file && ( 73 |
80 | 81 | {file.name} 82 | 90 |
91 | )} 92 |
93 |
94 |
95 | ); 96 | } 97 | 98 | export default NewSyllabus; 99 | -------------------------------------------------------------------------------- /src/components/landingPage/Footer.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Grid, Link, Typography } from "@material-ui/core"; 3 | import FavoriteIcon from "@material-ui/icons/Favorite"; 4 | 5 | function Footer() { 6 | return ( 7 |
8 | {" "} 9 | 10 | 11 | 18 | EduCouch 19 | 20 | 25 | subtitle2. Lorem ipsum dolor sit amet, consectetur adipisicing elit. 26 | Quos blanditiis tenetur 27 | 28 | 29 | 30 | 31 | 32 | Connect with us{" "} 33 | 34 | 35 | gmail 40 | 41 | 42 | github 47 | 48 | 52 | linkedin 57 | 58 | 59 | codepen 64 | 65 | 66 | 67 |
68 |
69 | 78 | 79 | Made by  {" "} 80 | 84 | Palak Gupta 85 | 86 | 87 |
88 |
89 | ); 90 | } 91 | 92 | export default Footer; 93 | -------------------------------------------------------------------------------- /src/components/Navigation.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { makeStyles, Grid, Hidden, Typography } from "@material-ui/core"; 3 | import { NavLink } from "react-router-dom"; 4 | import AccountCircleOutlinedIcon from "@material-ui/icons/AccountCircleOutlined"; 5 | import DashboardRoundedIcon from "@material-ui/icons/DashboardRounded"; 6 | import LibraryBooksRoundedIcon from "@material-ui/icons/LibraryBooksRounded"; 7 | import DateRangeRoundedIcon from "@material-ui/icons/DateRangeRounded"; 8 | import LinkRoundedIcon from "@material-ui/icons/LinkRounded"; 9 | import SignoutButton from "./SignoutButton"; 10 | function Nav() { 11 | const classes = useStyles(); 12 | 13 | return ( 14 |
15 | 20 | 21 | 22 | 23 | 24 | Account 25 | 26 | 27 | 28 | 33 | 34 | 35 | 36 | 37 | Dashboard 38 | 39 | 40 | 41 | 46 | 47 | 48 | 49 | 50 | Courses 51 | 52 | 53 | 54 | 59 | 60 | 61 | 62 | 63 | Calendar 64 | 65 | 66 | 67 | 72 | 73 | 74 | 75 | 76 | Connect 77 | 78 | 79 | 80 |
81 | ); 82 | } 83 | 84 | export default Nav; 85 | 86 | const useStyles = makeStyles((theme) => ({ 87 | nav: { 88 | display: "flex", 89 | flexDirection: "column", 90 | alignItems: "center", 91 | color: "white", 92 | }, 93 | navItem: { 94 | display: "flex", 95 | flexDirection: "column", 96 | alignItems: "center", 97 | padding: "4px", 98 | [theme.breakpoints.up("md")]: { 99 | padding: "15px", 100 | }, 101 | }, 102 | navlink: { 103 | textDecoration: "none", 104 | color: "white", 105 | 106 | "&:hover": { 107 | fontWeight: "bold", 108 | color: theme.palette.secondary.main, 109 | }, 110 | }, 111 | navActive: { 112 | fontWeight: "bold", 113 | color: theme.palette.secondary.main, 114 | }, 115 | })); 116 | -------------------------------------------------------------------------------- /src/components/dashboard/EducatorDashboard.js: -------------------------------------------------------------------------------- 1 | import React, { useContext, useEffect, useState } from "react"; 2 | import PropTypes from "prop-types"; 3 | import { makeStyles } from "@material-ui/core/styles"; 4 | import AppBar from "@material-ui/core/AppBar"; 5 | import Tabs from "@material-ui/core/Tabs"; 6 | import Tab from "@material-ui/core/Tab"; 7 | import Typography from "@material-ui/core/Typography"; 8 | import Box from "@material-ui/core/Box"; 9 | import { Container } from "@material-ui/core"; 10 | import Published from "./Published"; 11 | import AllCourses from "./AllCourses"; 12 | import Drafts from "./Drafts"; 13 | import { CourseContext } from "../../context/CourseContext"; 14 | import { DataStore } from "@aws-amplify/datastore"; 15 | import { Course } from "../../models"; 16 | import { UserContext } from "../../context/UserContext"; 17 | 18 | function EducatorDashboard() { 19 | const user = useContext(UserContext); 20 | const classes = useStyles(); 21 | const [value, setValue] = React.useState(0); 22 | const [courses, setCourses] = useState([]); 23 | 24 | useEffect(() => { 25 | async function getCourses() { 26 | const models = (await DataStore.query(Course)).filter( 27 | (c) => c.createdBy === user.username 28 | ); 29 | setCourses(models); 30 | } 31 | getCourses(); 32 | const subscription = DataStore.observe(Course).subscribe((msg) => { 33 | getCourses(); 34 | }); 35 | return () => subscription.unsubscribe(); 36 | }, [user.username]); 37 | 38 | const handleChange = (event, newValue) => { 39 | setValue(newValue); 40 | }; 41 | 42 | return ( 43 | 44 |
45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 |
70 |
71 | ); 72 | } 73 | export default EducatorDashboard; 74 | 75 | const useStyles = makeStyles((theme) => ({ 76 | root: { 77 | flexGrow: 1, 78 | backgroundColor: theme.palette.background.paper, 79 | marginTop: "40px", 80 | }, 81 | })); 82 | 83 | function TabPanel(props) { 84 | const { children, value, index, ...other } = props; 85 | 86 | return ( 87 | 100 | ); 101 | } 102 | 103 | TabPanel.propTypes = { 104 | children: PropTypes.node, 105 | index: PropTypes.any.isRequired, 106 | value: PropTypes.any.isRequired, 107 | }; 108 | 109 | function a11yProps(index) { 110 | return { 111 | id: `simple-tab-${index}`, 112 | "aria-controls": `simple-tabpanel-${index}`, 113 | }; 114 | } 115 | -------------------------------------------------------------------------------- /src/pages/lessons/NewLesson.js: -------------------------------------------------------------------------------- 1 | import React, { useContext, useState } from "react"; 2 | import { 3 | Button, 4 | TextField, 5 | Dialog, 6 | DialogActions, 7 | DialogContent, 8 | DialogTitle, 9 | } from "@material-ui/core"; 10 | import AddIcon from "@material-ui/icons/Add"; 11 | import { makeStyles } from "@material-ui/core/styles"; 12 | import { useParams } from "react-router"; 13 | import { DataStore } from "@aws-amplify/datastore"; 14 | import { Lesson } from "../../models"; 15 | import { UserContext } from "../../context/UserContext"; 16 | const useStyles = makeStyles((theme) => ({ 17 | form: { 18 | [theme.breakpoints.up("md")]: { 19 | minWidth: 500, 20 | }, 21 | }, 22 | })); 23 | 24 | export default function NewLesson(props) { 25 | const user = useContext(UserContext); 26 | const classes = useStyles(); 27 | let { id } = useParams(); 28 | const [open, setOpen] = useState(false); 29 | const [title, setTitle] = useState(); 30 | const [summary, setSummary] = useState(); 31 | const [url, setURL] = useState(); 32 | 33 | const handleOpen = () => { 34 | setOpen(true); 35 | }; 36 | 37 | const handleClose = () => { 38 | setTitle(""); 39 | setSummary(""); 40 | setURL(""); 41 | setOpen(false); 42 | }; 43 | 44 | async function handleSubmit(event) { 45 | event.preventDefault(); 46 | await DataStore.save( 47 | new Lesson({ 48 | title: title, 49 | summary: summary, 50 | videoURL: url, 51 | courseID: id, 52 | createdBy: user.username, 53 | }) 54 | ); 55 | setTitle(""); 56 | setSummary(""); 57 | setURL(""); 58 | setOpen(false); 59 | } 60 | return ( 61 |
62 | 71 | 76 |
77 | Add New Lesson 78 | 79 | setTitle(event.target.value)} 86 | /> 87 |
88 | setSummary(event.target.value)} 97 | /> 98 |
99 | setURL(event.target.value)} 106 | /> 107 |
108 |
109 | 110 | 111 | 114 | 117 | 118 |
119 |
120 |
121 | ); 122 | } 123 | -------------------------------------------------------------------------------- /src/routes/Routes.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { Switch, Route, Redirect } from "react-router-dom"; 3 | import { UserContext } from "../context/UserContext"; 4 | import { DataStore } from "@aws-amplify/datastore"; 5 | import { User } from "../models"; 6 | import Auth from "@aws-amplify/auth"; 7 | import PrivateRoute from "./PrivateRoute"; 8 | import ProtectedRoute from "./ProtectedRoute"; 9 | 10 | import SignIn from "../auth/SignIn"; 11 | import SignUp from "../auth/SignUp"; 12 | import Account from "../components/account/Account"; 13 | import Calendar from "../components/calendar/Calendar"; 14 | import Connect from "../components/Connect"; 15 | import Dashboard from "../components/dashboard/Dashboard"; 16 | import Error404 from "../components/Error404"; 17 | import Home from "../components/Home"; 18 | import Courses from "../components/course/Courses"; 19 | import CourseDetails from "../components/course/CourseDetails"; 20 | import EditCourse from "../components/course/EditCourse"; 21 | import Assignments from "../pages/assignments/Assignments"; 22 | import Announcements from "../pages/announcements/Announcements"; 23 | import Lessons from "../pages/lessons/Lessons"; 24 | import Discussions from "../pages/discussions/Discussions"; 25 | import Syllabus from "../pages/syllabus/Syllabus"; 26 | import EditAnnouncement from "../pages/announcements/EditAnnouncement"; 27 | import Students from "../pages/students/Students"; 28 | 29 | export default function Routes() { 30 | const [currentUser, setCurrentUser] = useState([]); 31 | useEffect(() => { 32 | getUser(); 33 | const subscription = DataStore.observe(User).subscribe((msg) => { 34 | getUser(); 35 | }); 36 | return () => subscription.unsubscribe(); 37 | }, []); 38 | async function getUser() { 39 | const user = await Auth.currentAuthenticatedUser(); 40 | const currentUser = (await DataStore.query(User)).filter( 41 | (c) => c.username === user.username 42 | ); 43 | setCurrentUser(currentUser); 44 | console.log("user"); 45 | } 46 | return ( 47 | 48 | 49 | 50 | 51 | 52 | {currentUser.map((user, index) => ( 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 68 | 72 | 76 | 80 | 81 | {user.isEducator && ( 82 | 83 | )} 84 | 85 | ))} 86 | 87 | 88 | ); 89 | } 90 | -------------------------------------------------------------------------------- /src/components/dashboard/Drafts.js: -------------------------------------------------------------------------------- 1 | import React, { useContext } from "react"; 2 | import { withStyles, makeStyles } from "@material-ui/core/styles"; 3 | import Table from "@material-ui/core/Table"; 4 | import TableBody from "@material-ui/core/TableBody"; 5 | import TableCell from "@material-ui/core/TableCell"; 6 | import TableContainer from "@material-ui/core/TableContainer"; 7 | import TableHead from "@material-ui/core/TableHead"; 8 | import TableRow from "@material-ui/core/TableRow"; 9 | import Paper from "@material-ui/core/Paper"; 10 | import { CourseContext } from "../../context/CourseContext"; 11 | import { Chip } from "@material-ui/core"; 12 | import LabelIcon from "@material-ui/icons/Label"; 13 | import { Link } from "react-router-dom"; 14 | 15 | const StyledTableCell = withStyles((theme) => ({ 16 | head: { 17 | backgroundColor: theme.palette.common.black, 18 | color: theme.palette.common.white, 19 | }, 20 | body: { 21 | fontSize: 14, 22 | }, 23 | }))(TableCell); 24 | 25 | const StyledTableRow = withStyles((theme) => ({ 26 | root: { 27 | "&:nth-of-type(odd)": { 28 | backgroundColor: theme.palette.action.hover, 29 | }, 30 | }, 31 | }))(TableRow); 32 | 33 | const useStyles = makeStyles({ 34 | table: { 35 | minWidth: 700, 36 | }, 37 | }); 38 | 39 | export default function CustomizedTables() { 40 | const classes = useStyles(); 41 | const courses = useContext(CourseContext); 42 | 43 | return ( 44 | 45 | 46 | 47 | 48 | Course Title 49 | Course Description 50 | Status 51 | Created on 52 | Course Pin 53 | 54 | 55 | 56 | {courses.map((course, index) => ( 57 | <> 58 | {course.status === "DRAFT" && ( 59 | 60 | 61 | 66 | {course.title} 67 | 68 | 69 | 70 | {course.desc} 71 | 72 | 73 | } 75 | label={course.status} 76 | variant="outlined" 77 | style={{ 78 | border: "1px solid rgb(0, 123, 255)", 79 | background: "rgb(0, 123, 255, 0.2)", 80 | }} 81 | /> 82 | 83 | 84 | {course.createdAt} 85 | 86 | 87 | { 88 | 93 | } 94 | 95 | 96 | )} 97 | 98 | ))} 99 | 100 |
101 |
102 | ); 103 | } 104 | -------------------------------------------------------------------------------- /src/auth/SignIn.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import Amplify, { Auth } from "aws-amplify"; 3 | import awsconfig from "../aws-exports"; 4 | import { Link, useHistory } from "react-router-dom"; 5 | import { 6 | makeStyles, 7 | Grid, 8 | Typography, 9 | Container, 10 | TextField, 11 | Button, 12 | Box, 13 | Hidden, 14 | } from "@material-ui/core"; 15 | import login from "../assests/login.svg"; 16 | 17 | Auth.configure(awsconfig); //For NoUserPool Found error 18 | Amplify.configure(awsconfig); 19 | const useStyles = makeStyles((theme) => ({ 20 | right: { 21 | backgroundColor: theme.palette.primary.main, 22 | height: "100vh", 23 | display: "flex", 24 | flexDirection: "column", 25 | justifyContent: "center", 26 | alignItems: "center", 27 | }, 28 | container: { 29 | height: "100vh", 30 | display: "flex", 31 | flexDirection: "column", 32 | justifyContent: "center", 33 | alignItems: "flex-start", 34 | }, 35 | textField: { 36 | width: "70%", 37 | marginBottom: "20px", 38 | }, 39 | })); 40 | const initialFormState = { 41 | username: "", 42 | password: "", 43 | formType: "signIn", 44 | }; 45 | 46 | function SignIn() { 47 | const classes = useStyles(); 48 | let history = useHistory(); 49 | const [formState, updateFormState] = useState(initialFormState); 50 | 51 | const onChange = (e) => { 52 | e.persist(); 53 | updateFormState(() => ({ ...formState, [e.target.name]: e.target.value })); 54 | }; 55 | const { formType } = formState; 56 | 57 | async function signIn() { 58 | const { username, password } = formState; 59 | try { 60 | await Auth.signIn(username, password); 61 | updateFormState(() => ({ ...formState })); 62 | localStorage.setItem("auth", username); 63 | history.push("/"); 64 | window.location.reload(); 65 | } catch (err) { 66 | alert(err.message); 67 | console.log("error signing in user...", err.message); 68 | } 69 | } 70 | 71 | return ( 72 | 73 | 74 | 75 | 76 | login 77 | 78 | 79 | 80 | 81 | {formType === "signIn" && ( 82 | 83 | 84 | Sign In Now! 85 | 86 | 87 | 88 | Username 89 | 90 | 98 | 99 | Password 100 | 101 | 110 | 113 |
114 | Not a member? Sign up now 115 |
116 |
117 | Demo credentials: 118 |

username: test-user

119 |

password: Test@123#

120 |
121 |
122 | )} 123 |
124 |
125 |
126 | ); 127 | } 128 | export default SignIn; 129 | -------------------------------------------------------------------------------- /src/pages/discussions/NewDiscussion.js: -------------------------------------------------------------------------------- 1 | import React, { useContext, useState } from "react"; 2 | import { 3 | Button, 4 | Dialog, 5 | DialogActions, 6 | DialogContent, 7 | DialogTitle, 8 | } from "@material-ui/core"; 9 | import AddIcon from "@material-ui/icons/Add"; 10 | import { makeStyles } from "@material-ui/core/styles"; 11 | import { useParams } from "react-router"; 12 | import { DataStore } from "@aws-amplify/datastore"; 13 | import { PostModel } from "../../models"; 14 | import { UserContext } from "../../context/UserContext"; 15 | import { Editor } from "react-draft-wysiwyg"; 16 | import { EditorState, convertToRaw } from "draft-js"; 17 | import "react-draft-wysiwyg/dist/react-draft-wysiwyg.css"; 18 | import draftToHtml from "draftjs-to-html"; 19 | 20 | const useStyles = makeStyles((theme) => ({ 21 | form: { 22 | [theme.breakpoints.up("md")]: { 23 | minWidth: 500, 24 | }, 25 | }, 26 | })); 27 | 28 | export default function NewDiscussion() { 29 | const user = useContext(UserContext); 30 | const classes = useStyles(); 31 | let { id } = useParams(); 32 | const [open, setOpen] = useState(false); 33 | const [content, setContent] = useState(); 34 | const [editorState, setEditorState] = useState(EditorState.createEmpty()); 35 | 36 | const handleOpen = () => { 37 | setOpen(true); 38 | }; 39 | 40 | const handleClose = () => { 41 | setContent(""); 42 | setOpen(false); 43 | }; 44 | function onEditorStateChange(editorState) { 45 | setContent(draftToHtml(convertToRaw(editorState.getCurrentContent()))); 46 | setEditorState(editorState); 47 | } 48 | async function handleSubmit(event) { 49 | event.preventDefault(); 50 | await DataStore.save( 51 | new PostModel({ 52 | content: content, 53 | createdAt: new Date().toLocaleString(), 54 | createdBy: user.username, 55 | courseID: id, 56 | User: user, 57 | }) 58 | ); 59 | setContent(""); 60 | setEditorState(""); 61 | setOpen(false); 62 | } 63 | return ( 64 |
65 | 74 | 79 |
80 | 81 | Create New Discussion 82 | 83 | 84 | 107 | 108 | 109 | 110 | 113 | 116 | 117 |
118 |
119 |
120 | ); 121 | } 122 | -------------------------------------------------------------------------------- /src/components/dashboard/Published.js: -------------------------------------------------------------------------------- 1 | import React, { useContext } from "react"; 2 | import { withStyles, makeStyles } from "@material-ui/core/styles"; 3 | import Table from "@material-ui/core/Table"; 4 | import TableBody from "@material-ui/core/TableBody"; 5 | import TableCell from "@material-ui/core/TableCell"; 6 | import TableContainer from "@material-ui/core/TableContainer"; 7 | import TableHead from "@material-ui/core/TableHead"; 8 | import TableRow from "@material-ui/core/TableRow"; 9 | import Paper from "@material-ui/core/Paper"; 10 | import { CourseContext } from "../../context/CourseContext"; 11 | import { Chip } from "@material-ui/core"; 12 | import CheckCircleIcon from "@material-ui/icons/CheckCircle"; 13 | import { Link } from "react-router-dom"; 14 | 15 | const StyledTableCell = withStyles((theme) => ({ 16 | head: { 17 | backgroundColor: theme.palette.common.black, 18 | color: theme.palette.common.white, 19 | }, 20 | body: { 21 | fontSize: 14, 22 | }, 23 | }))(TableCell); 24 | 25 | const StyledTableRow = withStyles((theme) => ({ 26 | root: { 27 | "&:nth-of-type(odd)": { 28 | backgroundColor: theme.palette.action.hover, 29 | }, 30 | }, 31 | }))(TableRow); 32 | 33 | const useStyles = makeStyles({ 34 | table: { 35 | minWidth: 700, 36 | }, 37 | }); 38 | 39 | export default function CustomizedTables() { 40 | const classes = useStyles(); 41 | const courses = useContext(CourseContext); 42 | 43 | return ( 44 | 45 | 46 | 47 | 48 | Course Title 49 | Course Description 50 | Status 51 | Created on 52 | Course Pin 53 | 54 | 55 | 56 | {courses.map((course, index) => ( 57 | <> 58 | {course.status === "PUBLISHED" && ( 59 | 60 | 61 | 66 | {course.title} 67 | 68 | 69 | 70 | {course.desc} 71 | 72 | 73 | 78 | } 79 | label={course.status} 80 | variant="outlined" 81 | style={{ 82 | border: "1px solid rgba(34, 208, 36)", 83 | background: "rgba(34, 208, 36, 0.2)", 84 | }} 85 | /> 86 | 87 | 88 | {course.createdAt} 89 | 90 | 91 | { 92 | 97 | } 98 | 99 | 100 | )} 101 | 102 | ))} 103 | 104 |
105 |
106 | ); 107 | } 108 | -------------------------------------------------------------------------------- /public/hero.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/course/NewCourse.js: -------------------------------------------------------------------------------- 1 | import React, { useContext, useState } from "react"; 2 | import { 3 | Button, 4 | TextField, 5 | Dialog, 6 | DialogActions, 7 | DialogContent, 8 | DialogTitle, 9 | } from "@material-ui/core"; 10 | import AddIcon from "@material-ui/icons/Add"; 11 | import { makeStyles } from "@material-ui/core/styles"; 12 | import { DataStore } from "@aws-amplify/datastore"; 13 | import { Course, CourseStatus, CourseUser } from "../../models"; 14 | import { UserContext } from "../../context/UserContext"; 15 | 16 | const useStyles = makeStyles((theme) => ({ 17 | form: { 18 | [theme.breakpoints.up("md")]: { 19 | minWidth: 500, 20 | }, 21 | }, 22 | })); 23 | 24 | export default function NewCourse() { 25 | const user = useContext(UserContext); 26 | const classes = useStyles(); 27 | const [open, setOpen] = useState(false); 28 | const [title, setTitle] = useState(); 29 | const [desc, setDesc] = useState(); 30 | const [coursePin, setCoursePin] = useState(); 31 | 32 | const handleOpen = () => { 33 | setOpen(true); 34 | }; 35 | 36 | const handleClose = () => { 37 | setTitle(""); 38 | setDesc(""); 39 | setOpen(false); 40 | }; 41 | 42 | async function handleSubmit(event) { 43 | event.preventDefault(); 44 | await DataStore.save( 45 | new Course({ 46 | title: title, 47 | desc: desc, 48 | introduction: "", 49 | createdBy: user.username, 50 | createdAt: new Date().toLocaleString(), 51 | User: user, 52 | status: CourseStatus.DRAFT, 53 | enrolledStudents: [user.id], 54 | coursePin: coursePin, 55 | }) 56 | ).then((res) => async () => { 57 | await DataStore.save( 58 | new CourseUser({ 59 | course: res, 60 | user: user, 61 | }) 62 | ); 63 | }); 64 | 65 | setTitle(""); 66 | setDesc(""); 67 | setOpen(false); 68 | } 69 | return ( 70 |
71 | 80 | 85 |
86 | Create New Course 87 | 88 | setTitle(event.target.value)} 96 | /> 97 |
98 | setDesc(event.target.value)} 108 | /> 109 |
110 | setCoursePin(event.target.value)} 118 | /> 119 |
120 |
121 | 122 | 123 | 126 | 134 | 135 |
136 |
137 |
138 | ); 139 | } 140 | -------------------------------------------------------------------------------- /src/assests/hero.svg: -------------------------------------------------------------------------------- 1 | abstract -------------------------------------------------------------------------------- /src/pages/announcements/Edit.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { useHistory, useParams } from "react-router"; 3 | import { DataStore } from "@aws-amplify/datastore"; 4 | import { AnnouncementsModel } from "../../models"; 5 | import { 6 | Box, 7 | Button, 8 | Container, 9 | TextField, 10 | Typography, 11 | } from "@material-ui/core"; 12 | import { Editor } from "react-draft-wysiwyg"; 13 | import { 14 | EditorState, 15 | convertToRaw, 16 | ContentState, 17 | convertFromHTML, 18 | } from "draft-js"; 19 | import "react-draft-wysiwyg/dist/react-draft-wysiwyg.css"; 20 | import draftToHtml from "draftjs-to-html"; 21 | 22 | function Edit() { 23 | const [title, setTitle] = useState(); 24 | const [content, setContent] = useState(); 25 | const [editorState, setEditorState] = useState(); 26 | const { aID } = useParams(); 27 | let history = useHistory(); 28 | useEffect(() => { 29 | async function fetchAnnouncementModels() { 30 | const models = await DataStore.query(AnnouncementsModel, aID); 31 | setTitle(models.title); 32 | setContent(models.content); 33 | setEditorState( 34 | EditorState.createWithContent( 35 | ContentState.createFromBlockArray(convertFromHTML(models.content)) 36 | ) 37 | ); 38 | } 39 | 40 | fetchAnnouncementModels(); 41 | }, [aID]); 42 | 43 | function onEditorStateChange(editorState) { 44 | setContent(draftToHtml(convertToRaw(editorState.getCurrentContent()))); 45 | setEditorState(editorState); 46 | } 47 | 48 | async function handleSubmit(event) { 49 | event.preventDefault(); 50 | if (title === "" || content === "") { 51 | alert("Cannot be blank!"); 52 | } else { 53 | const original = await DataStore.query(AnnouncementsModel, aID); 54 | 55 | await DataStore.save( 56 | AnnouncementsModel.copyOf(original, (updated) => { 57 | updated.title = title; 58 | updated.content = content; 59 | }) 60 | ); 61 | history.goBack(); 62 | } 63 | } 64 | function handleCancel() { 65 | history.goBack(); 66 | } 67 | return ( 68 | 75 | 76 | Edit Announcement 77 | 78 | setTitle(event.target.value)} 87 | /> 88 |
89 | 90 | 114 | 115 | 116 | 117 | 125 | 128 | 129 |
130 | ); 131 | } 132 | 133 | export default Edit; 134 | -------------------------------------------------------------------------------- /src/components/course/CourseDetails.js: -------------------------------------------------------------------------------- 1 | import React, { useContext, useEffect, useState } from "react"; 2 | import { useTheme } from "@material-ui/core/styles"; 3 | import { useStyles } from "../../utils/useStyles"; 4 | import { 5 | AppBar, 6 | Box, 7 | Chip, 8 | Drawer, 9 | Grid, 10 | Hidden, 11 | Toolbar, 12 | } from "@material-ui/core"; 13 | import MenuIcon from "@material-ui/icons/Menu"; 14 | import Navigation from "../Navigation"; 15 | import Navlinks from "./Navlinks"; 16 | import Breadcrumb from "../Breadcrumb"; 17 | import { UserContext } from "../../context/UserContext"; 18 | import { DataStore } from "@aws-amplify/datastore"; 19 | import { Course } from "../../models"; 20 | import { useParams } from "react-router"; 21 | import DeleteButton from "./DeleteButton"; 22 | import EditButton from "./EditButton"; 23 | import CourseIntro from "./CourseIntro"; 24 | import PublishButton from "./PublishButton"; 25 | import CourseStatus from "./CourseStatus"; 26 | 27 | export default function CourseDetails() { 28 | const user = useContext(UserContext); 29 | const classes = useStyles(); 30 | const theme = useTheme(); 31 | const { id } = useParams(); 32 | const [course, setCourse] = useState([]); 33 | const [mobileOpen, setMobileOpen] = React.useState(false); 34 | useEffect(() => { 35 | async function getCourses() { 36 | const models = await DataStore.query(Course, id); 37 | setCourse(models); 38 | } 39 | getCourses(); 40 | }, [id]); 41 | 42 | const handleDrawerToggle = () => { 43 | setMobileOpen(!mobileOpen); 44 | }; 45 | 46 | const drawer = ( 47 |
48 | 49 |
50 | ); 51 | return ( 52 | 53 | 54 | 55 | 56 | 57 | 58 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | {" "} 73 | 74 | 75 | 76 | 77 | 78 | 90 | {drawer} 91 | 92 | 93 | 94 | {drawer} 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | {course.createdBy === user.username && user.isEducator && ( 103 |
113 | 114 | {course.status === "DRAFT" && } 115 | 116 | 117 | 122 |
123 | )} 124 |
125 | 126 | 127 | 128 |
129 |
130 |
131 | ); 132 | } 133 | -------------------------------------------------------------------------------- /src/components/dashboard/AllCourses.js: -------------------------------------------------------------------------------- 1 | import React, { useContext } from "react"; 2 | import { withStyles, makeStyles } from "@material-ui/core/styles"; 3 | import Table from "@material-ui/core/Table"; 4 | import TableBody from "@material-ui/core/TableBody"; 5 | import TableCell from "@material-ui/core/TableCell"; 6 | import TableContainer from "@material-ui/core/TableContainer"; 7 | import TableHead from "@material-ui/core/TableHead"; 8 | import TableRow from "@material-ui/core/TableRow"; 9 | import Paper from "@material-ui/core/Paper"; 10 | import { CourseContext } from "../../context/CourseContext"; 11 | import { Chip } from "@material-ui/core"; 12 | import LabelIcon from "@material-ui/icons/Label"; 13 | import CheckCircleIcon from "@material-ui/icons/CheckCircle"; 14 | import { Link } from "react-router-dom"; 15 | 16 | const StyledTableCell = withStyles((theme) => ({ 17 | head: { 18 | backgroundColor: theme.palette.common.black, 19 | color: theme.palette.common.white, 20 | }, 21 | body: { 22 | fontSize: 14, 23 | }, 24 | }))(TableCell); 25 | 26 | const StyledTableRow = withStyles((theme) => ({ 27 | root: { 28 | "&:nth-of-type(odd)": { 29 | backgroundColor: theme.palette.action.hover, 30 | }, 31 | }, 32 | }))(TableRow); 33 | 34 | const useStyles = makeStyles({ 35 | table: { 36 | minWidth: 700, 37 | }, 38 | }); 39 | 40 | export default function CustomizedTables() { 41 | const classes = useStyles(); 42 | const courses = useContext(CourseContext); 43 | 44 | return ( 45 | 46 | 47 | 48 | 49 | S. No. 50 | Course Title 51 | Course Description 52 | Status 53 | Created On 54 | Course Pin 55 | 56 | 57 | 58 | {courses.map((course, index) => ( 59 | 60 | {index + 1} 61 | 62 | 67 | {course.title} 68 | 69 | 70 | {course.desc} 71 | 72 | {course.status === "PUBLISHED" ? ( 73 | 76 | } 77 | label={course.status} 78 | variant="outlined" 79 | style={{ 80 | border: "1px solid rgba(34, 208, 36)", 81 | background: "rgba(34, 208, 36, 0.2)", 82 | }} 83 | /> 84 | ) : ( 85 | } 87 | label={course.status} 88 | variant="outlined" 89 | style={{ 90 | border: "1px solid rgb(0, 123, 255)", 91 | background: "rgb(0, 123, 255, 0.2)", 92 | }} 93 | /> 94 | )} 95 | 96 | 97 | {course.createdAt} 98 | 99 | 100 | { 101 | 106 | } 107 | 108 | 109 | ))} 110 | 111 |
112 |
113 | ); 114 | } 115 | -------------------------------------------------------------------------------- /src/pages/announcements/NewAnnouncement.js: -------------------------------------------------------------------------------- 1 | import React, { useContext, useState } from "react"; 2 | import { 3 | Button, 4 | TextField, 5 | Dialog, 6 | DialogActions, 7 | DialogContent, 8 | DialogTitle, 9 | } from "@material-ui/core"; 10 | import AddIcon from "@material-ui/icons/Add"; 11 | import { makeStyles } from "@material-ui/core/styles"; 12 | import { useParams } from "react-router"; 13 | import { DataStore } from "@aws-amplify/datastore"; 14 | import { AnnouncementsModel } from "../../models"; 15 | import { UserContext } from "../../context/UserContext"; 16 | import { Editor } from "react-draft-wysiwyg"; 17 | import { EditorState, convertToRaw } from "draft-js"; 18 | import "react-draft-wysiwyg/dist/react-draft-wysiwyg.css"; 19 | import draftToHtml from "draftjs-to-html"; 20 | 21 | const useStyles = makeStyles((theme) => ({ 22 | form: { 23 | [theme.breakpoints.up("md")]: { 24 | minWidth: 500, 25 | }, 26 | }, 27 | })); 28 | 29 | export default function NewAnnouncement() { 30 | const user = useContext(UserContext); 31 | const classes = useStyles(); 32 | let { id } = useParams(); 33 | const [open, setOpen] = useState(false); 34 | const [title, setTitle] = useState(); 35 | const [content, setContent] = useState(); 36 | const [editorState, setEditorState] = useState(EditorState.createEmpty()); 37 | 38 | const handleOpen = () => { 39 | setOpen(true); 40 | }; 41 | 42 | const handleClose = () => { 43 | setTitle(""); 44 | setContent(""); 45 | setOpen(false); 46 | }; 47 | function onEditorStateChange(editorState) { 48 | setContent(draftToHtml(convertToRaw(editorState.getCurrentContent()))); 49 | setEditorState(editorState); 50 | } 51 | async function handleSubmit(event) { 52 | event.preventDefault(); 53 | await DataStore.save( 54 | new AnnouncementsModel({ 55 | title: title, 56 | content: content, 57 | createdAt: new Date().toLocaleString(), 58 | courseID: id, 59 | User: user, 60 | }) 61 | ); 62 | setTitle(""); 63 | setContent(""); 64 | setEditorState(""); 65 | setOpen(false); 66 | } 67 | return ( 68 |
69 | 78 | 83 |
84 | 85 | Create New Announcement 86 | 87 | 88 | setTitle(event.target.value)} 95 | /> 96 |
97 | 121 |
122 | 123 | 124 | 127 | 130 | 131 |
132 |
133 |
134 | ); 135 | } 136 | -------------------------------------------------------------------------------- /src/models/index.d.ts: -------------------------------------------------------------------------------- 1 | import { ModelInit, MutableModel, PersistentModelConstructor } from "@aws-amplify/datastore"; 2 | 3 | export enum CourseStatus { 4 | PUBLISHED = "PUBLISHED", 5 | DRAFT = "DRAFT" 6 | } 7 | 8 | 9 | 10 | export declare class AssignmentModel { 11 | readonly id: string; 12 | readonly title?: string; 13 | readonly content?: string; 14 | readonly S3Key?: string; 15 | readonly courseID?: string; 16 | readonly uploadedBy?: string; 17 | readonly uploadedAt?: string; 18 | constructor(init: ModelInit); 19 | static copyOf(source: AssignmentModel, mutator: (draft: MutableModel) => MutableModel | void): AssignmentModel; 20 | } 21 | 22 | export declare class SyllabusModel { 23 | readonly id: string; 24 | readonly title?: string; 25 | readonly S3Key?: string; 26 | readonly courseID?: string; 27 | readonly uploadedBy?: string; 28 | readonly uploadedAt?: string; 29 | constructor(init: ModelInit); 30 | static copyOf(source: SyllabusModel, mutator: (draft: MutableModel) => MutableModel | void): SyllabusModel; 31 | } 32 | 33 | export declare class CommentModel { 34 | readonly id: string; 35 | readonly comment?: string; 36 | readonly createdBy?: string; 37 | readonly createdAt?: string; 38 | readonly postmodelID?: string; 39 | readonly User?: User; 40 | constructor(init: ModelInit); 41 | static copyOf(source: CommentModel, mutator: (draft: MutableModel) => MutableModel | void): CommentModel; 42 | } 43 | 44 | export declare class User { 45 | readonly id: string; 46 | readonly name?: string; 47 | readonly email?: string; 48 | readonly username?: string; 49 | readonly isEducator?: boolean; 50 | readonly courses?: (CourseUser | null)[]; 51 | constructor(init: ModelInit); 52 | static copyOf(source: User, mutator: (draft: MutableModel) => MutableModel | void): User; 53 | } 54 | 55 | export declare class CourseUser { 56 | readonly id: string; 57 | readonly course: Course; 58 | readonly user: User; 59 | constructor(init: ModelInit); 60 | static copyOf(source: CourseUser, mutator: (draft: MutableModel) => MutableModel | void): CourseUser; 61 | } 62 | 63 | export declare class Course { 64 | readonly id: string; 65 | readonly title?: string; 66 | readonly desc?: string; 67 | readonly introduction?: string; 68 | readonly createdBy?: string; 69 | readonly Lessons?: (Lesson | null)[]; 70 | readonly AnnouncementsModels?: (AnnouncementsModel | null)[]; 71 | readonly createdAt?: string; 72 | readonly User?: User; 73 | readonly status?: CourseStatus | keyof typeof CourseStatus; 74 | readonly PostModels?: (PostModel | null)[]; 75 | readonly SyllabusModels?: (SyllabusModel | null)[]; 76 | readonly AssignmentModels?: (AssignmentModel | null)[]; 77 | readonly enrolledStudents?: (string | null)[]; 78 | readonly CourseUsers?: (CourseUser | null)[]; 79 | readonly coursePin?: string; 80 | constructor(init: ModelInit); 81 | static copyOf(source: Course, mutator: (draft: MutableModel) => MutableModel | void): Course; 82 | } 83 | 84 | export declare class Lesson { 85 | readonly id: string; 86 | readonly title?: string; 87 | readonly summary?: string; 88 | readonly videoURL?: string; 89 | readonly courseID?: string; 90 | readonly createdBy?: string; 91 | constructor(init: ModelInit); 92 | static copyOf(source: Lesson, mutator: (draft: MutableModel) => MutableModel | void): Lesson; 93 | } 94 | 95 | export declare class AnnouncementsModel { 96 | readonly id: string; 97 | readonly title?: string; 98 | readonly content?: string; 99 | readonly createdAt?: string; 100 | readonly courseID?: string; 101 | readonly User?: User; 102 | constructor(init: ModelInit); 103 | static copyOf(source: AnnouncementsModel, mutator: (draft: MutableModel) => MutableModel | void): AnnouncementsModel; 104 | } 105 | 106 | export declare class PostModel { 107 | readonly id: string; 108 | readonly content?: string; 109 | readonly createdBy?: string; 110 | readonly createdAt?: string; 111 | readonly CommentModels?: (CommentModel | null)[]; 112 | readonly courseID?: string; 113 | readonly User?: User; 114 | constructor(init: ModelInit); 115 | static copyOf(source: PostModel, mutator: (draft: MutableModel) => MutableModel | void): PostModel; 116 | } -------------------------------------------------------------------------------- /src/components/course/CourseIntro.js: -------------------------------------------------------------------------------- 1 | import React, { useContext, useState } from "react"; 2 | import { useParams } from "react-router"; 3 | import { DataStore } from "@aws-amplify/datastore"; 4 | import { Course } from "../../models"; 5 | import { Box, Button, Container, Paper, Typography } from "@material-ui/core"; 6 | import { Editor } from "react-draft-wysiwyg"; 7 | import { convertToRaw } from "draft-js"; 8 | import "react-draft-wysiwyg/dist/react-draft-wysiwyg.css"; 9 | import draftToHtml from "draftjs-to-html"; 10 | import ReactHtmlParser from "react-html-parser"; 11 | import { UserContext } from "../../context/UserContext"; 12 | import Avatar from "boring-avatars"; 13 | import useCourses from "../../customHook/useCourses"; 14 | 15 | function CourseIntro() { 16 | const user = useContext(UserContext); 17 | const [introduction, setIntroduction] = useState(); 18 | const [editorState, setEditorState] = useState(); 19 | const { id } = useParams(); 20 | const course = useCourses(id); 21 | 22 | function onEditorStateChange(editorState) { 23 | setIntroduction(draftToHtml(convertToRaw(editorState.getCurrentContent()))); 24 | setEditorState(editorState); 25 | } 26 | 27 | async function handleSubmit(event) { 28 | event.preventDefault(); 29 | const original = await DataStore.query(Course, id); 30 | await DataStore.save( 31 | Course.copyOf(original, (updated) => { 32 | updated.introduction = introduction; 33 | }) 34 | ); 35 | } 36 | 37 | return ( 38 |
39 | 48 | {course.title} 49 | {course.desc} 50 | 59 | Meet your instructor 60 | 66 | @{course.createdBy} 67 | 68 | 69 | 70 | {course.introduction === "" ? ( 71 | 72 | {course.createdBy === user.username && user.isEducator && ( 73 |
74 | 78 | Add Introduction to the course 79 | 80 | 81 | 106 | 107 | {" "} 115 |
116 | )} 117 |
118 | ) : ( 119 | 120 | {ReactHtmlParser(course.introduction)} 121 | 122 | )} 123 |
124 | ); 125 | } 126 | 127 | export default CourseIntro; 128 | -------------------------------------------------------------------------------- /src/pages/discussions/Discussion.js: -------------------------------------------------------------------------------- 1 | import React, { useContext } from "react"; 2 | import { 3 | Box, 4 | Button, 5 | Dialog, 6 | DialogActions, 7 | DialogContent, 8 | DialogTitle, 9 | Grid, 10 | makeStyles, 11 | Paper, 12 | Typography, 13 | } from "@material-ui/core"; 14 | import DeleteIcon from "@material-ui/icons/Delete"; 15 | import { UserContext } from "../../context/UserContext"; 16 | import ReactHtmlParser from "react-html-parser"; 17 | import Avatar from "boring-avatars"; 18 | import AddComment from "./AddComment"; 19 | import DisplayComments from "./DisplayComments"; 20 | 21 | function Discussion({ discussion, handleDelete }) { 22 | const classes = useStyles(); 23 | const user = useContext(UserContext); 24 | const [open, setOpen] = React.useState(false); 25 | 26 | const handleClickOpen = () => { 27 | setOpen(true); 28 | }; 29 | 30 | const handleClose = () => { 31 | handleDelete(discussion.id); 32 | setOpen(false); 33 | }; 34 | const handleCancel = () => { 35 | setOpen(false); 36 | }; 37 | 38 | return ( 39 |
40 | 41 | 42 | 43 | 44 | 45 | 58 | 59 | {discussion.User.name} 60 | 61 | {discussion.createdAt} 62 | 63 | 64 | 65 | {user.username === discussion.User.username && ( 66 | 67 | 68 | 72 | 78 | 79 | {"Are you sure you want to delete this post?"} 80 | 81 | 82 | 83 | 90 | 97 | 98 | 99 | 100 | 101 | )} 102 | 103 | 104 | 105 | {discussion.title} 106 | 107 | 108 | {ReactHtmlParser(discussion.content)} 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 |
121 | ); 122 | } 123 | 124 | export default Discussion; 125 | 126 | const useStyles = makeStyles((theme) => ({ 127 | paper: { 128 | margin: theme.spacing(2), 129 | padding: theme.spacing(2), 130 | }, 131 | header: { 132 | display: "flex", 133 | flexDirection: "row", 134 | padding: "10px 0px", 135 | }, 136 | root: { 137 | display: "flex", 138 | flexDirection: "row", 139 | justifyContent: "space-between", 140 | }, 141 | icons: { 142 | padding: theme.spacing(2), 143 | }, 144 | delete: { 145 | cursor: "pointer", 146 | "&:hover": { color: "red" }, 147 | }, 148 | })); 149 | -------------------------------------------------------------------------------- /src/components/Details.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Box, Container, Divider, Link, Typography } from "@material-ui/core"; 3 | 4 | function Details() { 5 | return ( 6 | 7 | 8 | 9 | This project is built using 10 | 11 | 12 | Frontend 13 | 14 | 15 | 16 | react 21 | 22 | 26 | react-router-dom 31 | 32 | 33 | material-ui 38 | 39 | 40 | Backend 41 | 42 | 43 | 44 | aws 49 | 50 | 51 | graphql 56 | 57 | 58 | 59 | aws-amplify 64 | 65 | 66 | Other Tools 67 | 68 | 69 | 70 | npm 75 | 76 | figma 81 | vs-code 86 | 87 | 88 | 89 | {" "} 90 | Connect or see my other work{" "} 91 | 92 | 93 | 94 | gmail 99 | 100 | 101 | github 106 | 107 | 111 | linkedin 116 | 117 | 118 | codepen 123 | 124 | 125 | 126 | ); 127 | } 128 | 129 | export default Details; 130 | --------------------------------------------------------------------------------