├── .github ├── ISSUE_TEMPLATE │ └── feature_request.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .prettierrc ├── README.md ├── package-lock.json ├── package.json ├── public ├── android-chrome-192x192.png ├── android-chrome-512x512.png ├── favicon.ico ├── index.html ├── manifest.json └── robots.txt └── src ├── App.js ├── assets ├── fonts │ ├── Calibre │ │ ├── Calibre-Light.ttf │ │ ├── Calibre-Light.woff │ │ ├── Calibre-Light.woff2 │ │ ├── Calibre-LightItalic.ttf │ │ ├── Calibre-LightItalic.woff │ │ ├── Calibre-LightItalic.woff2 │ │ ├── Calibre-Medium.ttf │ │ ├── Calibre-Medium.woff │ │ ├── Calibre-Medium.woff2 │ │ ├── Calibre-MediumItalic.ttf │ │ ├── Calibre-MediumItalic.woff │ │ ├── Calibre-MediumItalic.woff2 │ │ ├── Calibre-Regular.ttf │ │ ├── Calibre-Regular.woff │ │ ├── Calibre-Regular.woff2 │ │ ├── Calibre-RegularItalic.ttf │ │ ├── Calibre-RegularItalic.woff │ │ ├── Calibre-RegularItalic.woff2 │ │ ├── Calibre-Semibold.ttf │ │ ├── Calibre-Semibold.woff │ │ ├── Calibre-Semibold.woff2 │ │ ├── Calibre-SemiboldItalic.ttf │ │ ├── Calibre-SemiboldItalic.woff │ │ └── Calibre-SemiboldItalic.woff2 │ ├── Imperial │ │ ├── Imperial Bold Italic.ttf │ │ ├── Imperial Bold.ttf │ │ └── Imperial.ttf │ └── SFMono │ │ ├── SFMono-Medium.ttf │ │ ├── SFMono-Medium.woff │ │ ├── SFMono-Medium.woff2 │ │ ├── SFMono-MediumItalic.ttf │ │ ├── SFMono-MediumItalic.woff │ │ ├── SFMono-MediumItalic.woff2 │ │ ├── SFMono-Regular.ttf │ │ ├── SFMono-Regular.woff │ │ ├── SFMono-Regular.woff2 │ │ ├── SFMono-RegularItalic.ttf │ │ ├── SFMono-RegularItalic.woff │ │ ├── SFMono-RegularItalic.woff2 │ │ ├── SFMono-Semibold.ttf │ │ ├── SFMono-Semibold.woff │ │ ├── SFMono-Semibold.woff2 │ │ ├── SFMono-SemiboldItalic.ttf │ │ ├── SFMono-SemiboldItalic.woff │ │ └── SFMono-SemiboldItalic.woff2 ├── icons │ ├── add_new 1.png │ ├── admin-mentoring.svg │ ├── autograder-loading.svg │ ├── beaker.svg │ ├── cards.svg │ ├── check.js │ ├── checkpoint.svg │ ├── cli.svg │ ├── document.svg │ ├── exclamation.js │ ├── flag.svg │ ├── github.js │ ├── lock.js │ ├── locked-hint.svg │ ├── prof-pic.png │ ├── resume-pie.svg │ ├── split-cards.svg │ ├── start-flag.svg │ ├── tests.zip │ ├── threedots.js │ ├── unused │ │ ├── atom.js │ │ ├── brickwall.js │ │ └── circle.js │ └── upload.svg └── logo │ ├── README.md │ ├── android-chrome-192x192.png │ ├── android-chrome-512x512.png │ ├── apple-touch-icon.png │ ├── browserconfig.xml │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── favicon.ico │ ├── html_code.html │ ├── logo.png │ ├── logo.svg │ ├── mstile-150x150.png │ ├── safari-pinned-tab.svg │ └── site.webmanifest ├── components ├── Error │ └── 404NotFound.js ├── Explore │ ├── Content.js │ ├── Explore.js │ ├── FeaturedDisplay.js │ ├── Sidebar.js │ └── TopicDisplay.js ├── HOC │ ├── WithApiCache.js │ ├── WithAuthentication.js │ ├── WithChakraTheme.js │ ├── WithErrorBoundaries.js │ ├── WithGlobalHOC.js │ ├── WithNavBar.js │ ├── WithPageSpinner.js │ ├── WithStyledTheme.js │ └── WithUserData.js ├── Home.js ├── Learn │ ├── Checkpoint │ │ ├── Autograder │ │ │ ├── CLI.js │ │ │ ├── Home.js │ │ │ └── Result.js │ │ ├── Checkpoint.js │ │ ├── Loading.js │ │ ├── Media │ │ │ └── Home.js │ │ ├── MultipleChoice │ │ │ ├── Home.js │ │ │ └── McCard.js │ │ ├── ShortAnswer │ │ │ └── Home.js │ │ ├── Upload.js │ │ └── unused │ │ │ └── Upload.js │ ├── Concept │ │ ├── Concept.js │ │ └── Slide.js │ ├── Content │ │ ├── Content.js │ │ └── ContentHeader.js │ ├── Hint │ │ ├── LockedHint.js │ │ ├── LockedHintSection.js │ │ ├── UnlockedHint.js │ │ └── UnlockedHintSection.js │ ├── Learn.js │ ├── NextButton │ │ ├── Central │ │ │ └── Central.js │ │ ├── NextButton.js │ │ ├── Peripheral.js │ │ └── SelectState │ │ │ ├── CentralAnimes.js │ │ │ ├── CentralContent.js │ │ │ └── CentralStyles.js │ ├── Sidebar │ │ ├── CardHints.js │ │ ├── Sidebar.js │ │ ├── SidebarHeader.js │ │ └── SidebarNav.js │ ├── Toolbar │ │ ├── Settings.js │ │ └── Toolbar.js │ └── unused │ │ ├── Checkpoint │ │ ├── CheckpointModal.js │ │ ├── Result.js │ │ └── Upload.js │ │ ├── Concept │ │ ├── Concept.js │ │ └── ConceptModal.js │ │ ├── Content │ │ └── Content.js │ │ ├── Hint │ │ ├── Hint.js │ │ ├── HintModal.js │ │ └── HintSection.js │ │ ├── Navigation │ │ ├── NavDropdown.js │ │ └── Navigation.js │ │ └── OldLearn.js ├── NavBar │ └── NavBar.js ├── Student │ ├── Activity │ │ └── Activity.js │ ├── Dashboard │ │ ├── ChooseProject.js │ │ ├── Dashboard.js │ │ ├── DashboardHero.js │ │ ├── Journey │ │ │ ├── Floating.js │ │ │ ├── Journey.js │ │ │ ├── Progress.js │ │ │ ├── ProgressItem.js │ │ │ └── unused │ │ │ │ ├── ActivityCard.js │ │ │ │ ├── Details.js │ │ │ │ └── Journey.js │ │ ├── PickCard.js │ │ ├── Suggested.js │ │ └── unused │ │ │ └── ProgressCard.js │ ├── Module │ │ ├── ActivityList.js │ │ ├── ActivityModal.js │ │ ├── Module.js │ │ └── ProjectModal.js │ ├── Profile │ │ └── Profile.js │ ├── Student.js │ ├── World │ │ └── World.js │ └── unused │ │ ├── CurrentTopics.js │ │ ├── CurrentTrack.js │ │ ├── GreetingSection.js │ │ ├── LabVerticalCard.js │ │ ├── MenuBar.js │ │ ├── OldFinalCopy.js │ │ ├── SelectTopic.js │ │ └── TopicInfo.js ├── Teacher │ ├── Content │ │ ├── Content.js │ │ └── SubmissionCheckpoint.js │ ├── Details │ │ ├── Details.js │ │ └── DetailsHeader.js │ ├── Sidebar │ │ ├── NavItem.js │ │ ├── Sidebar.js │ │ ├── SidebarHeader.js │ │ └── SidebarNav.js │ ├── Teacher.js │ ├── Toolbar │ │ └── Toolbar.js │ └── unused │ │ ├── Grade │ │ ├── EntryBox.js │ │ └── Submission.js │ │ ├── Teacher.js │ │ ├── TeacherContent.js │ │ └── TeacherHero.js ├── Visitor │ └── Visitor.js └── shared │ ├── MarkdownContent.js │ ├── containers │ ├── ActiveList.js │ ├── DynamicModal.js │ ├── PostModal.js │ ├── Scrollable.js │ └── TwoPanel.js │ ├── high │ ├── ActionCard.js │ ├── AvatarGroup.js │ ├── GoBack.js │ ├── GradeStatus.js │ ├── HelpCard.js │ ├── IconWithProgress.js │ ├── MarkdownArea.js │ ├── MuiIconFormatter.js │ └── QuickAction.js │ ├── low │ ├── Button.js │ ├── ConfirmCancel.js │ ├── DotIndicator.js │ ├── DotRating.js │ ├── HeaderShadow.js │ ├── Hero.js │ ├── Icon.js │ ├── IconArea.js │ ├── IconLine.js │ ├── ImgAndContent.js │ ├── Interactive.js │ ├── MediaLightbox.js │ ├── ProfPic.js │ ├── ProgressBar.js │ ├── ProgressCircle.js │ ├── StatusIcon.js │ └── ThreeCheckbox.js │ ├── unused │ ├── Button.js │ └── Card.js │ └── utils │ ├── ClampedDiv.js │ └── ClampedText.js ├── index.js ├── redux ├── actionTypes.js ├── actions │ ├── account.js │ ├── cache.js │ ├── learnData.js │ ├── studentData.js │ ├── teacherData.js │ └── theme.js ├── reducers │ ├── account.js │ ├── cache.js │ ├── learnData.js │ ├── studentData.js │ ├── teacherData.js │ └── theme.js ├── rootReducer.js └── store.js ├── services ├── AccountService.js ├── Auth0Service.js ├── AxiosInstances.js ├── ContentService.js ├── ExploreService.js ├── LearnService.js ├── PusherService.js ├── StudentService.js ├── TeacherService.js └── TestService.js ├── styles ├── GlobalAnime.js ├── GlobalStyles.js ├── media.js └── theme.js └── utils ├── DataStructures.js ├── customHooks.js └── objUtils.js /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Figma Design** 17 | Visualization of what needs to get created 18 | 19 | **Component Details** 20 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # What did you implement/accomplish in this pull request 2 | A clear and concise description of what you did and the changes that you are merging. 3 | 4 | # What are the issues that you completed 5 | Link the issues that you completed/solved: 6 | - #ISSUE_NUMBER (eg: #17) 7 | 8 | # Close Issues(Only fill this out if merging to master branch) 9 | You can use the keyword 'close' and the issue number to close an issue automatically when the pull request is merged 10 | - close #ISSUE_NUMBER (ex: "close #17") 11 | 12 | # Checklist 13 | - [ ] Add staging tag to issues if the pull request is being merged into new-ui 14 | - [ ] Remove staging tag from issues if pull request is being merged in to master 15 | -------------------------------------------------------------------------------- /.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 17 | .env.local 18 | .env.development.local 19 | .env.test.local 20 | .env.production.local 21 | 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | 26 | /.VSCodeCounter 27 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 80, 3 | "singleQuote": true, 4 | "semi": false, 5 | "tabWidth": 2, 6 | "useTabs": true, 7 | "trailingComma": "none", 8 | "arrowParens": "avoid" 9 | } 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bit-project-react", 3 | "version": "0.1.0", 4 | "private": true, 5 | "engines": { 6 | "npm": "6.13.7", 7 | "node": "12.x" 8 | }, 9 | "dependencies": { 10 | "@auth0/auth0-spa-js": "^1.6.5", 11 | "@chakra-ui/core": "^0.7.0", 12 | "@emotion/core": "^10.0.28", 13 | "@emotion/styled": "^10.0.27", 14 | "@material-ui/core": "^4.8.0", 15 | "@material-ui/icons": "^4.5.1", 16 | "@material-ui/lab": "^4.0.0-alpha.49", 17 | "@testing-library/jest-dom": "^4.2.4", 18 | "@testing-library/react": "^9.3.2", 19 | "@testing-library/user-event": "^7.1.2", 20 | "animejs": "^3.1.0", 21 | "auth0-js": "^9.12.2", 22 | "axios": "^0.19.0", 23 | "camelcase-keys-deep": "^0.1.0", 24 | "contentful": "^7.11.3", 25 | "dotenv": "^8.2.0", 26 | "emotion-theming": "^10.0.27", 27 | "filepond": "^4.9.3", 28 | "filepond-plugin-image-exif-orientation": "^1.0.6", 29 | "filepond-plugin-image-preview": "^4.6.0", 30 | "jwt-decode": "^2.2.0", 31 | "lodash": "^4.17.15", 32 | "p-cancelable": "^2.0.0", 33 | "pusher-js": "^5.1.1", 34 | "react": "^16.12.0", 35 | "react-aspect-ratio": "^1.0.42", 36 | "react-avatar": "^3.9.2", 37 | "react-dom": "^16.12.0", 38 | "react-dropzone": "^10.2.1", 39 | "react-expand-animated": "^1.0.1", 40 | "react-filepond": "^7.0.1", 41 | "react-images": "^1.1.0-beta.3", 42 | "react-markdown": "^4.2.2", 43 | "react-portal": "^4.2.1", 44 | "react-pusher": "^0.2.0", 45 | "react-quill": "^1.3.3", 46 | "react-redux": "^7.1.3", 47 | "react-responsive-carousel": "^3.1.51", 48 | "react-router-dom": "^5.1.2", 49 | "react-scripts": "^3.4.1", 50 | "react-scroll": "^1.7.16", 51 | "react-spinkit": "^3.0.0", 52 | "react-syntax-highlighter": "^12.2.1", 53 | "react-transition-group": "^4.3.0", 54 | "redux": "^4.0.4", 55 | "redux-thunk": "^2.3.0", 56 | "rich-text-to-react": "^1.1.0", 57 | "snakecase-keys": "^3.1.2", 58 | "styled-components": "^4.4.1", 59 | "turndown": "^5.0.3", 60 | "use-callback-ref": "^1.2.1", 61 | "uuid": "^7.0.3", 62 | "validator": "^13.0.0" 63 | }, 64 | "scripts": { 65 | "start": "react-scripts start", 66 | "build": "react-scripts build", 67 | "test": "react-scripts test", 68 | "eject": "react-scripts eject" 69 | }, 70 | "eslintConfig": { 71 | "extends": "react-app" 72 | }, 73 | "browserslist": { 74 | "production": [ 75 | ">0.2%", 76 | "not dead", 77 | "not op_mini all" 78 | ], 79 | "development": [ 80 | "last 1 chrome version", 81 | "last 1 firefox version", 82 | "last 1 safari version" 83 | ] 84 | }, 85 | "devDependencies": { 86 | "eslint": "^6.8.0", 87 | "redux-immutable-state-invariant": "^2.1.0" 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /public/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitprj/bit-frontend/d7450ca1e6c1c2c9b6d77cf806c8e92c81ff8956/public/android-chrome-192x192.png -------------------------------------------------------------------------------- /public/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitprj/bit-frontend/d7450ca1e6c1c2c9b6d77cf806c8e92c81ff8956/public/android-chrome-512x512.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitprj/bit-frontend/d7450ca1e6c1c2c9b6d77cf806c8e92c81ff8956/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | Bit Project 13 | 14 | 15 |
16 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Bit Project", 3 | "name": "Bit Project", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "android-chrome-192x192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "android-chrome-512x512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react' 2 | import { Route, Switch, BrowserRouter } from 'react-router-dom' 3 | 4 | import Home from './components/Home' 5 | 6 | import Student from './components/Student/Student' 7 | import Teacher from './components/Teacher/Teacher' 8 | 9 | import Learn from './components/Learn/Learn' 10 | import Explore from './components/Explore/Explore' 11 | import Module from './components/Student/Module/Module' 12 | import Activity from './components/Student/Activity/Activity' 13 | import World from './components/Student/World/World' 14 | 15 | import NotFound from './components/Error/404NotFound' 16 | 17 | import WithGlobalHOC from './components/HOC/WithGlobalHOC' 18 | 19 | import { ping } from './services/TestService' 20 | 21 | const App = () => { 22 | useEffect(() => { 23 | ping() 24 | }, []) 25 | 26 | return ( 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | ) 43 | } 44 | 45 | export default App 46 | -------------------------------------------------------------------------------- /src/assets/fonts/Calibre/Calibre-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitprj/bit-frontend/d7450ca1e6c1c2c9b6d77cf806c8e92c81ff8956/src/assets/fonts/Calibre/Calibre-Light.ttf -------------------------------------------------------------------------------- /src/assets/fonts/Calibre/Calibre-Light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitprj/bit-frontend/d7450ca1e6c1c2c9b6d77cf806c8e92c81ff8956/src/assets/fonts/Calibre/Calibre-Light.woff -------------------------------------------------------------------------------- /src/assets/fonts/Calibre/Calibre-Light.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitprj/bit-frontend/d7450ca1e6c1c2c9b6d77cf806c8e92c81ff8956/src/assets/fonts/Calibre/Calibre-Light.woff2 -------------------------------------------------------------------------------- /src/assets/fonts/Calibre/Calibre-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitprj/bit-frontend/d7450ca1e6c1c2c9b6d77cf806c8e92c81ff8956/src/assets/fonts/Calibre/Calibre-LightItalic.ttf -------------------------------------------------------------------------------- /src/assets/fonts/Calibre/Calibre-LightItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitprj/bit-frontend/d7450ca1e6c1c2c9b6d77cf806c8e92c81ff8956/src/assets/fonts/Calibre/Calibre-LightItalic.woff -------------------------------------------------------------------------------- /src/assets/fonts/Calibre/Calibre-LightItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitprj/bit-frontend/d7450ca1e6c1c2c9b6d77cf806c8e92c81ff8956/src/assets/fonts/Calibre/Calibre-LightItalic.woff2 -------------------------------------------------------------------------------- /src/assets/fonts/Calibre/Calibre-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitprj/bit-frontend/d7450ca1e6c1c2c9b6d77cf806c8e92c81ff8956/src/assets/fonts/Calibre/Calibre-Medium.ttf -------------------------------------------------------------------------------- /src/assets/fonts/Calibre/Calibre-Medium.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitprj/bit-frontend/d7450ca1e6c1c2c9b6d77cf806c8e92c81ff8956/src/assets/fonts/Calibre/Calibre-Medium.woff -------------------------------------------------------------------------------- /src/assets/fonts/Calibre/Calibre-Medium.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitprj/bit-frontend/d7450ca1e6c1c2c9b6d77cf806c8e92c81ff8956/src/assets/fonts/Calibre/Calibre-Medium.woff2 -------------------------------------------------------------------------------- /src/assets/fonts/Calibre/Calibre-MediumItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitprj/bit-frontend/d7450ca1e6c1c2c9b6d77cf806c8e92c81ff8956/src/assets/fonts/Calibre/Calibre-MediumItalic.ttf -------------------------------------------------------------------------------- /src/assets/fonts/Calibre/Calibre-MediumItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitprj/bit-frontend/d7450ca1e6c1c2c9b6d77cf806c8e92c81ff8956/src/assets/fonts/Calibre/Calibre-MediumItalic.woff -------------------------------------------------------------------------------- /src/assets/fonts/Calibre/Calibre-MediumItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitprj/bit-frontend/d7450ca1e6c1c2c9b6d77cf806c8e92c81ff8956/src/assets/fonts/Calibre/Calibre-MediumItalic.woff2 -------------------------------------------------------------------------------- /src/assets/fonts/Calibre/Calibre-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitprj/bit-frontend/d7450ca1e6c1c2c9b6d77cf806c8e92c81ff8956/src/assets/fonts/Calibre/Calibre-Regular.ttf -------------------------------------------------------------------------------- /src/assets/fonts/Calibre/Calibre-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitprj/bit-frontend/d7450ca1e6c1c2c9b6d77cf806c8e92c81ff8956/src/assets/fonts/Calibre/Calibre-Regular.woff -------------------------------------------------------------------------------- /src/assets/fonts/Calibre/Calibre-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitprj/bit-frontend/d7450ca1e6c1c2c9b6d77cf806c8e92c81ff8956/src/assets/fonts/Calibre/Calibre-Regular.woff2 -------------------------------------------------------------------------------- /src/assets/fonts/Calibre/Calibre-RegularItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitprj/bit-frontend/d7450ca1e6c1c2c9b6d77cf806c8e92c81ff8956/src/assets/fonts/Calibre/Calibre-RegularItalic.ttf -------------------------------------------------------------------------------- /src/assets/fonts/Calibre/Calibre-RegularItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitprj/bit-frontend/d7450ca1e6c1c2c9b6d77cf806c8e92c81ff8956/src/assets/fonts/Calibre/Calibre-RegularItalic.woff -------------------------------------------------------------------------------- /src/assets/fonts/Calibre/Calibre-RegularItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitprj/bit-frontend/d7450ca1e6c1c2c9b6d77cf806c8e92c81ff8956/src/assets/fonts/Calibre/Calibre-RegularItalic.woff2 -------------------------------------------------------------------------------- /src/assets/fonts/Calibre/Calibre-Semibold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitprj/bit-frontend/d7450ca1e6c1c2c9b6d77cf806c8e92c81ff8956/src/assets/fonts/Calibre/Calibre-Semibold.ttf -------------------------------------------------------------------------------- /src/assets/fonts/Calibre/Calibre-Semibold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitprj/bit-frontend/d7450ca1e6c1c2c9b6d77cf806c8e92c81ff8956/src/assets/fonts/Calibre/Calibre-Semibold.woff -------------------------------------------------------------------------------- /src/assets/fonts/Calibre/Calibre-Semibold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitprj/bit-frontend/d7450ca1e6c1c2c9b6d77cf806c8e92c81ff8956/src/assets/fonts/Calibre/Calibre-Semibold.woff2 -------------------------------------------------------------------------------- /src/assets/fonts/Calibre/Calibre-SemiboldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitprj/bit-frontend/d7450ca1e6c1c2c9b6d77cf806c8e92c81ff8956/src/assets/fonts/Calibre/Calibre-SemiboldItalic.ttf -------------------------------------------------------------------------------- /src/assets/fonts/Calibre/Calibre-SemiboldItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitprj/bit-frontend/d7450ca1e6c1c2c9b6d77cf806c8e92c81ff8956/src/assets/fonts/Calibre/Calibre-SemiboldItalic.woff -------------------------------------------------------------------------------- /src/assets/fonts/Calibre/Calibre-SemiboldItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitprj/bit-frontend/d7450ca1e6c1c2c9b6d77cf806c8e92c81ff8956/src/assets/fonts/Calibre/Calibre-SemiboldItalic.woff2 -------------------------------------------------------------------------------- /src/assets/fonts/Imperial/Imperial Bold Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitprj/bit-frontend/d7450ca1e6c1c2c9b6d77cf806c8e92c81ff8956/src/assets/fonts/Imperial/Imperial Bold Italic.ttf -------------------------------------------------------------------------------- /src/assets/fonts/Imperial/Imperial Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitprj/bit-frontend/d7450ca1e6c1c2c9b6d77cf806c8e92c81ff8956/src/assets/fonts/Imperial/Imperial Bold.ttf -------------------------------------------------------------------------------- /src/assets/fonts/Imperial/Imperial.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitprj/bit-frontend/d7450ca1e6c1c2c9b6d77cf806c8e92c81ff8956/src/assets/fonts/Imperial/Imperial.ttf -------------------------------------------------------------------------------- /src/assets/fonts/SFMono/SFMono-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitprj/bit-frontend/d7450ca1e6c1c2c9b6d77cf806c8e92c81ff8956/src/assets/fonts/SFMono/SFMono-Medium.ttf -------------------------------------------------------------------------------- /src/assets/fonts/SFMono/SFMono-Medium.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitprj/bit-frontend/d7450ca1e6c1c2c9b6d77cf806c8e92c81ff8956/src/assets/fonts/SFMono/SFMono-Medium.woff -------------------------------------------------------------------------------- /src/assets/fonts/SFMono/SFMono-Medium.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitprj/bit-frontend/d7450ca1e6c1c2c9b6d77cf806c8e92c81ff8956/src/assets/fonts/SFMono/SFMono-Medium.woff2 -------------------------------------------------------------------------------- /src/assets/fonts/SFMono/SFMono-MediumItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitprj/bit-frontend/d7450ca1e6c1c2c9b6d77cf806c8e92c81ff8956/src/assets/fonts/SFMono/SFMono-MediumItalic.ttf -------------------------------------------------------------------------------- /src/assets/fonts/SFMono/SFMono-MediumItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitprj/bit-frontend/d7450ca1e6c1c2c9b6d77cf806c8e92c81ff8956/src/assets/fonts/SFMono/SFMono-MediumItalic.woff -------------------------------------------------------------------------------- /src/assets/fonts/SFMono/SFMono-MediumItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitprj/bit-frontend/d7450ca1e6c1c2c9b6d77cf806c8e92c81ff8956/src/assets/fonts/SFMono/SFMono-MediumItalic.woff2 -------------------------------------------------------------------------------- /src/assets/fonts/SFMono/SFMono-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitprj/bit-frontend/d7450ca1e6c1c2c9b6d77cf806c8e92c81ff8956/src/assets/fonts/SFMono/SFMono-Regular.ttf -------------------------------------------------------------------------------- /src/assets/fonts/SFMono/SFMono-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitprj/bit-frontend/d7450ca1e6c1c2c9b6d77cf806c8e92c81ff8956/src/assets/fonts/SFMono/SFMono-Regular.woff -------------------------------------------------------------------------------- /src/assets/fonts/SFMono/SFMono-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitprj/bit-frontend/d7450ca1e6c1c2c9b6d77cf806c8e92c81ff8956/src/assets/fonts/SFMono/SFMono-Regular.woff2 -------------------------------------------------------------------------------- /src/assets/fonts/SFMono/SFMono-RegularItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitprj/bit-frontend/d7450ca1e6c1c2c9b6d77cf806c8e92c81ff8956/src/assets/fonts/SFMono/SFMono-RegularItalic.ttf -------------------------------------------------------------------------------- /src/assets/fonts/SFMono/SFMono-RegularItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitprj/bit-frontend/d7450ca1e6c1c2c9b6d77cf806c8e92c81ff8956/src/assets/fonts/SFMono/SFMono-RegularItalic.woff -------------------------------------------------------------------------------- /src/assets/fonts/SFMono/SFMono-RegularItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitprj/bit-frontend/d7450ca1e6c1c2c9b6d77cf806c8e92c81ff8956/src/assets/fonts/SFMono/SFMono-RegularItalic.woff2 -------------------------------------------------------------------------------- /src/assets/fonts/SFMono/SFMono-Semibold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitprj/bit-frontend/d7450ca1e6c1c2c9b6d77cf806c8e92c81ff8956/src/assets/fonts/SFMono/SFMono-Semibold.ttf -------------------------------------------------------------------------------- /src/assets/fonts/SFMono/SFMono-Semibold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitprj/bit-frontend/d7450ca1e6c1c2c9b6d77cf806c8e92c81ff8956/src/assets/fonts/SFMono/SFMono-Semibold.woff -------------------------------------------------------------------------------- /src/assets/fonts/SFMono/SFMono-Semibold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitprj/bit-frontend/d7450ca1e6c1c2c9b6d77cf806c8e92c81ff8956/src/assets/fonts/SFMono/SFMono-Semibold.woff2 -------------------------------------------------------------------------------- /src/assets/fonts/SFMono/SFMono-SemiboldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitprj/bit-frontend/d7450ca1e6c1c2c9b6d77cf806c8e92c81ff8956/src/assets/fonts/SFMono/SFMono-SemiboldItalic.ttf -------------------------------------------------------------------------------- /src/assets/fonts/SFMono/SFMono-SemiboldItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitprj/bit-frontend/d7450ca1e6c1c2c9b6d77cf806c8e92c81ff8956/src/assets/fonts/SFMono/SFMono-SemiboldItalic.woff -------------------------------------------------------------------------------- /src/assets/fonts/SFMono/SFMono-SemiboldItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitprj/bit-frontend/d7450ca1e6c1c2c9b6d77cf806c8e92c81ff8956/src/assets/fonts/SFMono/SFMono-SemiboldItalic.woff2 -------------------------------------------------------------------------------- /src/assets/icons/add_new 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitprj/bit-frontend/d7450ca1e6c1c2c9b6d77cf806c8e92c81ff8956/src/assets/icons/add_new 1.png -------------------------------------------------------------------------------- /src/assets/icons/beaker.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/assets/icons/cards.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/assets/icons/check.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default ({ color, check_width, check_height, icon_style }) => { 4 | const container_style = { 5 | minWidth: check_width, 6 | marginTop: '-.7rem' 7 | } 8 | 9 | return ( 10 |
11 | 12 | 13 | 17 | 18 | 19 |
20 | ) 21 | }; 22 | -------------------------------------------------------------------------------- /src/assets/icons/cli.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/assets/icons/exclamation.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const exclamation = () => ( 4 | 11 | 12 | 13 | 14 | 15 | ) 16 | 17 | export default exclamation 18 | -------------------------------------------------------------------------------- /src/assets/icons/flag.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/assets/icons/github.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default ({ color, width, height }) => ( 4 | 5 | 14 | 15 | ); 16 | -------------------------------------------------------------------------------- /src/assets/icons/lock.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default ({ color, width, height }) => ( 4 | // More advanced padlock but relies on background color for padlock 5 | 6 | 7 | 11 | 15 | 21 | 22 | 23 | 24 | // Simple black-outline padlock 25 | // 26 | // 30 | // 35 | // 39 | // 40 | ); 41 | -------------------------------------------------------------------------------- /src/assets/icons/prof-pic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitprj/bit-frontend/d7450ca1e6c1c2c9b6d77cf806c8e92c81ff8956/src/assets/icons/prof-pic.png -------------------------------------------------------------------------------- /src/assets/icons/resume-pie.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/icons/split-cards.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/assets/icons/tests.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitprj/bit-frontend/d7450ca1e6c1c2c9b6d77cf806c8e92c81ff8956/src/assets/icons/tests.zip -------------------------------------------------------------------------------- /src/assets/icons/threedots.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from 'styled-components'; 3 | 4 | const ThreeDots = styled.div` 5 | 6 | ` 7 | 8 | export default ({ color, width, height }) => ( 9 | 10 | 11 | 17 | 18 | 19 | ); 20 | -------------------------------------------------------------------------------- /src/assets/icons/unused/brickwall.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default ({ color, width, height }) => ( 4 | 5 | 15 | 16 | ); 17 | -------------------------------------------------------------------------------- /src/assets/icons/unused/circle.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export default ({ color, width, height }) => ( 4 | 5 | 6 | 7 | ); 8 | -------------------------------------------------------------------------------- /src/assets/icons/upload.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/assets/logo/README.md: -------------------------------------------------------------------------------- 1 | # Your Favicon Package 2 | 3 | This package was generated with [RealFaviconGenerator](https://realfavicongenerator.net/) [v0.16](https://realfavicongenerator.net/change_log#v0.16) 4 | 5 | ## Install instructions 6 | 7 | To install this package: 8 | 9 | Extract this package in the root of your web site. If your site is http://www.example.com, you should be able to access a file named http://www.example.com/favicon.ico. 10 | 11 | Insert the following code in the `head` section of your pages: 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | *Optional* - Check your favicon with the [favicon checker](https://realfavicongenerator.net/favicon_checker) -------------------------------------------------------------------------------- /src/assets/logo/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitprj/bit-frontend/d7450ca1e6c1c2c9b6d77cf806c8e92c81ff8956/src/assets/logo/android-chrome-192x192.png -------------------------------------------------------------------------------- /src/assets/logo/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitprj/bit-frontend/d7450ca1e6c1c2c9b6d77cf806c8e92c81ff8956/src/assets/logo/android-chrome-512x512.png -------------------------------------------------------------------------------- /src/assets/logo/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitprj/bit-frontend/d7450ca1e6c1c2c9b6d77cf806c8e92c81ff8956/src/assets/logo/apple-touch-icon.png -------------------------------------------------------------------------------- /src/assets/logo/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #9acfff 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/assets/logo/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitprj/bit-frontend/d7450ca1e6c1c2c9b6d77cf806c8e92c81ff8956/src/assets/logo/favicon-16x16.png -------------------------------------------------------------------------------- /src/assets/logo/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitprj/bit-frontend/d7450ca1e6c1c2c9b6d77cf806c8e92c81ff8956/src/assets/logo/favicon-32x32.png -------------------------------------------------------------------------------- /src/assets/logo/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitprj/bit-frontend/d7450ca1e6c1c2c9b6d77cf806c8e92c81ff8956/src/assets/logo/favicon.ico -------------------------------------------------------------------------------- /src/assets/logo/html_code.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/assets/logo/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitprj/bit-frontend/d7450ca1e6c1c2c9b6d77cf806c8e92c81ff8956/src/assets/logo/logo.png -------------------------------------------------------------------------------- /src/assets/logo/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitprj/bit-frontend/d7450ca1e6c1c2c9b6d77cf806c8e92c81ff8956/src/assets/logo/mstile-150x150.png -------------------------------------------------------------------------------- /src/assets/logo/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/logo/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Bit Project", 3 | "short_name": "Bit Project", 4 | "icons": [ 5 | { 6 | "src": "/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "/android-chrome-512x512.png", 12 | "sizes": "512x512", 13 | "type": "image/png" 14 | } 15 | ], 16 | "theme_color": "#9acfff", 17 | "background_color": "#9acfff", 18 | "display": "standalone" 19 | } 20 | -------------------------------------------------------------------------------- /src/components/Error/404NotFound.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | 4 | const Container = styled.div` 5 | padding: 2em; 6 | text-align: center; 7 | ` 8 | 9 | const NotFound = () => ( 10 | 11 |

You appear to be on a missing page. Oh well!

12 |
13 |

(ノ◕ヮ◕)ノ︵ ✧・゚┻━┻*:・゚✧

14 |
15 | ) 16 | 17 | export default NotFound 18 | -------------------------------------------------------------------------------- /src/components/Explore/Content.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | 4 | import FeaturedDisplay from './FeaturedDisplay' 5 | import TopicDisplay from './TopicDisplay' 6 | 7 | const Container = styled.div` 8 | padding-top: 2em; 9 | width: calc(100% - 18em); 10 | ` 11 | 12 | const HorzScroll = styled.div` 13 | padding: 2em; 14 | padding-right: 2em; 15 | display: flex; 16 | flex-wrap: nowrap; 17 | 18 | > div { 19 | flex-shrink: 0; 20 | margin-right: 2em; 21 | } 22 | 23 | > div:last-child::before { 24 | content: ''; 25 | margin-right: 3em; 26 | display: block; 27 | // width: 40em; 28 | height: 0.1px; 29 | } 30 | ` 31 | 32 | const Content = props => { 33 | return ( 34 | 35 |

{props.activeName}

36 | 37 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 |
51 | ) 52 | } 53 | 54 | export default Content 55 | -------------------------------------------------------------------------------- /src/components/Explore/Explore.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import styled from 'styled-components' 3 | 4 | import Sidebar from './Sidebar' 5 | import Content from './Content' 6 | 7 | import { sizes } from '../../styles/media' 8 | 9 | const Container = styled.div` 10 | display: flex; 11 | overflow-x: hidden; 12 | font-size: 80%; 13 | 14 | @media screen and (orientation: portrait) and (max-width: ${sizes.bigDesktop}px) { 15 | font-size: 100%; 16 | } 17 | ` 18 | 19 | const views = [ 20 | { 21 | // name: "👋 For You", 22 | name: 'For You', 23 | topics: [{}, {}] 24 | }, 25 | { 26 | // name: "✏ Design", 27 | name: 'Design', 28 | topics: [{}, {}] 29 | }, 30 | { 31 | // name: "📈 Statistics", 32 | name: 'Statistics', 33 | topics: [{}, {}] 34 | }, 35 | { 36 | // name: "🐍 Python", 37 | name: 'Python', 38 | topics: [{}, {}] 39 | }, 40 | { 41 | // name: "💻 Web Development ", 42 | name: 'Web Development ', 43 | topics: [{}, {}] 44 | }, 45 | { 46 | // name: "⚛️ Logic and Math", 47 | name: 'Logic and Math', 48 | topics: [{}, {}] 49 | }, 50 | { 51 | // name: "📠 Machine Learning", 52 | name: 'Machine Learning', 53 | topics: [{}, {}] 54 | } 55 | ] 56 | 57 | const Explore = () => { 58 | const [activeName, setActiveName] = useState(views[0].name) 59 | 60 | return ( 61 | 62 | 67 | 68 | 69 | ) 70 | } 71 | 72 | export default Explore 73 | -------------------------------------------------------------------------------- /src/components/Explore/FeaturedDisplay.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled from "styled-components"; 3 | 4 | const Featured = styled.div` 5 | padding: 4em 2.5em; 6 | padding-right: 16em; 7 | height: 20em; 8 | width: 40em; 9 | position: relative; 10 | cursor: pointer; 11 | 12 | border-radius: 0.8em; 13 | background: ${props => props.theme.accentVariant} url(${props => props.url}); 14 | ${props => (props.bgColor ? `background-color: ${props.bgColor}` : "")}; 15 | background-size: 100% auto; 16 | background-position: bottom right; 17 | `; 18 | 19 | const Category = styled.div` 20 | position: absolute; 21 | bottom: 2em; 22 | left: 2.5em; 23 | font-weight: bold; 24 | `; 25 | 26 | const FeaturedDisplay = props => { 27 | return ( 28 | 33 |

{props.name}

34 | {props.category} 35 |
36 | ); 37 | }; 38 | 39 | export default FeaturedDisplay; 40 | -------------------------------------------------------------------------------- /src/components/Explore/Sidebar.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled from "styled-components"; 3 | 4 | const Container = styled.div` 5 | padding: 2em 3em; 6 | padding-right: 0; 7 | width: 18em; 8 | `; 9 | 10 | const MenuItem = styled.div` 11 | margin: 0.8em 0; 12 | padding: 0.3em 0.8em; 13 | padding-right: 1em; 14 | border-radius: 50em; 15 | width: fit-content; 16 | 17 | cursor: pointer; 18 | transition: ease 0.25s all; 19 | 20 | &:hover { 21 | background-color: #0002; 22 | } 23 | 24 | &.active { 25 | color: #fff; 26 | background-color: #000; 27 | cursor: default; 28 | } 29 | `; 30 | 31 | const Sidebar = props => { 32 | const handleClick = name => { 33 | props.setActiveName(name); 34 | console.log(name) 35 | }; 36 | 37 | const sidebarItems = props.views.map((view, index) => { 38 | const className = props.activeName === view.name ? "active" : ""; 39 | return ( 40 | handleClick(view.name)} 44 | > 45 | {view.name} 46 | 47 | ); 48 | }); 49 | 50 | return ( 51 | 52 |

Explore

53 | {sidebarItems} 54 |
55 | ); 56 | }; 57 | 58 | export default Sidebar; 59 | -------------------------------------------------------------------------------- /src/components/Explore/TopicDisplay.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | 4 | import AccessTimeIcon from '@material-ui/icons/AccessTime' 5 | import IconLine from '../shared/low/IconLine' 6 | import Icon from '../shared/low/Icon' 7 | 8 | const Container = styled.div`` 9 | 10 | const RenderedActivity = styled.div` 11 | padding: 1.5em 1.5em; 12 | margin: 0.5em 0; 13 | display: flex; 14 | align-items: center; 15 | cursor: pointer; 16 | ` 17 | 18 | const AppIcon = styled(Icon)` 19 | margin-right: 1.6em; 20 | border-radius: 1.4em; 21 | ` 22 | 23 | const Activity = props => { 24 | return ( 25 | 26 | 27 |
28 |

{props.name}

29 | }>{props.time} 30 |
31 |
32 | ) 33 | } 34 | 35 | const TopicDisplay = props => { 36 | return ( 37 | 38 |

{props.name}

39 | 40 | 41 | 42 | 43 |
44 | ) 45 | } 46 | 47 | export default TopicDisplay 48 | -------------------------------------------------------------------------------- /src/components/HOC/WithAuthentication.js: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react' 2 | import { connect } from 'react-redux' 3 | 4 | import { fetchMetaData } from '../../services/AccountService' 5 | import { authenticate, deauthenticate } from '../../redux/actions/account' 6 | 7 | const WithAuthentication = ({ 8 | children, 9 | isVisitor, 10 | onAuthenticate, 11 | onDeauthenticate 12 | }) => { 13 | useEffect(() => { 14 | const storedMeta = localStorage.getItem('meta') 15 | if (storedMeta) return 16 | ;(async () => { 17 | try { 18 | const meta = await fetchMetaData() 19 | onAuthenticate(meta) 20 | } catch (error) { 21 | if (!isVisitor) onDeauthenticate() 22 | console.log('[WithAuthentication] deauthenticated') 23 | // history.push('/') axios instance does it 24 | } 25 | })() 26 | }, []) 27 | 28 | return children 29 | } 30 | 31 | const mapStateToProps = state => ({ 32 | isVisitor: !state.account.meta 33 | }) 34 | 35 | const mapDispatchToProps = dispatch => ({ 36 | onAuthenticate: meta => dispatch(authenticate(meta)), 37 | onDeauthenticate: () => dispatch(deauthenticate()) 38 | }) 39 | 40 | export default connect(mapStateToProps, mapDispatchToProps)(WithAuthentication) 41 | -------------------------------------------------------------------------------- /src/components/HOC/WithChakraTheme.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { ThemeProvider, CSSReset, theme } from '@chakra-ui/core' 3 | 4 | export const chakraTheme = { 5 | accent: '#007bed', 6 | accentVariant: '#9acfff', //86c5ff 7 | mild: '#00498c', 8 | bg: '#0a192f', 9 | bgVariant: '#172A45', 10 | bgPage: '#f5faff' 11 | } 12 | 13 | export const customTheme = { 14 | ...theme, 15 | fonts: { 16 | body: 'Open Sans', 17 | heading: 'Poppins', 18 | monospace: 'Source Code Pro' 19 | }, 20 | colors: { 21 | ...theme.colors, 22 | theme: chakraTheme 23 | } 24 | } 25 | 26 | const WithChakraTheme = ({ children }) => { 27 | return ( 28 | 29 | {/* */} 30 | {children} 31 | 32 | ) 33 | } 34 | 35 | export default WithChakraTheme 36 | -------------------------------------------------------------------------------- /src/components/HOC/WithErrorBoundaries.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | 3 | // use like so: 4 | // const [, setError] = useState(null) 5 | // 6 | // setError(() => { // trigger rerender and throw error from trycatch 7 | // throw error 8 | // }) 9 | class WithErrorBoundaries extends Component { 10 | constructor(props) { 11 | super(props) 12 | this.state = { error: null, errorInfo: null } 13 | } 14 | 15 | static getDerivedStateFromError(error) { 16 | // Update state so the next render will show the fallback UI. 17 | return { error: true, errorInfo: error } 18 | } 19 | 20 | componentDidCatch(error, errorInfo) { 21 | console.log(error) 22 | // Catch errors in any components below and re-render with error message 23 | // this.setState({ error, errorInfo }) 24 | // You can also log error messages to an error reporting service here 25 | } 26 | 27 | render() { 28 | if (this.state.error) { 29 | console.log(this.state.error.response) 30 | // Error path 31 | return ( 32 | <> 33 |

Something went wrong.

34 | {/*
35 | {this.state.error && this.state.error.toString()} 36 |
37 | {this.state.errorInfo.componentStack} 38 |
*/} 39 | 40 | ) 41 | } 42 | 43 | return this.props.children 44 | } 45 | } 46 | 47 | export default WithErrorBoundaries 48 | -------------------------------------------------------------------------------- /src/components/HOC/WithGlobalHOC.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import WithStyledTheme from './WithStyledTheme' 4 | import WithChakraTheme from './WithChakraTheme' 5 | import WithErrorBoundaries from './WithErrorBoundaries' 6 | import WithAuthentication from './WithAuthentication' 7 | import WithUserData from './WithUserData' 8 | import WithNavBar from './WithNavBar' 9 | 10 | const WithGlobalHOC = ({ children }) => ( 11 | 12 | 13 | 14 | 15 | 16 | {children} 17 | 18 | 19 | 20 | 21 | 22 | ) 23 | 24 | export default WithGlobalHOC 25 | -------------------------------------------------------------------------------- /src/components/HOC/WithNavBar.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Switch, Route } from 'react-router-dom' 3 | import styled from 'styled-components' 4 | 5 | import NavBar from '../NavBar/NavBar' 6 | 7 | const Main = styled.main` 8 | background-color: #f9fafa; 9 | position: relative; 10 | ` 11 | 12 | const WithNavBar = props => ( 13 | <> 14 | 15 | 16 | 17 | 18 | 19 |
{props.children}
20 | 21 | ) 22 | 23 | export default WithNavBar 24 | -------------------------------------------------------------------------------- /src/components/HOC/WithPageSpinner.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useContext } from 'react' 2 | import styled, { ThemeContext } from 'styled-components' 3 | import anime from 'animejs' 4 | import Transition from 'react-transition-group/Transition' 5 | import Spinkit from 'react-spinkit' 6 | 7 | import { fadeIn, statusFadeOut } from '../../styles/GlobalAnime' 8 | 9 | const SpinnerWrapper = styled.div` 10 | display: flex; 11 | justify-content: center; 12 | align-items: center; 13 | 14 | position: fixed; 15 | left: 0; 16 | right: 0; 17 | top: 0; 18 | bottom: 0; 19 | z-index: 999; 20 | opacity: 0; 21 | 22 | background-color: ${props => props.theme.bgPage}33; 23 | pointer-events: none; 24 | 25 | div { 26 | width: 10em; 27 | height: 10em; 28 | } 29 | ` 30 | 31 | const WithPageSpinner = ({ children, show }) => { 32 | const themeContext = useContext(ThemeContext) 33 | 34 | useEffect(() => { 35 | fadeIn('.learn-i-spin') 36 | }, []) 37 | 38 | return ( 39 | <> 40 | 41 | {status => { 42 | statusFadeOut(status, '.learn-i-spin', 1000) 43 | return ( 44 | 45 | 51 | 52 | ) 53 | }} 54 | 55 | {children} 56 | 57 | ) 58 | } 59 | 60 | export default WithPageSpinner 61 | -------------------------------------------------------------------------------- /src/components/HOC/WithStyledTheme.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { connect } from 'react-redux' 3 | import { ThemeProvider } from 'styled-components' 4 | import { GlobalStyle, GlobalStyleReset } from '../../styles/GlobalStyles' 5 | 6 | const WithStyledTheme = props => { 7 | return ( 8 | 9 | 10 | 11 | {props.children} 12 | 13 | ) 14 | } 15 | 16 | const mapStateToProps = state => { 17 | return { theme: state.theme } 18 | } 19 | 20 | export default connect(mapStateToProps)(WithStyledTheme) 21 | -------------------------------------------------------------------------------- /src/components/HOC/WithUserData.js: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react' 2 | import { connect } from 'react-redux' 3 | import { initUserData } from '../../redux/actions/account' 4 | import { init as initStudentData } from '../../redux/actions/studentData' 5 | 6 | const WithUserData = ({ 7 | children, 8 | userId, 9 | studentId, 10 | onInitUserData, 11 | onInitStudentData 12 | }) => { 13 | useEffect(() => { 14 | if (userId) onInitUserData(userId) 15 | if (studentId) onInitStudentData(studentId) 16 | }, [userId, studentId]) 17 | 18 | return children 19 | } 20 | 21 | const mapStateToProps = state => { 22 | const { userId, studentId } = state.account.meta ?? {} 23 | return { 24 | userId, 25 | studentId 26 | } 27 | } 28 | 29 | const mapDispatchToProps = dispatch => { 30 | return { 31 | onInitUserData: userId => dispatch(initUserData(userId)), 32 | onInitStudentData: studentId => dispatch(initStudentData(studentId)) 33 | } 34 | } 35 | 36 | export default connect(mapStateToProps, mapDispatchToProps)(WithUserData) 37 | -------------------------------------------------------------------------------- /src/components/Home.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Redirect } from 'react-router-dom' 3 | import { connect } from 'react-redux' 4 | 5 | import Visitor from './Visitor/Visitor' 6 | 7 | const Home = ({ meta }) => { 8 | const selectHome = () => { 9 | if (!meta) { 10 | return 11 | } 12 | if (meta.studentId) { 13 | return 14 | } 15 | if (meta.teacherId) { 16 | return null 17 | } 18 | 19 | console.log("[HOME] we shouldn't be here... missing meta?", meta) 20 | return null 21 | } 22 | return <>{selectHome()} 23 | } 24 | 25 | const mapStateToProps = state => ({ 26 | meta: state.account.meta 27 | }) 28 | 29 | export default connect(mapStateToProps)(Home) 30 | -------------------------------------------------------------------------------- /src/components/Learn/Checkpoint/Autograder/CLI.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | 4 | import ImgAndContent from '../../../shared/low/ImgAndContent' 5 | 6 | const cliSvg = require('../../../../assets/icons/cli.svg') 7 | 8 | const Container = styled.div` 9 | height: 100%; 10 | display: flex; 11 | justify-content: center; 12 | align-items: center; 13 | font-size: 110%; 14 | ` 15 | 16 | const TitleArea = styled(ImgAndContent)` 17 | padding: 0; 18 | cursor: default; 19 | ` 20 | 21 | const CodeArea = styled.pre` 22 | padding: 1em 1.5em; 23 | background-color: #2b2b2b; 24 | color: #fff; 25 | white-space: nowrap; 26 | border-radius: 0.5em; 27 | ` 28 | 29 | const CLI = ({ 30 | description = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud.', 31 | command = 'bit_autograder input -i redit.py' 32 | }) => { 33 | return ( 34 | 35 |
36 | 42 | Upload Code Using Terminal 43 | 44 |

{description}

45 | 46 | {command} 47 | 48 |
49 |
50 | ) 51 | } 52 | 53 | export default CLI 54 | -------------------------------------------------------------------------------- /src/components/Learn/Checkpoint/Loading.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react' 2 | import styled from 'styled-components' 3 | import { connect } from 'react-redux' 4 | 5 | import Icon from '../../shared/low/Icon' 6 | import { AUTOGRADER } from './Checkpoint' 7 | 8 | import { resetSubmittedCheckpointSuccessful } from '../../../redux/actions/learnData' 9 | 10 | const autograderLoading = require('../../../assets/icons/autograder-loading.svg') 11 | 12 | const Container = styled.div` 13 | height: 100%; 14 | display: flex; 15 | flex-direction: column; 16 | align-items: center; 17 | justify-content: center; 18 | ` 19 | 20 | const StyledIcon = styled(Icon)` 21 | margin-right: -2em; 22 | ` 23 | 24 | const Title = styled.h1` 25 | margin: 0; 26 | margin-top: 1em; 27 | ${props => (props.error ? `color: ${props.theme.pastel.red}` : '')} 28 | ` 29 | 30 | const Caption = styled.p` 31 | margin: 0; 32 | ${props => (props.error ? `color: ${props.theme.pastel.red}` : '')} 33 | ` 34 | 35 | const Loading = ({ 36 | type, 37 | pushViewAndRemoveIntermediaries, 38 | previousViewAndRemoveIntermediaries, 39 | submittedCheckpointSuccessful, 40 | onResetSubmittedCheckpointSuccessful 41 | }) => { 42 | const [error, setError] = useState(false) 43 | 44 | useEffect(() => { 45 | if (submittedCheckpointSuccessful !== undefined) { 46 | if (submittedCheckpointSuccessful) { 47 | if (type === 'Autograder') { 48 | pushViewAndRemoveIntermediaries(AUTOGRADER) 49 | } else { 50 | previousViewAndRemoveIntermediaries() 51 | } 52 | onResetSubmittedCheckpointSuccessful() 53 | } else if (!submittedCheckpointSuccessful) { 54 | setError(true) 55 | } 56 | } 57 | }, [submittedCheckpointSuccessful]) 58 | 59 | return ( 60 | 61 | 62 | 63 | {!error ? 'Give us a sec' : 'An error occurred'} 64 | 65 | 66 | {!error ? 'Swapping space and time' : 'Please try again in a bit'} 67 | 68 | 69 | ) 70 | } 71 | 72 | const mapStateToProps = state => { 73 | const { 74 | learnData: { 75 | indicators: { submittedCheckpointSuccessful } 76 | } 77 | } = state 78 | 79 | return { submittedCheckpointSuccessful } 80 | } 81 | 82 | const mapDispatchToProps = dispatch => ({ 83 | onResetSubmittedCheckpointSuccessful: () => 84 | dispatch(resetSubmittedCheckpointSuccessful()) 85 | }) 86 | 87 | export default connect(mapStateToProps, mapDispatchToProps)(Loading) 88 | -------------------------------------------------------------------------------- /src/components/Learn/Checkpoint/Media/Home.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | 4 | import { Card } from '../Autograder/Home' 5 | import MediaLightbox, { 6 | TYPE_IMAGE, 7 | TYPE_VIDEO 8 | } from '../../../shared/low/MediaLightbox' 9 | import TwoPanel from '../../../shared/containers/TwoPanel' 10 | import Button from '../../../shared/low/Button' 11 | 12 | import { UPLOAD } from '../Checkpoint' 13 | 14 | const splitCardsSvg = require('../../../../assets/icons/split-cards.svg') 15 | 16 | const Instruction = styled.div` 17 | padding-top: 1em; 18 | padding-right: 1em; 19 | ` 20 | 21 | const StyledTwoPanel = styled(TwoPanel)` 22 | padding: 0 4em 2em; 23 | padding-right: 2em; 24 | ` 25 | 26 | const Resubmit = styled(Button)` 27 | position: absolute; 28 | right: 2em; 29 | bottom: 1em; 30 | ` 31 | 32 | const Home = ({ 33 | pushView, 34 | type: checkpointType, 35 | instruction = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud.', 36 | content 37 | }) => { 38 | return ( 39 | {instruction}} 42 | second={ 43 | content ? ( 44 | 48 | ) : ( 49 | pushView(UPLOAD)} 54 | /> 55 | ) 56 | } 57 | > 58 | {content && ( 59 | pushView(UPLOAD)}> 60 | Resubmit 61 | 62 | )} 63 | 64 | ) 65 | } 66 | 67 | export default Home 68 | -------------------------------------------------------------------------------- /src/components/Learn/Checkpoint/MultipleChoice/McCard.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | 4 | const Container = styled.div` 5 | display: flex; 6 | justify-content: center; 7 | align-items: center; 8 | border-radius: 1em; 9 | 10 | cursor: pointer; 11 | 12 | &.active { 13 | background-color: ${props => props.theme.accent}; 14 | color: ${props => props.theme.fontInvert}; 15 | } 16 | 17 | &.wrong { 18 | background-color: ${props => props.theme.pastel.red}; 19 | color: ${props => props.theme.fontInvert}; 20 | } 21 | 22 | &.correct { 23 | background-color: ${props => props.theme.pastel.green}; 24 | color: ${props => props.theme.fontInvert}; 25 | } 26 | 27 | :hover { 28 | background-color: ${props => props.theme.accent}; 29 | color: ${props => props.theme.fontInvert}; 30 | } 31 | ` 32 | 33 | const McCard = ({ className, children, ...props }) => { 34 | return ( 35 | 39 | {children} 40 | 41 | ) 42 | } 43 | 44 | export default McCard 45 | -------------------------------------------------------------------------------- /src/components/Learn/Checkpoint/ShortAnswer/Home.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import styled from 'styled-components' 3 | import { connect } from 'react-redux' 4 | 5 | import MarkdownArea from '../../../shared/high/MarkdownArea' 6 | import Button from '../../../shared/low/Button' 7 | 8 | import { initSubmitCheckpointProgress } from '../../../../redux/actions/learnData' 9 | 10 | import { LOADING } from '../Checkpoint' 11 | 12 | const Container = styled.div` 13 | height: calc(100% - 3em); 14 | padding: 0 4em; 15 | display: flex; 16 | flex-direction: column; 17 | ` 18 | 19 | const InstructionWrapper = styled.div` 20 | margin-bottom: 2em; 21 | min-height: 6em; 22 | display: flex; 23 | align-items: safe center; 24 | ` 25 | 26 | const StyledMarkdownArea = styled(MarkdownArea)` 27 | flex: 1; 28 | ` 29 | 30 | const Submit = styled(Button)` 31 | position: absolute; 32 | right: 2em; 33 | bottom: 1em; 34 | ` 35 | 36 | const Home = ({ 37 | activityId, 38 | id, 39 | 40 | type, 41 | instruction, 42 | content, 43 | pushView, 44 | 45 | onInitSubmitCheckpointProgress 46 | }) => { 47 | const [answer, setAnswer] = useState() 48 | 49 | const handleSubmit = () => { 50 | onInitSubmitCheckpointProgress(activityId, id, type, answer) 51 | pushView(LOADING) 52 | } 53 | 54 | return ( 55 | 56 | 57 |
{instruction}
58 |
59 | setAnswer(contents)} 63 | /> 64 | 65 | Submit 66 | 67 |
68 | ) 69 | } 70 | 71 | const mapDispatchToProps = dispatch => ({ 72 | onInitSubmitCheckpointProgress: (activityId, id, type, content) => 73 | dispatch(initSubmitCheckpointProgress(activityId, id, type, content)) 74 | }) 75 | 76 | export default connect(null, mapDispatchToProps)(Home) 77 | -------------------------------------------------------------------------------- /src/components/Learn/Concept/Concept.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import styled from 'styled-components' 3 | import { connect } from 'react-redux' 4 | 5 | import { Carousel } from 'react-responsive-carousel' 6 | import 'react-responsive-carousel/lib/styles/carousel.min.css' 7 | 8 | import Slide from './Slide' 9 | import Peripheral from '../NextButton/Peripheral' 10 | import DynamicModal from '../../shared/containers/DynamicModal' 11 | 12 | const StyledCarousel = styled(Carousel)` 13 | width: 100%; 14 | 15 | .carousel, 16 | .slider-wrapper, 17 | .slider, 18 | .slide, 19 | .slide > div { 20 | height: 100%; 21 | } 22 | 23 | .carousel .slide { 24 | text-align: left; 25 | background: transparent; 26 | } 27 | 28 | .carousel.carousel-slider .control-arrow { 29 | visibility: hidden; 30 | } 31 | ` 32 | 33 | const Concept = ({ 34 | STATE_CONCEPT, 35 | 36 | render, 37 | open, 38 | setOpen, 39 | 40 | conceptMetas, 41 | removeAndBroadcastButtonState 42 | }) => { 43 | /** 44 | * Persistent memory of current concept even if user closes concept modal 45 | * # TODO doesn't work rn because carousel onchange is messed 46 | */ 47 | const [slideIndex, setSlideIndex] = useState(0) 48 | 49 | const handleClose = () => { 50 | setOpen(false) 51 | removeAndBroadcastButtonState(STATE_CONCEPT) 52 | } 53 | 54 | const slides = conceptMetas?.map((concept, index) => ( 55 | 62 | )) 63 | 64 | return ( 65 | <> 66 | {render && ( 67 | setOpen(true)} 70 | top="65%" 71 | left="65%" 72 | /> 73 | )} 74 | 75 | setSlideIndex(slideIndex)} 81 | width="100%" 82 | > 83 | {slides} 84 | 85 | 86 | 87 | ) 88 | } 89 | 90 | const mapStateToProps = state => { 91 | const { 92 | cache: { cachedActivities, cachedCards }, 93 | learnData: { 94 | selectedActivity: { id: activityId }, 95 | indicators: { currentCardIndex } 96 | } 97 | } = state 98 | 99 | const cardId = cachedActivities[activityId]?.cards[currentCardIndex]?.id 100 | 101 | const conceptMetas = cachedCards[cardId]?.concepts 102 | 103 | return { 104 | conceptMetas 105 | } 106 | } 107 | 108 | export default connect(mapStateToProps)(Concept) 109 | -------------------------------------------------------------------------------- /src/components/Learn/Content/ContentHeader.js: -------------------------------------------------------------------------------- 1 | import React, { forwardRef } from 'react' 2 | import styled from 'styled-components' 3 | import { connect } from 'react-redux' 4 | import { compose } from 'redux' 5 | 6 | import HeaderShadow from '../../shared/low/HeaderShadow' 7 | import ImgAndContent from '../../shared/low/ImgAndContent' 8 | 9 | import withApiCache, { CACHE_MODULE } from '../../HOC/WithApiCache' 10 | 11 | const Container = styled.div` 12 | position: fixed; 13 | top: 0; 14 | width: 75%; 15 | z-index: 99; 16 | opacity: 0; 17 | ` 18 | 19 | const Header = styled(ImgAndContent)` 20 | margin: 0; 21 | padding: 2em 1em 1.5em; 22 | padding-left: 5em; 23 | cursor: auto; 24 | background-color: #fff; 25 | 26 | transition: 0.2s ease padding; 27 | 28 | &.content-minimized { 29 | padding-top: 1em; 30 | padding-bottom: 1em; 31 | } 32 | 33 | @media only screen and (orientation: vertical) { 34 | padding-left: 2.5em; 35 | } 36 | ` 37 | 38 | const mapStateToProps = state => ({ 39 | id: state.learnData.selectedActivity.moduleId 40 | }) 41 | const enhancer = compose(connect(mapStateToProps), withApiCache([CACHE_MODULE])) 42 | 43 | const ModuleName = enhancer(({ wac_data: [modu1e] }) => ( 44 | 45 | {modu1e?.name?.toUpperCase()} 46 | 47 | )) 48 | 49 | const ContentHeader = forwardRef(({ containerRef, name }, ref) => { 50 | return ( 51 | 52 |
61 | 62 |
63 | 64 |
65 | ) 66 | }) 67 | 68 | export default ContentHeader 69 | -------------------------------------------------------------------------------- /src/components/Learn/Learn.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react' 2 | import styled from 'styled-components' 3 | import { connect } from 'react-redux' 4 | import { compose } from 'redux' 5 | 6 | import Toolbar from './Toolbar/Toolbar' 7 | import Sidebar from './Sidebar/Sidebar' 8 | import Content from './Content/Content' 9 | import WithPageSpinner from '../HOC/WithPageSpinner' 10 | 11 | import withApiCache, { 12 | isDataReady, 13 | CACHE_ACTIVITY, 14 | CACHE_ACTIVITY_PROGRESS 15 | } from '../HOC/WithApiCache' 16 | import { init } from '../../redux/actions/learnData' 17 | 18 | const Container = styled.div` 19 | display: flex; 20 | position: relative; 21 | background: #fafafa; 22 | 23 | > :nth-child(1), 24 | > :nth-child(2), 25 | > :nth-child(3) { 26 | height: 100vh; 27 | } 28 | 29 | > :nth-child(1) { 30 | position: relative; 31 | z-index: 3; 32 | } 33 | > :nth-child(2) { 34 | position: relative; 35 | z-index: 2; 36 | } 37 | > :nth-child(3) { 38 | position: relative; 39 | z-index: 1; 40 | } 41 | 42 | @media only screen and (orientation: landscape) { 43 | font-size: 80%; 44 | } 45 | ` 46 | 47 | const Learn = ({ wac_data, id, isReady, onInit }) => { 48 | useEffect(() => { 49 | if (id && isDataReady(wac_data)) { 50 | onInit(...wac_data) 51 | } 52 | }, [id, wac_data]) // eslint-disable-line react-hooks/exhaustive-deps 53 | 54 | return ( 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | ) 63 | } 64 | 65 | const mapStateToProps = state => { 66 | const { 67 | cache: { cachedActivities, cachedCards }, 68 | learnData: { 69 | selectedActivity: { id } 70 | } 71 | } = state 72 | 73 | const cardId = cachedActivities[id]?.cards[0]?.id 74 | const card = cachedCards[cardId] 75 | 76 | const isReady = !!card?.content 77 | return { 78 | isReady, 79 | id 80 | } 81 | } 82 | 83 | const mapDispatchToProps = dispatch => ({ 84 | onInit: (activity, activityProgress) => 85 | dispatch(init(activity, activityProgress)) 86 | // onResetToInitialState: () => dispatch(resetToInitialState()) 87 | }) 88 | 89 | const enhancer = compose( 90 | connect(mapStateToProps, mapDispatchToProps), 91 | withApiCache([CACHE_ACTIVITY, CACHE_ACTIVITY_PROGRESS]) 92 | ) 93 | 94 | export default enhancer(Learn) 95 | -------------------------------------------------------------------------------- /src/components/Learn/NextButton/Peripheral.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | 4 | import { CentralTemplate } from './Central/Central' 5 | 6 | const Container = styled(CentralTemplate)` 7 | position: absolute; 8 | top: ${props => props.top}; 9 | left: ${props => props.left}; 10 | font-size: 50%; 11 | z-index: 1; 12 | ` 13 | 14 | const Peripheral = ({ top = 0, left = 0, currentButtonState, onClick }) => { 15 | return ( 16 | 22 | ) 23 | } 24 | 25 | export default Peripheral 26 | -------------------------------------------------------------------------------- /src/components/Learn/NextButton/SelectState/CentralAnimes.js: -------------------------------------------------------------------------------- 1 | import anime from 'animejs' 2 | import { 3 | STATE_NEXT, 4 | STATE_FINISH, 5 | STATE_CHECKPOINT, 6 | STATE_CONCEPT, 7 | STATE_HINT 8 | } from '../NextButton' 9 | 10 | export default function CentralAnimes(currentButtonState) { 11 | // reset 12 | const targets = '.learn-r-nextbutton, .learn-r-nextarrow' 13 | anime.remove(targets) 14 | document.querySelectorAll(targets).forEach(target => { 15 | target.classList.remove('transition-none') 16 | target.style.transform = '' // remove transform style 17 | }) 18 | 19 | switch (currentButtonState) { 20 | case STATE_HINT: { 21 | document 22 | .querySelectorAll('.learn-r-nextbutton, .learn-r-nextarrow') 23 | .forEach(target => target.classList.add('transition-none')) 24 | 25 | const options = { 26 | duration: 500, 27 | direction: 'alternate', 28 | loop: true 29 | } 30 | // bounce 31 | anime({ 32 | targets: '.learn-r-nextbutton', 33 | translateY: '-1em', 34 | translateZ: 0, 35 | easing: 'easeOutQuad', 36 | ...options 37 | }) 38 | // rotate 39 | anime({ 40 | targets: '.learn-r-nextarrow', 41 | rotate: '-90deg', 42 | easing: 'easeOutQuad', 43 | duration: 400 44 | }) 45 | // scale 46 | // anime({ 47 | // targets: '.learn-r-nextbutton, .learn-r-nextarrow', 48 | // scale: 1.5, 49 | // duration: 1000, 50 | // easing: 'easeOutQuad' 51 | // }) 52 | 53 | break 54 | } 55 | 56 | case STATE_CHECKPOINT: { 57 | break 58 | } 59 | 60 | case STATE_CONCEPT: { 61 | break 62 | } 63 | 64 | case STATE_FINISH: { 65 | break 66 | } 67 | 68 | case STATE_NEXT: { 69 | break 70 | } 71 | 72 | default: 73 | if (currentButtonState !== undefined) 74 | console.log( 75 | '[CentralAnimes] error... missing state check?', 76 | currentButtonState 77 | ) 78 | break 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/components/Learn/NextButton/SelectState/CentralContent.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import RightArrow from '@material-ui/icons/KeyboardArrowRightRounded' 4 | import Clipboard from '@material-ui/icons/AssignmentRounded' 5 | import Flag from '@material-ui/icons/EmojiFlagsRounded' 6 | import Finish from '@material-ui/icons/DoneRounded' 7 | 8 | import { 9 | STATE_NEXT, 10 | STATE_FINISH, 11 | STATE_CONCEPT, 12 | STATE_CHECKPOINT, 13 | STATE_HINT 14 | } from '../NextButton' 15 | 16 | const Container = styled.div` 17 | display: flex; 18 | justify-content: center; 19 | align-items: center; 20 | color: #fff; 21 | font-size: 200%; 22 | ` 23 | 24 | export default function CentralContent({ className, currentButtonState }) { 25 | const centralContent = () => { 26 | switch (currentButtonState) { 27 | case STATE_CHECKPOINT: 28 | return 29 | 30 | case STATE_CONCEPT: 31 | return 32 | 33 | case STATE_HINT: 34 | case STATE_NEXT: 35 | return 36 | 37 | case STATE_FINISH: 38 | return 39 | 40 | default: 41 | if (currentButtonState !== undefined) 42 | console.log('[CentralContent] error... missing state check?') 43 | return null 44 | } 45 | } 46 | 47 | return {centralContent()} 48 | } 49 | -------------------------------------------------------------------------------- /src/components/Learn/NextButton/SelectState/CentralStyles.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | import { 3 | STATE_NEXT, 4 | STATE_FINISH, 5 | STATE_CHECKPOINT, 6 | STATE_CONCEPT, 7 | STATE_HINT 8 | } from '../NextButton' 9 | 10 | export const createColorTemplate = color => ` 11 | background-color: ${color}; 12 | box-shadow: 0 4px 14px 0 ${color}88; 13 | ` 14 | 15 | const CentralStyles = styled.div` 16 | ${props => { 17 | switch (props.currentButtonState) { 18 | case STATE_HINT: 19 | return createColorTemplate(props.theme.pastel.magenta) 20 | 21 | case STATE_CONCEPT: 22 | return createColorTemplate(props.theme.pastel.yellow) 23 | 24 | case STATE_CHECKPOINT: 25 | return createColorTemplate(props.theme.pastel.green) 26 | 27 | case STATE_FINISH: 28 | case STATE_NEXT: 29 | return createColorTemplate(props.theme.accent) 30 | 31 | default: 32 | if (props.currentButtonState !== undefined) 33 | console.log('[CentralStyles] error... missing state check?') 34 | return ` 35 | background-color: #fff; 36 | box-shadow: 0 4px 14px 0 #00000088; 37 | .learn-r-nextarrow { 38 | color: #aaa; 39 | } 40 | ` 41 | } 42 | }} 43 | ` 44 | 45 | export default CentralStyles 46 | -------------------------------------------------------------------------------- /src/components/Learn/Sidebar/Sidebar.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react' 2 | import styled from 'styled-components' 3 | import { connect } from 'react-redux' 4 | 5 | import SidebarHeader from './SidebarHeader' 6 | import SidebarNav from './SidebarNav' 7 | 8 | import { slideIn, fadeIn } from '../../../styles/GlobalAnime' 9 | 10 | const Container = styled.div` 11 | flex: 0.36; 12 | background: #fafafa; 13 | display: flex; 14 | flex-direction: column; 15 | 16 | @media screen and (orientation: landscape) { 17 | flex: 0.21; 18 | } 19 | ` 20 | 21 | const Sidebar = ({ isReady }) => { 22 | useEffect(() => { 23 | if (isReady) { 24 | fadeIn('.learn-i-sidebar') 25 | slideIn('.learn-i-sidebar') 26 | } 27 | }, [isReady]) 28 | 29 | return ( 30 | 31 | {isReady && ( 32 | <> 33 | 34 | 35 | 36 | )} 37 | 38 | ) 39 | } 40 | 41 | const mapStateToProps = state => { 42 | const { 43 | cache: { cachedActivities, cachedCards }, 44 | learnData: { 45 | selectedActivity: { id: activityId } 46 | } 47 | } = state 48 | 49 | const cardId = cachedActivities[activityId]?.cards[0].id 50 | const isReady = cachedCards[cardId]?.id 51 | 52 | return { 53 | isReady 54 | } 55 | } 56 | 57 | export default connect(mapStateToProps)(Sidebar) 58 | -------------------------------------------------------------------------------- /src/components/Learn/Sidebar/SidebarHeader.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import { connect } from 'react-redux' 4 | import { compose } from 'redux' 5 | 6 | import DotRating from '../../shared/low/DotRating' 7 | import { setCurrentCardByIndex } from '../../../redux/actions/learnData' 8 | 9 | import withApiCache, { CACHE_MODULE } from '../../HOC/WithApiCache' 10 | 11 | const Container = styled.div` 12 | padding: 2em; 13 | ` 14 | 15 | const SidebarHeader = ({ 16 | wac_data: [modu1e], 17 | 18 | name, 19 | cardsLength, 20 | currentCardIndex, 21 | lastCardUnlockedIndex, 22 | onSetCurrentCardByIndex 23 | }) => { 24 | return ( 25 | 26 | 27 | {modu1e?.name?.toUpperCase()} 28 | 29 |

{name}

30 | { 38 | if (index <= lastCardUnlockedIndex && index !== currentCardIndex) 39 | onSetCurrentCardByIndex(index) 40 | }} 41 | /> 42 |
43 | ) 44 | } 45 | 46 | const mapStateToProps = state => { 47 | const { 48 | cache: { cachedActivities }, 49 | learnData: { 50 | selectedActivity: { id: activityId, moduleId }, 51 | indicators: { currentCardIndex, lastCardUnlockedIndex } 52 | } 53 | } = state 54 | 55 | const activity = cachedActivities[activityId] 56 | 57 | return { 58 | id: moduleId, 59 | name: activity?.name, 60 | cardsLength: activity?.cards.length, 61 | currentCardIndex, 62 | lastCardUnlockedIndex 63 | } 64 | } 65 | 66 | const mapDispatchToProps = dispatch => ({ 67 | onSetCurrentCardByIndex: cardIndex => 68 | dispatch(setCurrentCardByIndex(cardIndex)) 69 | }) 70 | 71 | const enhancer = compose( 72 | connect(mapStateToProps, mapDispatchToProps), 73 | withApiCache([CACHE_MODULE]) 74 | ) 75 | 76 | export default enhancer(SidebarHeader) 77 | -------------------------------------------------------------------------------- /src/components/Learn/Toolbar/Settings.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import styled from 'styled-components' 3 | import { connect } from 'react-redux' 4 | 5 | import SettingsIcon from '@material-ui/icons/Settings' 6 | 7 | import QuickAction from '../../shared/high/QuickAction' 8 | import Button from '../../shared/low/Button' 9 | 10 | import { deleteActivityProgress } from '../../../services/LearnService' 11 | 12 | const Container = styled.div` 13 | width: 100%; 14 | display: flex; 15 | flex-direction: column; 16 | align-items: center; 17 | justify-content: center; 18 | ` 19 | 20 | const IconWrapper = styled.div` 21 | font-size: 200%; 22 | color: white; 23 | height: 1em; 24 | line-height: 1em; 25 | ` 26 | 27 | const StyledButton = styled(Button)` 28 | text-align: right; 29 | ` 30 | 31 | const Settings = ({ activityId }) => { 32 | const action = () => 33 | deleteActivityProgress(activityId).then(res => { 34 | const success = 35 | !res.response?.status && 36 | (!res.message?.includes('Error') || !res.msg?.includes('Error')) 37 | if (success) { 38 | window.location.replace('/learn/') 39 | } 40 | }) 41 | 42 | return process.env.NODE_ENV === 'development' ? ( 43 | Reset your progress for this activity?

} 47 | > 48 | 49 | 50 | 51 |
52 | ) : null 53 | } 54 | 55 | const mapStateToProps = state => { 56 | const { 57 | learnData: { 58 | selectedActivity: { id: activityId } 59 | } 60 | } = state 61 | return { activityId } 62 | } 63 | 64 | export default connect(mapStateToProps)(Settings) 65 | -------------------------------------------------------------------------------- /src/components/Learn/Toolbar/Toolbar.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react' 2 | import styled from 'styled-components' 3 | import anime from 'animejs' 4 | import { connect } from 'react-redux' 5 | import { Link } from 'react-router-dom' 6 | 7 | import Settings from './Settings' 8 | import Icon from '../../shared/low/Icon' 9 | 10 | const Container = styled.div` 11 | display: flex; 12 | flex-direction: column; 13 | // justify-content: space-around; 14 | // align-items: center; 15 | flex: 0.08; 16 | 17 | background-color: ${props => props.theme.bg}; 18 | text-align: center; 19 | 20 | @media screen and (orientation: landscape) { 21 | flex: 0.04; 22 | } 23 | ` 24 | 25 | const TopSection = styled.div`` 26 | const BottomSection = styled.div`` 27 | 28 | const MiddleSection = styled.div` 29 | flex: 1; 30 | display: flex; 31 | flex-direction: column; 32 | justify-content: center; 33 | ` 34 | 35 | const Elem = styled.div` 36 | margin: 1em 0; 37 | padding: 10%; 38 | display: flex; 39 | justify-content: center; 40 | align-items: center; 41 | 42 | &.pointer { 43 | cursor: pointer; 44 | } 45 | 46 | @media screen and (orientation: landscape) { 47 | padding: 12%; 48 | } 49 | ` 50 | 51 | const Toolbar = ({ gems }) => { 52 | useEffect(() => { 53 | anime({ 54 | targets: '.learn-r-gems', 55 | innerText: gems, 56 | easing: 'easeOutQuad', 57 | round: 1 58 | }) 59 | }, [gems]) 60 | 61 | return ( 62 | 63 | 64 | 65 | 66 | 70 | 71 | 72 | 73 | 74 | 75 |
💎
76 |
80 | 81 | 82 | 83 |
84 |
85 | ) 86 | } 87 | 88 | const mapStateToProps = state => { 89 | const { 90 | studentData: { gems } 91 | } = state 92 | return { gems } 93 | } 94 | 95 | export default connect(mapStateToProps)(Toolbar) 96 | -------------------------------------------------------------------------------- /src/components/Learn/unused/Checkpoint/CheckpointModal.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { makeStyles } from '@material-ui/core/styles'; 3 | import Modal from '@material-ui/core/Modal'; 4 | import Backdrop from '@material-ui/core/Backdrop'; 5 | import Fade from '@material-ui/core/Fade'; 6 | 7 | import Upload from './Upload'; 8 | import Result from './Result'; 9 | 10 | const useStyles = makeStyles(theme => ({ 11 | modal: { 12 | display: 'flex', 13 | alignItems: 'center', 14 | justifyContent: 'center', 15 | }, 16 | })); 17 | 18 | const CheckpointModal = (props) => { 19 | const classes = useStyles(); 20 | const [open, setOpen] = React.useState(false); 21 | const [result, setResult] = React.useState({}); 22 | const [currentSlide, setSlide] = React.useState('upload'); 23 | 24 | const openModal = () => { 25 | setOpen(true); 26 | }; 27 | 28 | const closeModal = () => { 29 | setOpen(false); 30 | }; 31 | 32 | const switchToResult = () => { 33 | setSlide('checkpoint'); 34 | } 35 | 36 | const switchToUpload = () => { 37 | setSlide('upload'); 38 | } 39 | 40 | const fillResult = (result) => { 41 | setResult(result); 42 | } 43 | 44 | return ( 45 |
46 | 47 | 48 | 57 | 58 | 59 | {currentSlide === 'upload' ? 60 | 61 | : 62 | } 63 | 64 | 65 |
66 | ); 67 | } 68 | 69 | export default CheckpointModal; 70 | -------------------------------------------------------------------------------- /src/components/Learn/unused/Concept/ConceptModal.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Modal from "@material-ui/core/Modal"; 3 | import Backdrop from "@material-ui/core/Backdrop"; 4 | import Fade from "@material-ui/core/Fade"; 5 | 6 | import Concept from "./Concept"; 7 | 8 | class ConceptModal extends React.Component { 9 | state = { 10 | open: true 11 | }; 12 | 13 | setOpen = value => { 14 | this.setState({ 15 | open: value 16 | }); 17 | }; 18 | 19 | componentDidUpdate(prevProps) { 20 | if (this.props.conceptID !== prevProps.conceptID) { 21 | this.setOpen(true); 22 | } 23 | } 24 | 25 | render() { 26 | return ( 27 |
28 | 31 | 32 | this.setOpen(false)} 36 | BackdropComponent={Backdrop} 37 | BackdropProps={{ timeout: 200 }} 38 | > 39 | 40 | 44 | 45 | 46 | 47 | 54 |
55 | ); 56 | } 57 | } 58 | 59 | export default ConceptModal; 60 | -------------------------------------------------------------------------------- /src/components/Learn/unused/Navigation/NavDropdown.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | 3 | // const arrow_style = { 4 | // verticalAlign: 'middle', 5 | // marginRight: 7, 6 | // display: 'inline-block', 7 | // lineHeight: 0 8 | // }; 9 | 10 | const NavDropdown = (props) => { 11 | const [dropdown, setDropdown] = useState(false); 12 | 13 | return ( 14 |
15 |
setDropdown(!dropdown)}> 18 | 19 | {props.gemBox} 20 | {props.labTitle} 21 | {props.cardTitle} 22 | 23 | {/* */} 24 | {/* */} 25 | {/* */} 26 |
27 | 28 |
29 | {props.steps} 30 |
31 | 32 | 72 |
73 | ); 74 | } 75 | 76 | export default NavDropdown; 77 | -------------------------------------------------------------------------------- /src/components/Student/Activity/Activity.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import HelpCard from '../../../components/shared/high/HelpCard'; 3 | import ActionCard from '../../../components/shared/high/ActionCard'; 4 | 5 | const Activity = (props) => { 6 | 7 | return ( 8 | <> 9 | 10 | 11 | 12 | 13 | 14 | ) 15 | } 16 | 17 | export default Activity; 18 | -------------------------------------------------------------------------------- /src/components/Student/Dashboard/Dashboard.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import ProgressHero from './DashboardHero' 4 | import Journey from './Journey/Journey' 5 | 6 | const Progress = ({}) => { 7 | return ( 8 | <> 9 | 10 | 11 | 12 | ) 13 | } 14 | 15 | export default Progress 16 | -------------------------------------------------------------------------------- /src/components/Student/Dashboard/Journey/Journey.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import { connect } from 'react-redux' 4 | 5 | import Floating from './Floating' 6 | import Progress, { TYPE_JOURNEY } from './Progress' 7 | 8 | import Scrollable from '../../../shared/containers/Scrollable' 9 | import TwoPanel from '../../../shared/containers/TwoPanel' 10 | 11 | const Container = styled.div`` 12 | 13 | const FloatingWrapper = styled.div` 14 | position: sticky; 15 | top: 0; 16 | height: 100vh; 17 | display: flex; 18 | align-items: center; 19 | ` 20 | 21 | const ModulesContainer = styled.div` 22 | padding: 2.5em 2em; 23 | 24 | section:not(:last-of-type) { 25 | margin-bottom: 4em; 26 | } 27 | ` 28 | 29 | const Journey = ({ hasProgress, inprogressModules }) => { 30 | console.log(inprogressModules) 31 | return ( 32 | hasProgress && ( 33 | 39 | 42 | 43 | } 44 | second={ 45 | 46 | {inprogressModules 47 | ?.slice() 48 | .reverse() 49 | .map(im => { 50 | return ( 51 | 57 | ) 58 | })} 59 | 60 | } 61 | /> 62 | ) 63 | ) 64 | } 65 | 66 | const mapStateToProps = state => { 67 | const { 68 | studentData: { inprogressModules, completedModules, incompleteModules } 69 | } = state 70 | 71 | const hasProgress = 72 | !!inprogressModules?.length || 73 | !!completedModules?.length || 74 | !!incompleteModules?.length 75 | 76 | return { hasProgress, inprogressModules } 77 | } 78 | 79 | export default connect(mapStateToProps)(Journey) 80 | -------------------------------------------------------------------------------- /src/components/Student/Dashboard/Journey/unused/ActivityCard.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Link } from 'react-router-dom' 3 | import styled from 'styled-components' 4 | 5 | import StatusIcon from '../../../../shared/low/StatusIcon' 6 | 7 | const ActivityCardWrapper = styled.div` 8 | margin: 4.5em 2.25em; 9 | position: relative; 10 | ` 11 | 12 | const RenderActivityCard = styled.div` 13 | padding: 2.25em 2.25em 0; 14 | height: 18em; 15 | border-radius: 1em; 16 | background-color: #fff; 17 | text-align: center; 18 | display: inline-block; 19 | overflow-y: auto; 20 | cursor: pointer; 21 | ` 22 | const DottedLine = styled.div` 23 | border-bottom: 3px #eaeaea dashed; 24 | position: absolute; 25 | width: 100%; 26 | left: ${props => (props.isLeft ? '50%' : '-50%')}; 27 | bottom: 4.2em; 28 | z-index: -1; 29 | ` 30 | 31 | const styledLink = { color: 'black', textDecoration: 'none' } 32 | 33 | const ActivityCard = ({ isLast, isLeft, id, name }) => { 34 | return ( 35 | 36 | 37 | 38 |
39 | 40 |

{name}

41 |

42 | {/* Choose a module to learn an interesting tidbit about Python */} 43 |

44 |
45 |
46 | 47 | {!isLast && } 48 |
49 | ) 50 | } 51 | 52 | export default ActivityCard 53 | -------------------------------------------------------------------------------- /src/components/Student/Dashboard/Journey/unused/Details.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | 4 | const Container = styled.div` 5 | flex: 3; 6 | ` 7 | 8 | const Details = ({}) => { 9 | return ( 10 | 11 | lol 12 | 13 | ) 14 | } 15 | 16 | export default Details 17 | -------------------------------------------------------------------------------- /src/components/Student/Dashboard/Journey/unused/Journey.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import { connect } from 'react-redux' 4 | 5 | import ActivityCard from './ActivityCard' 6 | 7 | const Container = styled.div` 8 | padding-bottom: 6em; 9 | flex: 6; 10 | display: flex; 11 | flex-flow: row wrap; 12 | align-items: start; 13 | font-size: 90%; 14 | position: relative; 15 | ` 16 | 17 | const ColOne = styled.div` 18 | flex: 1; 19 | ` 20 | 21 | const ColTwo = styled.div` 22 | flex: 1; 23 | position: relative; 24 | top: 5em; 25 | ` 26 | 27 | const Journey = ({ inprogressModules }) => { 28 | const colOne = inprogressModules 29 | .filter((_, index) => index % 2 === 0) 30 | .map((mod, index) => { 31 | return ( 32 | 39 | ) 40 | }) 41 | 42 | const colTwo = inprogressModules 43 | ?.filter((_, index) => index % 2 === 1) 44 | .map((mod, index) => { 45 | return ( 46 | 53 | ) 54 | }) 55 | 56 | return ( 57 | 58 | 59 | {/* */} 60 | {colOne} 61 | 62 | {colTwo} 63 | 64 | ) 65 | } 66 | 67 | const mapStateToProps = state => { 68 | const { 69 | studentData: { inprogressModules } 70 | } = state 71 | 72 | return { inprogressModules } 73 | } 74 | 75 | export default connect(mapStateToProps)(Journey) 76 | -------------------------------------------------------------------------------- /src/components/Student/Dashboard/PickCard.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | 4 | import AddIcon from '@material-ui/icons/Add' 5 | import MuiIconFormatter from '../../shared/high/MuiIconFormatter' 6 | 7 | const Container = styled.div` 8 | margin-top: 0; 9 | padding: 2em 3em; 10 | height: 15em; 11 | 12 | display: flex; 13 | align-items: center; 14 | 15 | border-radius: 1em; 16 | background-color: #fff; 17 | text-align: center; 18 | cursor: pointer; 19 | ` 20 | 21 | const ButtonContainer = styled(MuiIconFormatter)` 22 | margin-top: 1.5em; 23 | ` 24 | 25 | const PickCard = ({}) => { 26 | return ( 27 | 28 |
29 |

Pick a Module

30 |

31 | Choose a module and learn an interesting tidbit about python 32 |

33 | 34 | 35 | 36 |
37 |
38 | ) 39 | } 40 | 41 | export default PickCard 42 | -------------------------------------------------------------------------------- /src/components/Student/Dashboard/Suggested.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | 4 | import Button from '../../shared/low/Button' 5 | 6 | import withApiCache, { CACHE_ACTIVITY } from '../../HOC/WithApiCache' 7 | 8 | const Container = styled.div` 9 | width: 100%; 10 | height: 100%; 11 | 12 | display: flex; 13 | flex-direction: column; 14 | justify-content: center; 15 | align-items: center; 16 | position: relative; 17 | 18 | color: #fff; 19 | ` 20 | 21 | const Background = styled.div` 22 | width: 100%; 23 | height: 100%; 24 | position: absolute; 25 | 26 | background: ${props => 27 | `${props.theme.bgVariant} url("${props.image ?? 28 | 'https://i.imgur.com/J1FzZ1l.png'}")`}; 29 | background-position: center; 30 | background-size: auto; 31 | 32 | box-shadow: inset 0 0 69420px 69420px rgba(0, 0, 0, 0.69); 33 | 34 | &:hover { 35 | box-shadow: inset 0 0 69420px 69420px rgba(0, 0, 0, 0.69); 36 | } 37 | ` 38 | 39 | const Name = styled.h3` 40 | margin: 0; 41 | margin-bottom: 0.5em; 42 | ` 43 | 44 | const Description = styled.p` 45 | font-size: 65%; 46 | ` 47 | 48 | const StyledButton = styled(Button)` 49 | margin: 1em 0 0.5em; 50 | padding: 0.4em 3em; 51 | font-size: 69%; 52 | ` 53 | 54 | const Suggested = ({ 55 | id, 56 | wac_data: [activity], 57 | 58 | loading, 59 | onClickButton 60 | }) => { 61 | const { name, summary, image } = activity ?? {} 62 | const isReady = !!name 63 | 64 | return ( 65 | 66 | 67 | {!loading ? ( 68 | id ? ( 69 | isReady ? ( 70 |
71 | {name} 72 | {summary} 73 | 74 | Resume 75 | 76 |
77 | ) : ( 78 | . . . 79 | ) 80 | ) : ( 81 | An activity has not been started yet 82 | ) 83 | ) : null} 84 |
85 | ) 86 | } 87 | 88 | export default withApiCache([CACHE_ACTIVITY])(Suggested) 89 | -------------------------------------------------------------------------------- /src/components/Student/Dashboard/unused/ProgressCard.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | 4 | import BrickWall from '../../../../assets/icons/unused/brickwall' 5 | import GitHub from '../../../../assets/icons/github' 6 | import Button from '../../../shared/low/Button' 7 | 8 | import withApiCache, { CACHE_ACTIVITY } from '../../../HOC/WithApiCache' 9 | 10 | const Container = styled.div` 11 | padding: 2em; 12 | width: 15em; 13 | height: 18em; 14 | display: flex; 15 | flex-direction: column; 16 | 17 | background-color: ${props => props.theme.bgVariant}; 18 | color: #fff; 19 | text-align: center; 20 | 21 | &:hover { 22 | box-shadow: inset 0 0 100px 100px rgba(255, 255, 255, 0.05); 23 | } 24 | ` 25 | 26 | const InfoArea = styled.div` 27 | flex-shrink: 1; 28 | ` 29 | 30 | const IconWrapper = styled.div` 31 | margin-bottom: 0.75em; 32 | ` 33 | 34 | const Name = styled.h3` 35 | margin: 0; 36 | margin-bottom: 0.2em; 37 | ` 38 | 39 | const Description = styled.p` 40 | margin: 0.5em 0; 41 | margin-top: 0; 42 | font-size: 65%; 43 | padding: 0 10%; 44 | ` 45 | 46 | const ButtonContainer = styled.div` 47 | flex-grow: 1; 48 | display: flex; 49 | justify-content: center; 50 | align-items: flex-end; 51 | ` 52 | 53 | const StyledButton = styled(Button)` 54 | margin: 1em 0 0.5em; 55 | padding: 0.6em 2em; 56 | font-size: 75%; 57 | ` 58 | 59 | const ActivityCard = ({ 60 | wac_data: [activity], 61 | 62 | image, 63 | onClickButton 64 | }) => { 65 | const { name, summary } = activity ?? {} 66 | 67 | const renderAppropriateImage = (imageName, width, height) => { 68 | switch (imageName) { 69 | case 'brickwall': 70 | return 71 | 72 | case 'github': 73 | return 74 | 75 | default: 76 | return null 77 | } 78 | } 79 | 80 | return ( 81 | 82 | 83 | {renderAppropriateImage(image, '3em')} 84 | {name} 85 | {summary} 86 | 87 | 88 | 89 | 95 | Resume 96 | 97 | 98 | 99 | ) 100 | } 101 | 102 | export default withApiCache([CACHE_ACTIVITY])(ActivityCard) 103 | -------------------------------------------------------------------------------- /src/components/Student/Profile/Profile.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Profile = (props) => { 4 | return ( 5 |
6 |

Profile Page

7 |
8 | ) 9 | } 10 | 11 | export default Profile; -------------------------------------------------------------------------------- /src/components/Student/Student.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Route, Switch, useRouteMatch } from 'react-router-dom' 3 | 4 | import Dashboard from './Dashboard/Dashboard' 5 | 6 | const Student = ({}) => { 7 | const { path } = useRouteMatch() 8 | 9 | return ( 10 | 11 | 12 | 13 | ) 14 | } 15 | 16 | export default Student 17 | -------------------------------------------------------------------------------- /src/components/Student/World/World.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | 4 | const Container = styled.div`` 5 | 6 | const World = ({ className, ...props }) => { 7 | return ( 8 | 9 | {/* ... */} 10 | 11 | ) 12 | } 13 | 14 | export default World 15 | -------------------------------------------------------------------------------- /src/components/Student/unused/CurrentTrack.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled from "styled-components"; 3 | import { connect } from "react-redux"; 4 | 5 | const RenderSubject = styled.div``; 6 | 7 | const Title = styled.h1` 8 | margin-top: 0; 9 | margin-bottom: 0.3em; 10 | `; 11 | 12 | const Subtitle = styled.h3` 13 | margin: 0; 14 | `; 15 | 16 | const Description = styled.p` 17 | margin: 0.5em 0 2em; 18 | width: 75%; 19 | `; 20 | 21 | const ProgressBar = styled.div` 22 | width: 93%; 23 | height: 1em; 24 | background-color: #eee; 25 | position: relative; 26 | 27 | &:before { 28 | content: ''; 29 | position: absolute; 30 | top: 0; 31 | bottom: 0; 32 | left: 0; 33 | width: ${props => props.progress}; 34 | background-color: #4788ff; 35 | } 36 | 37 | &:after { 38 | content: '${props => props.progress}'; 39 | line-height: 1em; 40 | position: absolute; 41 | top: 0; 42 | bottom: 0; 43 | right: -3em; 44 | } 45 | 46 | @media only screen and (max-width: 555px) { 47 | width: 90%; 48 | } 49 | `; 50 | 51 | const Subject = props => { 52 | return ( 53 | 54 | {props.name} 55 | with emphasis in React.js 56 | {props.description} 57 | 58 | {/* */} 59 | 60 | ); 61 | }; 62 | 63 | const mapStateToProps = state => { 64 | const studentData = state.studentData; // reference for easy typing 65 | 66 | if (!studentData.current_track) { 67 | return { 68 | name: "", 69 | description: "" 70 | }; 71 | } 72 | return { 73 | name: studentData.current_track.name, 74 | subtitle: "I am a static piece text", 75 | description: 'I am a static piece text. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.' 76 | // description: studentData.currentTrack.description 77 | }; 78 | }; 79 | 80 | export default connect(mapStateToProps)(Subject); 81 | -------------------------------------------------------------------------------- /src/components/Student/unused/GreetingSection.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from 'styled-components'; 3 | 4 | const Greeting = styled.div` 5 | margin: 15vh 20%; 6 | ` 7 | 8 | const GreetingSection = (props) => { 9 | const renderedTop = (props.top) ?

{props.top}

: null; 10 | const renderedBottom = (props.bottom) ?
{props.bottom}
: null; 11 | 12 | return ( 13 | 14 | {renderedTop} 15 |

{props.title}

16 |

{props.subtitle}

17 | {renderedBottom} 18 |
19 | ) 20 | 21 | } 22 | 23 | export default GreetingSection; -------------------------------------------------------------------------------- /src/components/Student/unused/LabVerticalCard.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled from "styled-components"; 3 | 4 | import Button from "../../shared/Button"; 5 | 6 | const NavyCard = styled.div` 7 | padding: 1rem; 8 | max-width: 350px; 9 | min-height: 375px; 10 | border-radius: 7px; 11 | box-shadow: 0 2px 10px rgba(0, 0, 0, 0.12); 12 | background-color: #0b1354; 13 | text-align: center; 14 | `; 15 | 16 | const LabVerticalCard = props => { 17 | return ( 18 | 19 | labLogo 26 |

{props.labTitle}

27 |

{props.labDescription}

28 | 72 | 73 | 74 | ) 75 | } 76 | 77 | export default HelpCard; 78 | -------------------------------------------------------------------------------- /src/components/shared/high/IconWithProgress.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | 4 | import ProgressCircle from '../low/ProgressCircle' 5 | 6 | const defaultIcon = require('../../../assets/icons/cards.svg') 7 | 8 | const Container = styled.div` 9 | width: ${props => props.size}; 10 | height: ${props => props.size}; 11 | position: relative; 12 | border-radius: 50%; 13 | flex-shrink: 0; 14 | ` 15 | 16 | const FullAbsolute = styled.div` 17 | position: absolute; 18 | top: 0; 19 | left: 0; 20 | right: 0; 21 | bottom: 0; 22 | display: flex; 23 | justify-content: center; 24 | align-items: center; 25 | ` 26 | 27 | const Icon = styled.img` 28 | width: 100%; 29 | height: 100%; 30 | padding: calc(${props => props.size} / 5); 31 | ` 32 | 33 | const IconWithProgress = ({ 34 | iconUrl = defaultIcon, 35 | size, 36 | thickness = 3, 37 | midValue, 38 | value, 39 | color, 40 | 41 | ...props 42 | }) => { 43 | return ( 44 | 45 | 46 | 52 | 53 | 54 | 55 | ) 56 | } 57 | 58 | export default IconWithProgress 59 | -------------------------------------------------------------------------------- /src/components/shared/high/MarkdownArea.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react' 2 | import styled from 'styled-components' 3 | import TurndownService from 'turndown' 4 | 5 | import ReactQuill from 'react-quill' 6 | import 'react-quill/dist/quill.bubble.css' 7 | 8 | const turndownService = new TurndownService({ 9 | headingStyle: 'atx', 10 | fence: '```' 11 | }) 12 | 13 | const MarkdownArea = styled(ReactQuill)` 14 | border-radius: 0.5em; 15 | width: 100%; 16 | 17 | .ql-editor { 18 | padding: 1.5em; 19 | // min-height: 9em; 20 | // max-height: 18em; 21 | } 22 | ` 23 | 24 | const MarkdownAreaInput = ({ 25 | className, 26 | placeholder = 'Comments...', 27 | initialValue, 28 | onChange 29 | }) => { 30 | const convertHtmlToMarkdown = html => { 31 | return turndownService.turndown(html) 32 | } 33 | 34 | useEffect(() => { 35 | const className = 'ql-editor' 36 | Array.from(document.getElementsByClassName(className)).forEach(mai => { 37 | if (!mai.classList.contains('low-profile-scrollbar')) { 38 | mai.classList.add('low-profile-scrollbar', 'only-hover') 39 | } 40 | }) 41 | }, []) 42 | 43 | return ( 44 | { 51 | const markdown = convertHtmlToMarkdown(contents) 52 | onChange(markdown) 53 | }} 54 | /> 55 | ) 56 | } 57 | 58 | export default MarkdownAreaInput 59 | -------------------------------------------------------------------------------- /src/components/shared/high/MuiIconFormatter.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | 3 | const MuiIconFormatter = styled.div.attrs(props => ({ 4 | width: props.size ?? props.width 5 | }))` 6 | margin: 0 auto; 7 | padding: calc(${props => props.width} / 7.5); 8 | width: ${props => props.width}; 9 | height: ${props => props.width}; 10 | ${props => 11 | props.circle 12 | ? 'border-radius: 50%;' 13 | : `border-radius: calc(${props.width} / 7.5);`} 14 | 15 | background-color: ${props => props.theme.accent}; 16 | color: ${props => props.theme.fontInvert}; 17 | 18 | > svg { 19 | width: 100%; 20 | height: 100%; 21 | } 22 | ` 23 | 24 | export default MuiIconFormatter 25 | -------------------------------------------------------------------------------- /src/components/shared/high/QuickAction.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import styled from 'styled-components' 3 | 4 | import ActionsIcon from '@material-ui/icons/RedoRounded' 5 | 6 | import DynamicModal from '../containers/DynamicModal' 7 | import Button from '../low/Button' 8 | 9 | const Container = styled.div` 10 | width: 100%; 11 | display: flex; 12 | flex-direction: column; 13 | align-items: center; 14 | justify-content: center; 15 | ` 16 | 17 | const IconWrapper = styled.div` 18 | font-size: 200%; 19 | color: white; 20 | height: 1em; 21 | line-height: 1em; 22 | ` 23 | 24 | const StyledButton = styled(Button)` 25 | font-size: 90%; 26 | padding: 0.6em 2em; 27 | ` 28 | 29 | const QuickAction = ({ 30 | action, 31 | title = 'Action', 32 | field =

Would you like to perform this action?

, 33 | buttonText = 'Confirm', 34 | children 35 | }) => { 36 | const [open, setOpen] = useState(false) 37 | const [waiting, setWaiting] = useState(false) // waiting for response 38 | 39 | const triggerAction = async () => { 40 | try { 41 | setWaiting(true) 42 | await action() 43 | } catch (e) { 44 | console.log(e) 45 | } finally { 46 | setWaiting(false) 47 | } 48 | } 49 | 50 | return ( 51 | <> 52 |
setOpen(true)}> 53 | {children || ( 54 | 55 | 56 | 57 | )} 58 |
59 | setOpen(false)} 62 | scaleX={0.6} 63 | scaleY={0.6} 64 | > 65 | 66 |
67 |

{title}

68 | {field} 69 | 70 | {buttonText} 71 | 72 |
73 |
74 |
75 | 76 | ) 77 | } 78 | 79 | export default QuickAction 80 | -------------------------------------------------------------------------------- /src/components/shared/low/Button.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | 4 | const RenderedButton = styled.button.attrs(props => { 5 | if (props.disabled) { 6 | if (props.invert) { 7 | props.dark = '#666666' 8 | props.light = '#aaaaaa' 9 | } else { 10 | props.dark = '#aaaaaa' 11 | } 12 | } 13 | return { 14 | dark: props.dark || props.theme.accent 15 | } 16 | })` 17 | display: inline-block; 18 | position: relative; 19 | margin: ${props => (props.fullWidth ? '' : '0.5em')}; 20 | padding: 0.75em 1.5em; 21 | 22 | ${props => (props.fullWidth ? 'width: 100%;' : '')} 23 | ${props => 24 | props.rounder ? 'border-radius: 0.5em;' : 'border-radius: 0.25em;'} 25 | 26 | ${props => 27 | !props.noOutline 28 | ? `border: ${props.dark} solid 0.1em;` 29 | : 'border: transparent;'} 30 | 31 | ${props => { 32 | if (props.invert) { 33 | return ` 34 | background-color: ${props.dark}}; 35 | color: ${props.light || '#fff'}; 36 | ${props.disabled ? '' : `box-shadow: 0 4px 14px 0 ${props.dark}77;`}` 37 | } else { 38 | return ` 39 | background-color: ${props.light || 'transparent'}; 40 | color: ${props.dark};` 41 | } 42 | }} 43 | 44 | text-align: center; 45 | outline: none; 46 | white-space: nowrap; 47 | transition: 0.2s ease all; 48 | font-size: inherit; 49 | user-select: none; 50 | 51 | ${props => (props.disabled ? 'pointer-events: none;' : '')} 52 | 53 | &:hover { 54 | ${props => { 55 | if (props.invert) return 'filter: brightness(110%);' 56 | return `box-shadow: inset 0 0 100em 100em ${ 57 | props.dark ? props.dark + '16' : props.theme.accent + '16' 58 | }` 59 | }} 60 | } 61 | 62 | &:active { 63 | ${props => { 64 | if (props.invert) return 'filter: brightness(120%);' 65 | return `box-shadow: inset 0 0 100em 100em ${ 66 | props.dark ? props.dark + '32' : props.theme.accent + '32' 67 | }` 68 | }} 69 | } 70 | ` 71 | 72 | /** 73 | * THICC BUTTON 74 | * 75 | * limitations: specify all colors with hexcode 6 digits 76 | * @param {*} props 77 | */ 78 | const Button = props => { 79 | return ( 80 | 92 | {props.children} 93 | 94 | ) 95 | } 96 | 97 | export default Button 98 | -------------------------------------------------------------------------------- /src/components/shared/low/ConfirmCancel.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | 4 | import Button from './Button' 5 | 6 | const Container = styled.div` 7 | margin: 0 auto; 8 | ` 9 | 10 | const CancelButton = styled(Button)` 11 | border: 0; 12 | padding: 0.5em 1em 0.5em 0.5em; 13 | ` 14 | 15 | const ConfirmButton = styled(Button)` 16 | padding: 0.5em 2em; 17 | ` 18 | 19 | const ConfirmCancel = ({ 20 | className, 21 | cancelText = 'Cancel', 22 | confirmText = 'Confirm', 23 | cancelProps, 24 | confirmProps, 25 | cancelOnClick, 26 | confirmOnClick 27 | }) => { 28 | return ( 29 | 30 | 31 | {cancelText} 32 | 33 | 34 | {confirmText} 35 | 36 | 37 | ) 38 | } 39 | 40 | export default ConfirmCancel 41 | -------------------------------------------------------------------------------- /src/components/shared/low/DotIndicator.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitprj/bit-frontend/d7450ca1e6c1c2c9b6d77cf806c8e92c81ff8956/src/components/shared/low/DotIndicator.js -------------------------------------------------------------------------------- /src/components/shared/low/DotRating.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import { isFunction } from 'lodash' 4 | 5 | const Container = styled.div` 6 | line-height: 1em; 7 | height: 1em; 8 | 9 | ${props => 10 | props.fullWidth 11 | ? `display: flex; 12 | align-items: center;` 13 | : ''} 14 | ` 15 | 16 | const Dot = styled.div` 17 | display: inline-block; 18 | 19 | &:not(:last-child) { 20 | margin-right: ${props => props.gap}; 21 | } 22 | 23 | width: ${props => props.dotSize}; 24 | height: ${props => props.dotSize}; 25 | vertical-align: middle; 26 | ${props => (props.type !== 'SQUARE' ? 'border-radius: 0.3em' : '')} 27 | ${props => (props.fullWidth ? 'flex: 1;' : '')} 28 | ${props => 29 | props.callback 30 | ? props.filled || props.offFilled 31 | ? 'cursor: pointer;' 32 | : 'cursor: default;' 33 | : ''} 34 | 35 | background-color: ${props => { 36 | if (props.filled) return props.filledColor || props.theme.accent 37 | if (props.offFilled) 38 | return props.offFilledColor || props.theme.accentVariant 39 | return props.offColor || props.theme.offFont 40 | }} 41 | ` 42 | 43 | /** 44 | * 45 | * @param {filled} props 46 | */ 47 | const Rating = ({ 48 | style, 49 | className, 50 | type, 51 | fullWidth, 52 | rating = 3, 53 | offRating, 54 | outOf = 5, 55 | upTo = true, 56 | filledColor, 57 | offFilledColor, 58 | offColor, 59 | dotSize = '0.3em', 60 | gap = '0.3em', 61 | callback 62 | }) => { 63 | const renderedDots = [...Array(outOf)].map((_, index) => ( 64 | isFunction(callback) && callback(index)} 77 | /> 78 | )) 79 | 80 | return ( 81 | 82 | {renderedDots} 83 | 84 | ) 85 | } 86 | 87 | export default Rating 88 | -------------------------------------------------------------------------------- /src/components/shared/low/Hero.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | 4 | import TwoPanel from '../containers/TwoPanel' 5 | 6 | const StyledTwoPanel = styled(TwoPanel)` 7 | background-color: ${props => props.theme.bgVariant}; 8 | ` 9 | 10 | const LeftPanel = styled.div` 11 | color: white; 12 | font-size: 85%; 13 | ` 14 | 15 | const Hero = ({ 16 | className, 17 | leftStyle, 18 | 19 | above, 20 | title, 21 | description, 22 | below, 23 | 24 | ratio, 25 | children 26 | }) => ( 27 | 33 | {above} 34 |

{title}

35 |

{description}

36 | {below} 37 | 38 | } 39 | firstCenterBoth 40 | // firstStyle={{ overflow: 'visible' }} 41 | second={children} 42 | secondCenterX 43 | /> 44 | ) 45 | 46 | export default Hero 47 | -------------------------------------------------------------------------------- /src/components/shared/low/Icon.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | 3 | const Icon = styled.img` 4 | ${props => { 5 | if (!props.sizeAuto) { 6 | return `width: ${props.width || '5em'}; 7 | ${ 8 | props.height 9 | ? `height: ${props.height}` 10 | : !props.width && !props.height 11 | ? 'height: 5em;' 12 | : '' 13 | }` 14 | } 15 | }} 16 | 17 | 18 | ${props => (props.center ? 'margin: 0 auto;' : '')} 19 | 20 | ${props => { 21 | if (props.sharp) { 22 | return 'border-radius: 0;' 23 | } else if (props.circle) { 24 | return 'border-radius: 50%;' 25 | } else { 26 | return 'border-radius: 1em;' 27 | } 28 | }} 29 | 30 | ${props => 31 | props.noDefault || props.src 32 | ? '' 33 | : `background-color: ${props.theme.accentVariant};`} 34 | ` 35 | 36 | export default Icon 37 | -------------------------------------------------------------------------------- /src/components/shared/low/IconArea.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | 4 | const VerticalAlign = styled.div` 5 | display: flex; 6 | align-items: center; 7 | ${props => (props.reverse ? 'flex-direction: column-reverse;' : '')} 8 | ` 9 | 10 | const IconWrapper = styled.div` 11 | ${props => 12 | !props.reverse 13 | ? `margin-right: ${props.gap};` 14 | : `margin-left: ${props.gap};`} 15 | ` 16 | 17 | const IconArea = ({ 18 | className, 19 | children, 20 | icon, 21 | gap = '0.5em', 22 | reverse, 23 | onClick 24 | }) => { 25 | return ( 26 | 27 | 28 | {icon} 29 | 30 | {children} 31 | 32 | ) 33 | } 34 | 35 | export default IconArea 36 | -------------------------------------------------------------------------------- /src/components/shared/low/IconLine.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | 4 | const Container = styled.span` 5 | line-height: 1em; 6 | ` 7 | 8 | const IconWrapper = styled.div` 9 | ${props => 10 | props.reverse 11 | ? ` 12 | margin-left: ${props.gap || '0.2em'};` 13 | : ` 14 | margin-right: ${props.gap || '0.2em'};`} 15 | 16 | display: inline-flex; 17 | align-self: center; 18 | 19 | > svg, 20 | > img { 21 | width: 1em; 22 | height: 1em; 23 | top: 0.125em; 24 | position: relative; 25 | font-size: inherit; 26 | ${props => (!props.noTransition ? 'transition: 0.1s ease all;' : '')} 27 | } 28 | ` 29 | 30 | const IconLine = ({ 31 | className, 32 | children, 33 | icon, 34 | gap = "0.5em", 35 | reverse, 36 | noTransition 37 | }) => { 38 | return ( 39 | 40 | {!reverse ? ( 41 | <> 42 | 43 | {icon} 44 | 45 | {children} 46 | 47 | ) : ( 48 | <> 49 | {children} 50 | 51 | {icon} 52 | 53 | 54 | )} 55 | 56 | ) 57 | } 58 | 59 | export default IconLine 60 | -------------------------------------------------------------------------------- /src/components/shared/low/Interactive.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { PseudoBox } from '@chakra-ui/core' 3 | import styled from 'styled-components' 4 | 5 | const Rendered = styled(PseudoBox)` 6 | transition: 250ms all; 7 | cursor: pointer; 8 | outline: 0; 9 | 10 | &.interactive:focus { 11 | box-shadow: 0 0 0 3px ${props => props.theme.accentVariant}; 12 | z-index: 999; 13 | } 14 | ` 15 | 16 | const Interactive = ({ className, onKeyDown, onClick, ...props }) => { 17 | return ( 18 | { 22 | switch (e.key) { 23 | case ' ': 24 | case 'Enter': 25 | e.preventDefault() 26 | onClick() 27 | default: 28 | break 29 | } 30 | }} 31 | onClick={onClick} 32 | {...props} 33 | /> 34 | ) 35 | } 36 | 37 | export default Interactive 38 | -------------------------------------------------------------------------------- /src/components/shared/low/MediaLightbox.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useRef, useEffect, useMemo, useCallback } from 'react' 2 | import styled from 'styled-components' 3 | import Lightbox, { Modal, ModalGateway } from 'react-images' 4 | import AspectRatio from 'react-aspect-ratio' 5 | import 'react-aspect-ratio/aspect-ratio.css' 6 | 7 | export const TYPE_IMAGE = 'image' 8 | export const TYPE_VIDEO = 'video' 9 | 10 | const Image = styled.img` 11 | max-height: 98%; 12 | ` 13 | 14 | const Video = styled.video` 15 | max-height: 98%; 16 | ` 17 | 18 | const MediaLightbox = ({ 19 | type = TYPE_IMAGE, 20 | className, 21 | src, 22 | 23 | ratio 24 | }) => { 25 | const [open, setOpen] = useState(false) 26 | 27 | const mediaRef = useRef(null) 28 | 29 | const [mediaMaxWidth, setMediaMaxWidth] = useState() 30 | const [mediaAspectRatio, setMediaAspectRatio] = useState() 31 | 32 | useEffect(() => { 33 | if (ratio === undefined) return 34 | 35 | if (type === TYPE_IMAGE) { 36 | if (!mediaMaxWidth) { 37 | const width = mediaRef.current.node.clientWidth 38 | const mediaMaxWidth = width / ratio 39 | setMediaMaxWidth(mediaMaxWidth) 40 | } 41 | 42 | if (!setMediaAspectRatio) { 43 | const child = mediaRef.current.node.children[0] 44 | const { naturalWidth, naturalHeight } = child 45 | const mediaAspectRatio = naturalWidth / naturalHeight 46 | setMediaAspectRatio(mediaAspectRatio) 47 | } 48 | } else { 49 | if (!mediaMaxWidth) { 50 | setMediaMaxWidth('100%') 51 | } 52 | if (!mediaAspectRatio) { 53 | setMediaAspectRatio(ratio) 54 | } 55 | } 56 | }) 57 | 58 | const selectMedia = () => { 59 | switch (type) { 60 | case TYPE_VIDEO: 61 | return ( 62 | 65 | ) 66 | 67 | case TYPE_IMAGE: 68 | default: 69 | return ( 70 | setOpen(true)} 74 | /> 75 | ) 76 | } 77 | } 78 | 79 | return ( 80 | <> 81 | {ratio ? ( 82 | 90 | {selectMedia()} 91 | 92 | ) : ( 93 | selectMedia() 94 | )} 95 | 96 | {open ? ( 97 | setOpen(false)}> 98 | 103 | 104 | ) : null} 105 | 106 | 107 | ) 108 | } 109 | 110 | export default MediaLightbox 111 | -------------------------------------------------------------------------------- /src/components/shared/low/ProfPic.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Avatar from 'react-avatar' 3 | import { Stack, Box, Text } from '@chakra-ui/core' 4 | 5 | const ProfPic = ({ name, src, size = '1.6em' }) => { 6 | return ( 7 | 8 | 9 | 10 | 11 | 12 | {name} 13 | 14 | 15 | ) 16 | } 17 | 18 | export default ProfPic 19 | -------------------------------------------------------------------------------- /src/components/shared/low/ProgressBar.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Box } from '@chakra-ui/core' 3 | 4 | const ProgressBar = ({ 5 | value = 69, 6 | midValue, 7 | color = 'theme.accent', 8 | midColor = 'theme.accentVariant', 9 | orientation = 'horizontal', 10 | reverse, 11 | ...props 12 | }) => { 13 | const isHorz = orientation === 'horizontal' 14 | 15 | const cappedValue = value > 100 ? 100 : value 16 | const cappedMidValue = midValue > 100 ? 100 : midValue 17 | return ( 18 | 27 | {/* Mid */} 28 | 40 | 41 | {/* Load */} 42 | 54 | 55 | ) 56 | } 57 | 58 | export default ProgressBar 59 | -------------------------------------------------------------------------------- /src/components/shared/low/ProgressCircle.js: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react' 2 | import styled, { ThemeContext } from 'styled-components' 3 | 4 | import CircularProgress from '@material-ui/core/CircularProgress' 5 | 6 | const Container = styled.div` 7 | width: ${props => props.size}; 8 | height: ${props => props.size}; 9 | ` 10 | 11 | const Back = styled.div` 12 | position: absolute; 13 | z-index: 97; 14 | ` 15 | 16 | const Mid = styled.div` 17 | position: absolute; 18 | z-index: 98; 19 | ` 20 | 21 | const Front = styled.div` 22 | position: absolute; 23 | z-index: 99; 24 | ` 25 | 26 | const ProgressCircle = ({ 27 | className, 28 | size = '1em', 29 | value = 32, 30 | midValue = 69, 31 | thickness = 5, 32 | ...props 33 | }) => { 34 | const themeContext = useContext(ThemeContext) 35 | const parsedThickness = thickness === '100%' ? 22 : thickness 36 | 37 | return ( 38 | 39 | 40 | 47 | 48 | 49 | 50 | 57 | 58 | 59 | 60 | 67 | 68 | 69 | ) 70 | } 71 | 72 | export default ProgressCircle 73 | -------------------------------------------------------------------------------- /src/components/shared/low/StatusIcon.js: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react' 2 | import styled from 'styled-components' 3 | import { ThemeContext } from 'styled-components' 4 | 5 | import CheckIcon from '@material-ui/icons/CheckRounded' 6 | import ThreeDotsIcon from '@material-ui/icons/MoreHoriz' 7 | import LockIcon from '@material-ui/icons/LockRounded' 8 | 9 | import MuiIconFormatter from '../high/MuiIconFormatter' 10 | 11 | const selectColor = props => { 12 | switch (props.type) { 13 | case 'complete': 14 | return props.theme.pastel.green 15 | case 'incomplete': 16 | return props.theme.pastel.yellow 17 | case 'locked': 18 | return props.theme.pastel.red 19 | 20 | default: 21 | return null 22 | } 23 | } 24 | 25 | const Container = styled(MuiIconFormatter)` 26 | background-color: ${props => selectColor(props)}; 27 | ` 28 | 29 | /** 30 | * Helper Class to choose Status Icon appropriately 31 | * @param {status} props 32 | */ 33 | const StatusIcon = ({ type, width = '2.25em' }) => { 34 | const themeContext = useContext(ThemeContext) 35 | 36 | const selectIcon = () => { 37 | switch (type) { 38 | case 'complete': 39 | return 40 | case 'incomplete': 41 | return 42 | case 'locked': 43 | return 44 | 45 | default: 46 | return null 47 | } 48 | } 49 | 50 | return ( 51 | 52 | {selectIcon()} 53 | 54 | ) 55 | } 56 | 57 | export default StatusIcon 58 | -------------------------------------------------------------------------------- /src/components/shared/low/ThreeCheckbox.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useRef } from 'react' 2 | import styled from 'styled-components' 3 | import anime from 'animejs' 4 | 5 | import Pass from '@material-ui/icons/Check' 6 | import Fail from '@material-ui/icons/Close' 7 | import None from '@material-ui/icons/CheckBoxOutlineBlank' 8 | 9 | const selectColor = props => { 10 | switch (props.state) { 11 | case true: { 12 | return props.theme.pastel.green 13 | } 14 | case false: { 15 | return props.theme.pastel.red 16 | } 17 | default: 18 | return props.theme.offFont 19 | } 20 | } 21 | 22 | const Container = styled.button` 23 | padding: 0.3em; 24 | border-radius: 50%; 25 | 26 | display: flex; 27 | justify-content: center; 28 | align-items: center; 29 | 30 | color: white; 31 | border: 0.1em solid transparent; 32 | background-color: ${props => selectColor(props)}; 33 | box-shadow: 0 4px 1.5em ${props => selectColor(props)}88; 34 | cursor: pointer; 35 | font-size: 170%; 36 | outline: 0; 37 | 38 | transition: background-color 0.2s ease, box-shadow 0.2s ease; 39 | 40 | :focus { 41 | border: 0.1em solid 42 | ${props => { 43 | if (props.state === 'NONE') return '#00000001' 44 | return `${selectColor(props)}66` 45 | }}; 46 | } 47 | ` 48 | 49 | /** 50 | * Uses three states as opposed to the traditional two-state checkbox 51 | * - true, false, undefined 52 | * 53 | * @param {*} param0 54 | */ 55 | const ThreeCheckbox = ({ 56 | size, 57 | initialState, 58 | onChange = v => console.log(v) 59 | }) => { 60 | const containerRef = useRef(null) 61 | 62 | const [state, setState] = useState(initialState) 63 | 64 | useEffect(() => { 65 | anime({ 66 | targets: containerRef.current, 67 | scale: [0.8, 1], 68 | easing: 'easeOutElastic()', 69 | duration: 750 70 | }) 71 | onChange(state) 72 | }, [state]) 73 | 74 | const handleNextState = () => { 75 | switch (state) { 76 | case true: { 77 | setState(false) 78 | break 79 | } 80 | case false: { 81 | setState(undefined) 82 | break 83 | } 84 | default: 85 | setState(true) 86 | break 87 | } 88 | } 89 | 90 | const selectState = () => { 91 | switch (state) { 92 | case true: { 93 | return 94 | } 95 | case false: { 96 | return 97 | } 98 | default: 99 | return 100 | } 101 | } 102 | 103 | return ( 104 | 110 | {selectState()} 111 | 112 | ) 113 | } 114 | 115 | export default ThreeCheckbox 116 | -------------------------------------------------------------------------------- /src/components/shared/unused/Button.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled from "styled-components"; 3 | 4 | import CheckIcon from "../../../assets/icons/check"; 5 | 6 | const RenderedButton = styled.div` 7 | display: inline-block; 8 | cursor: pointer; 9 | text-decoration: none; 10 | padding: 0.5em 1em; 11 | margin: 1em 0.5em; 12 | border-radius: 7px; 13 | color: #0070f3; 14 | background-color: transparent; 15 | border: none; 16 | font-size: inherit; 17 | line-height: inherit; 18 | transition: background 0.2s ease, color 0.2s ease, box-shadow 0.2s ease; 19 | text-align: center; 20 | border: 1px solid transparent; 21 | 22 | &:hover { 23 | color: #0070f3; 24 | background: rgba(0, 118, 255, 0.1); 25 | } 26 | 27 | &.invert { 28 | border-radius: 7px; 29 | background-color: #0070f3; 30 | box-shadow: 0 4px 14px 0 rgba(0, 118, 255, 0.39); 31 | color: white; 32 | } 33 | 34 | &.invert:hover { 35 | background: rgba(0, 118, 255, 0.9); 36 | box-shadow: 0 6px 20px rgba(0, 118, 255, 0.23); 37 | } 38 | 39 | &.invert:active { 40 | background: #006ae6; 41 | } 42 | 43 | &.outline { 44 | border: 1px #0070f3 solid; 45 | } 46 | 47 | &.less-round { 48 | border-radius: 3.5px; 49 | } 50 | `; 51 | 52 | const Button = props => { 53 | switch (props.buttonState) { 54 | case "Check": 55 | return ; 56 | case "NextHint": 57 | return ( 58 | props.click(props.index)} 61 | > 62 | > 63 | 64 | ); 65 | case "PrevCard": 66 | const prev = "< Prev"; 67 | return ( 68 | props.moveToPrev(-1)} 71 | > 72 | {prev} 73 | 74 | ); 75 | case "NextCard": 76 | return ( 77 | props.moveToNext(1)} 80 | > 81 | Next > 82 | 83 | ); 84 | case "": 85 | return true; 86 | 87 | default: 88 | return ( 89 | 90 | {props.buttonState} 91 | 92 | ); 93 | } 94 | }; 95 | 96 | export default Button; 97 | -------------------------------------------------------------------------------- /src/components/shared/unused/Card.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from 'styled-components'; 3 | 4 | const RenderedCard = styled.div` 5 | position: relative; 6 | display: flex; 7 | flex-direction: column; 8 | justify-content: space-between; 9 | padding: 1rem; 10 | border-radius: 7px; 11 | box-shadow: 0 2px 10px rgba(0, 0, 0, 0.12); 12 | overflow: hidden; 13 | ` 14 | 15 | const Card = (props) => { 16 | return ( 17 | 18 | {props.content} 19 | 20 | ) 21 | } 22 | 23 | export default Card; -------------------------------------------------------------------------------- /src/components/shared/utils/ClampedDiv.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | 4 | const Container = styled.div` 5 | ${props => (props.inline ? 'display: inline-block;' : '')} 6 | width: ${props => props.width || 'fit-to-content'}; 7 | white-space: nowrap; 8 | overflow: hidden; 9 | text-overflow: ellipsis; 10 | ` 11 | 12 | const ClampedDiv = ({ children, className, width, inline }) => { 13 | return ( 14 | 15 | {children} 16 | 17 | ) 18 | } 19 | 20 | export default ClampedDiv 21 | -------------------------------------------------------------------------------- /src/components/shared/utils/ClampedText.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | 4 | const Container = styled.span` 5 | display: -webkit-box; 6 | -webkit-box-orient: vertical; 7 | -webkit-line-clamp: ${props => props.clamp || 2}; 8 | overflow: hidden; 9 | ` 10 | 11 | const ClampedText = ({ children, className, clamp }) => { 12 | return ( 13 | 14 | {children} 15 | 16 | ) 17 | } 18 | 19 | export default ClampedText 20 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import { Provider as ReduxProvider } from 'react-redux' 4 | 5 | import store from './redux/store' 6 | import App from './App' 7 | 8 | ReactDOM.render( 9 | 10 | 11 | , 12 | document.getElementById('root') 13 | ) 14 | -------------------------------------------------------------------------------- /src/redux/actionTypes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * account 3 | */ 4 | export const AUTHENTICATE = 'AUTHENTICATE' 5 | export const DEAUTHENTICATE = 'DEAUTHENTICATE' 6 | export const SET_USER_DATA = 'SET_USER_DATA' 7 | 8 | /** 9 | * cache 10 | */ 11 | export const SAVE_TO_CACHE = 'SAVE_TO_CACHE' 12 | 13 | /** 14 | * studentData 15 | */ 16 | export const RESET_STUDENT_DATA = 'RESET_STUDENT_DATA' 17 | export const SAVE_TO_PROGRESS = 'SAVE_TO_PROGRESS' 18 | export const SET_STUDENT_DATA = 'SET_STUDENT_DATA' 19 | export const SET_CURRENT_TRACK = 'SET_CURRENT_TRACK' 20 | export const SET_CURRENT_TOPIC = 'SET_CURRENT_TOPIC' 21 | export const SET_SUGGESTED_ACTIVITY = 'SET_SUGGESTED_ACTIVITY' 22 | 23 | export const INCREMENT_GEMS_BY = 'INCREMENT_GEMS_BY' 24 | 25 | /** 26 | * teacherData 27 | */ 28 | /* initialization processes */ 29 | export const SET_SUBMISSIONS = 'SET_SUBMISSIONS' 30 | 31 | /* runtime processes */ 32 | export const UPDATE_FEEDBACKS = 'UPDATE_FEEDBACKS' 33 | export const SET_CURRENT_CLASSROOM_BY_INDEX = 'SET_CURRENT_CLASSROOM_BY_INDEX' 34 | export const SET_CURRENT_SUBMISSION_BY_INDEX = 'SET_CURRENT_SUBMISSION_BY_INDEX' 35 | 36 | /** 37 | * learnData 38 | */ 39 | export const RESET_LEARN_DATA = 'RESET_LEARN_DATA' 40 | export const SET_SELECTED_ACTIVITY = 'SET_SELECTED_ACTIVITY' 41 | export const SET_INDICATORS = 'SET_INDICATORS' 42 | 43 | export const SET_CURRENT_CARD_BY_INDEX = 'SET_CURRENT_CARD_BY_INDEX' 44 | export const INCREMENT_CURRENT_CARD_INDEX = 'INCREMENT_CURRENT_CARD_INDEX' 45 | export const SET_LAST_CARD_UNLOCKED_INDEX_BY_ID = 46 | 'SET_LAST_CARD_UNLOCKED_INDEX_BY_ID' 47 | export const INCREMENT_LAST_CARD_UNLOCKED_INDEX = 48 | 'INCREMENT_LAST_CARD_UNLOCKED_INDEX' 49 | 50 | export const BROADCAST_BUTTON_STATE = 'BROADCAST_BUTTON_STATE' 51 | export const SCHEDULE_BUTTON_STATE = 'SCHEDULE_BUTTON_STATE' 52 | export const RESET_BUTTON_STATE_SCHEDULE = 'RESET_BUTTON_STATE_SCHEDULE' 53 | 54 | /** 55 | * theme 56 | */ 57 | export const SET_THEME = 'SET_THEME' 58 | -------------------------------------------------------------------------------- /src/redux/actions/account.js: -------------------------------------------------------------------------------- 1 | import { AUTHENTICATE, DEAUTHENTICATE, SET_USER_DATA } from '../actionTypes' 2 | import { fetchUserData } from '../../services/AccountService' 3 | 4 | export const initUserData = userId => async dispatch => { 5 | const userData = await fetchUserData(userId) 6 | 7 | const validatedName = 8 | userData.name === 'None' ? userData.githubUsername : userData.name 9 | 10 | dispatch({ 11 | type: SET_USER_DATA, 12 | userData: { ...userData, name: validatedName } 13 | }) 14 | } 15 | 16 | export const authenticate = meta => { 17 | localStorage.setItem('meta', JSON.stringify(meta)) 18 | return { 19 | type: AUTHENTICATE, 20 | meta 21 | } 22 | } 23 | 24 | export const deauthenticate = () => { 25 | localStorage.removeItem('meta') 26 | 27 | if (window.location.pathname !== '/') { 28 | console.log('ok') 29 | window.location.replace('/') 30 | } 31 | 32 | return { 33 | type: DEAUTHENTICATE 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/redux/actions/cache.js: -------------------------------------------------------------------------------- 1 | import { SAVE_TO_CACHE } from '../actionTypes' 2 | 3 | /** 4 | * 5 | * @param {string} cacheType 6 | * @param {object} newLoads { [id1]: {...}, [id2]: {...} } 7 | * @param {object} options { overwrite: bool, ... } 8 | */ 9 | export const saveToCache = (cacheType, newLoads, options) => { 10 | const initialOptions = { 11 | merge: false // if false, newLoads will not overwrite prexisting ones 12 | } 13 | 14 | return { 15 | type: SAVE_TO_CACHE, 16 | cacheType, 17 | newLoads, 18 | options: options ?? initialOptions 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/redux/actions/studentData.js: -------------------------------------------------------------------------------- 1 | import { 2 | RESET_STUDENT_DATA, 3 | SET_STUDENT_DATA, 4 | INCREMENT_GEMS_BY 5 | } from '../actionTypes' 6 | 7 | import { 8 | fetchStudentData, 9 | setChosenProjects 10 | } from '../../services/StudentService' 11 | import { setSelectedActivity } from './learnData' 12 | import { saveToCache } from './cache' 13 | import { CACHE_MODULE_PROGRESS } from '../../components/HOC/WithApiCache' 14 | 15 | /* ===== INITIALIZATION */ 16 | 17 | export const init = studentId => async dispatch => { 18 | dispatch({ 19 | type: RESET_STUDENT_DATA 20 | }) 21 | 22 | const studentData = await fetchStudentData(studentId) 23 | 24 | /** 25 | * TODO TEMPORARY CODE TEMP WARN 26 | */ 27 | let inprogressModules = [...studentData.inprogressModules] 28 | if (studentData.incompleteModules.length) { 29 | inprogressModules = inprogressModules.concat(studentData.incompleteModules) 30 | } 31 | 32 | dispatch(setStudentData({ ...studentData, inprogressModules })) 33 | 34 | // external 35 | dispatch(setSelectedActivity(studentData.suggestedActivity)) 36 | } 37 | 38 | const setStudentData = studentData => ({ 39 | type: SET_STUDENT_DATA, 40 | studentData 41 | }) 42 | 43 | /** 44 | * 45 | * @param {int} moduleId 46 | * @param {int} id 47 | * @param {string} actionType inprogress | completed 48 | */ 49 | export const updateModuleActivityProgress = (moduleId, id, actionType) => ({ 50 | type: 'UPDATE_MODULE_ACTIVITY_PROGRESS', 51 | moduleId, 52 | id, 53 | actionType 54 | }) 55 | 56 | // ===== RUNTIME 57 | 58 | export const chooseProjects = (moduleId, projects) => dispatch => { 59 | dispatch( 60 | saveToCache( 61 | CACHE_MODULE_PROGRESS, 62 | { [moduleId]: { chosenProjects: projects } }, 63 | { merge: true } 64 | ) 65 | ) 66 | 67 | setChosenProjects(moduleId, projects).then(_ => console.log(_.message)) 68 | } 69 | 70 | export const incrementGemsBy = gemAmount => ({ 71 | type: INCREMENT_GEMS_BY, 72 | gemAmount 73 | }) 74 | -------------------------------------------------------------------------------- /src/redux/actions/teacherData.js: -------------------------------------------------------------------------------- 1 | import { 2 | SET_SUBMISSIONS, 3 | UPDATE_FEEDBACKS, 4 | SET_CURRENT_CLASSROOM_BY_INDEX, 5 | SET_CURRENT_SUBMISSION_BY_INDEX 6 | } from '../actionTypes' 7 | 8 | import { 9 | fetchTeacherData, 10 | fetchClassroom, 11 | fetchSubmissionsAll 12 | } from '../../services/TeacherService' 13 | 14 | /* ===== INITIALIZATION */ 15 | export const init = teacherId => async dispatch => { 16 | const teacherData = await fetchTeacherData(teacherId) 17 | const classroomId = teacherData.classrooms[0].id 18 | 19 | const [classroom, submissions] = await Promise.all([ 20 | fetchClassroom(classroomId), 21 | fetchSubmissionsAll(classroomId) 22 | ]) 23 | 24 | console.log(classroom, submissions) 25 | dispatch(setSubmissions(submissions)) 26 | } 27 | 28 | const setSubmissions = submissions => ({ 29 | type: SET_SUBMISSIONS, 30 | submissions 31 | }) 32 | 33 | /* ===== RUNTIME */ 34 | export const updateFeedbacks = (studentId, checkpointId, feedbackChanges) => ({ 35 | type: UPDATE_FEEDBACKS, 36 | studentId, 37 | checkpointId, 38 | feedbackChanges 39 | }) 40 | 41 | export const setCurrentClassroomByIndex = classroomIndex => ({ 42 | type: SET_CURRENT_CLASSROOM_BY_INDEX, 43 | classroomIndex 44 | }) 45 | 46 | export const setCurrentSubmissionByIndex = submissionIndex => ({ 47 | type: SET_CURRENT_SUBMISSION_BY_INDEX, 48 | submissionIndex 49 | }) 50 | -------------------------------------------------------------------------------- /src/redux/actions/theme.js: -------------------------------------------------------------------------------- 1 | import { SET_THEME } from "../actionTypes"; 2 | 3 | export const setTheme = theme => { 4 | return { type: SET_THEME, theme }; 5 | }; 6 | 7 | export default setTheme; 8 | -------------------------------------------------------------------------------- /src/redux/reducers/account.js: -------------------------------------------------------------------------------- 1 | import { AUTHENTICATE, DEAUTHENTICATE, SET_USER_DATA } from '../actionTypes' 2 | 3 | const storedMeta = localStorage.getItem('meta') 4 | 5 | const initialState = { 6 | meta: JSON.parse(storedMeta), 7 | user: null 8 | } 9 | 10 | const reducer = (state = initialState, action) => { 11 | switch (action.type) { 12 | case AUTHENTICATE: 13 | return { 14 | ...state, 15 | meta: action.meta 16 | } 17 | 18 | case DEAUTHENTICATE: 19 | return {} 20 | 21 | case SET_USER_DATA: 22 | return { 23 | ...state, 24 | user: action.userData 25 | } 26 | 27 | default: 28 | return state 29 | } 30 | } 31 | 32 | export default reducer 33 | -------------------------------------------------------------------------------- /src/redux/reducers/cache.js: -------------------------------------------------------------------------------- 1 | import { cloneDeep, mergeWith as mergeDeep } from 'lodash' 2 | import { SAVE_TO_CACHE } from '../actionTypes' 3 | import * as cacheTypes from '../../components/HOC/WithApiCache' 4 | 5 | const initialState = { 6 | [cacheTypes.CACHE_TOPIC]: {}, 7 | [cacheTypes.CACHE_MODULE]: {}, 8 | [cacheTypes.CACHE_ACTIVITY]: {}, 9 | [cacheTypes.CACHE_CARD]: {}, 10 | [cacheTypes.CACHE_CHECKPOINT]: {}, 11 | [cacheTypes.CACHE_CONCEPT]: {}, 12 | [cacheTypes.CACHE_HINT]: {}, 13 | 14 | [cacheTypes.CACHE_USER]: {}, 15 | [cacheTypes.CACHE_STUDENT]: {}, 16 | [cacheTypes.CACHE_MODULE_PROGRESS]: {}, 17 | [cacheTypes.CACHE_ACTIVITY_PROGRESS]: {}, 18 | [cacheTypes.CACHE_HINT_PROGRESS]: {}, 19 | [cacheTypes.CACHE_CHECKPOINTS_PROGRESS]: {} 20 | } 21 | 22 | const reducer = (state = initialState, action) => { 23 | switch (action.type) { 24 | case SAVE_TO_CACHE: { 25 | const { merge } = action.options 26 | 27 | return { 28 | ...state, 29 | [action.cacheType]: merge 30 | ? mergeDeep( 31 | cloneDeep(state[action.cacheType]), 32 | cloneDeep(action.newLoads), 33 | (objValue, srcValue) => { 34 | if (Array.isArray(objValue) && Array.isArray(srcValue)) { 35 | return srcValue 36 | } 37 | } 38 | ) 39 | : { 40 | ...action.newLoads, 41 | ...state[action.cacheType] 42 | } 43 | } 44 | } 45 | 46 | case 'UPDATE_MODULE_ACTIVITY_PROGRESS': { 47 | const modu1e = state[cacheTypes.CACHE_MODULE_PROGRESS][action.moduleId] 48 | if (!modu1e) return state 49 | 50 | const { 51 | incompleteActivities, 52 | inprogressActivities, 53 | completedActivities 54 | } = modu1e 55 | 56 | let final = modu1e 57 | if (action.actionType === 'inprogress') { 58 | final = { 59 | ...final, 60 | incompleteActivities: incompleteActivities?.filter( 61 | i => i.id !== action.id 62 | ), 63 | inprogressActivities: (inprogressActivities ?? []).concat([ 64 | { id: action.id } 65 | ]) 66 | } 67 | } else if (action.actionType === 'completed') { 68 | final = { 69 | ...final, 70 | inprogressActivities: inprogressActivities.filter( 71 | i => i.id !== action.id 72 | ), 73 | completedActivities: (completedActivities ?? []).concat([ 74 | { id: action.id } 75 | ]) 76 | } 77 | } 78 | 79 | return { 80 | ...state, 81 | [cacheTypes.CACHE_MODULE_PROGRESS]: { 82 | ...state[cacheTypes.CACHE_MODULE_PROGRESS], 83 | [action.moduleId]: final 84 | } 85 | } 86 | } 87 | 88 | default: 89 | return state 90 | } 91 | } 92 | 93 | export default reducer 94 | -------------------------------------------------------------------------------- /src/redux/reducers/studentData.js: -------------------------------------------------------------------------------- 1 | import { 2 | RESET_STUDENT_DATA, 3 | SAVE_TO_PROGRESS, 4 | SET_STUDENT_DATA, 5 | SET_CURRENT_TRACK, 6 | SET_CURRENT_TOPIC, 7 | SET_SUGGESTED_ACTIVITY, 8 | INCREMENT_GEMS_BY 9 | } from '../actionTypes' 10 | import * as cacheTypes from '../../components/HOC/WithApiCache' 11 | 12 | const initialState = { 13 | suggestedActivity: null, 14 | inprogressModules: [], 15 | inprogressTopics: [], 16 | gems: 860, 17 | 18 | [cacheTypes.CACHE_MODULE_PROGRESS]: {}, 19 | [cacheTypes.CACHE_ACTIVITY_PROGRESS]: {}, 20 | [cacheTypes.CACHE_HINT_PROGRESS]: {}, 21 | [cacheTypes.CACHE_CHECKPOINTS_PROGRESS]: {} 22 | } 23 | 24 | const reducer = (state = initialState, action) => { 25 | switch (action.type) { 26 | case RESET_STUDENT_DATA: { 27 | return initialState 28 | } 29 | 30 | case SAVE_TO_PROGRESS: { 31 | return state 32 | } 33 | 34 | case SET_STUDENT_DATA: { 35 | return { 36 | ...initialState, 37 | ...action.studentData 38 | } 39 | } 40 | 41 | case SET_CURRENT_TRACK: { 42 | return { 43 | ...state, 44 | current_track: { ...action.currentTrack } 45 | } 46 | } 47 | 48 | case SET_CURRENT_TOPIC: { 49 | return { 50 | ...state, 51 | current_topic: { ...action.currentTopic } 52 | } 53 | } 54 | 55 | case SET_SUGGESTED_ACTIVITY: { 56 | return { 57 | ...state, 58 | suggested_activity: { ...action.suggestedActivity } 59 | } 60 | } 61 | 62 | case INCREMENT_GEMS_BY: { 63 | return { 64 | ...state, 65 | gems: state.gems + action.gemAmount 66 | } 67 | } 68 | 69 | default: 70 | return state 71 | } 72 | } 73 | 74 | export default reducer 75 | -------------------------------------------------------------------------------- /src/redux/reducers/teacherData.js: -------------------------------------------------------------------------------- 1 | import { 2 | SET_SUBMISSIONS, 3 | UPDATE_FEEDBACKS, 4 | SET_CURRENT_CLASSROOM_BY_INDEX, 5 | SET_CURRENT_SUBMISSION_BY_INDEX 6 | } from '../actionTypes' 7 | 8 | const initialState = { 9 | indicators: { 10 | currentClassroomIndex: 0, 11 | currentSubmissionIndex: 1 12 | }, 13 | submissions: [], 14 | 15 | ram: { 16 | feedbacks: { 17 | // student1_checkpoint23: { 18 | // checkpoint_id: '69', 19 | // is_passed: 'true', 20 | // comment: 'I am a comment' 21 | // } 22 | } 23 | } 24 | } 25 | 26 | const reducer = (state = initialState, action) => { 27 | switch (action.type) { 28 | case SET_SUBMISSIONS: { 29 | return { 30 | ...state, 31 | submissions: action.submissions 32 | } 33 | } 34 | 35 | case UPDATE_FEEDBACKS: { 36 | const { studentId, checkpointId, feedbackChanges } = action 37 | const feedbackKey = `student${studentId}_checkpoint${checkpointId}` 38 | 39 | return { 40 | ...state, 41 | ram: { 42 | ...state.ram, 43 | feedbacks: { 44 | ...state.ram.feedbacks, 45 | [feedbackKey]: { 46 | ...state.ram.feedbacks?.[feedbackKey], 47 | ...feedbackChanges 48 | } 49 | } 50 | } 51 | } 52 | } 53 | 54 | case SET_CURRENT_CLASSROOM_BY_INDEX: { 55 | return { 56 | ...state, 57 | indicators: { 58 | ...state.indiciators, 59 | currentClassroomIndex: action.classroomIndex 60 | } 61 | } 62 | } 63 | 64 | case SET_CURRENT_SUBMISSION_BY_INDEX: { 65 | return { 66 | ...state, 67 | indicators: { 68 | ...state.indicators, 69 | currentSubmissionIndex: action.submissionIndex 70 | } 71 | } 72 | } 73 | 74 | default: 75 | return state 76 | } 77 | } 78 | 79 | export default reducer 80 | -------------------------------------------------------------------------------- /src/redux/reducers/theme.js: -------------------------------------------------------------------------------- 1 | import { SET_THEME } from '../actionTypes' 2 | import defaultTheme from '../../styles/theme' 3 | 4 | const initialState = defaultTheme 5 | 6 | const theme = (state = initialState, action) => { 7 | if (action.type === SET_THEME) { 8 | return action.theme 9 | } 10 | return state 11 | } 12 | 13 | export default theme 14 | -------------------------------------------------------------------------------- /src/redux/rootReducer.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux' 2 | import account from './reducers/account' 3 | import learnData from './reducers/learnData' 4 | import cache from './reducers/cache' 5 | import studentData from './reducers/studentData' 6 | import teacherData from './reducers/teacherData' 7 | import theme from './reducers/theme' 8 | 9 | export default combineReducers({ 10 | account, 11 | learnData, 12 | cache, 13 | studentData, 14 | teacherData, 15 | theme 16 | }) 17 | -------------------------------------------------------------------------------- /src/redux/store.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware, compose } from 'redux' 2 | import thunk from 'redux-thunk' 3 | 4 | import rootReducer from './rootReducer' 5 | 6 | import * as accountActions from './actions/account' 7 | import * as learnDataActions from './actions/learnData' 8 | import * as cacheActions from './actions/cache' 9 | import * as teacherDataActions from './actions/teacherData' 10 | import * as studentDataActions from './actions/studentData' 11 | import * as themeActions from './actions/theme' 12 | const actionCreators = { 13 | ...accountActions, 14 | ...learnDataActions, 15 | ...cacheActions, 16 | ...teacherDataActions, 17 | ...studentDataActions, 18 | ...themeActions 19 | } 20 | 21 | const configureStore = initialState => { 22 | const composeEnhancers = 23 | typeof window === 'object' && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ 24 | ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({ 25 | actionCreators, 26 | trace: true, 27 | traceLimit: 25 28 | }) 29 | : compose 30 | 31 | const middleware = [thunk] 32 | 33 | console.log(process.env.NODE_ENV) 34 | if (process.env.NODE_ENV !== 'production') 35 | middleware.unshift( 36 | require('redux-immutable-state-invariant').default({ 37 | ignore: ['learnData.currentCardUnnestedUnlockedHintRefs'] 38 | }) 39 | ) 40 | 41 | const store = createStore( 42 | rootReducer, 43 | initialState, 44 | composeEnhancers(applyMiddleware(...middleware)) 45 | ) 46 | 47 | // Enable Webpack hot module replacement for reducers 48 | // if (module.hot) { 49 | // module.hot.accept('./reducers', () => 50 | // store.replaceReducer(require('./reducers')) 51 | // ) 52 | // } 53 | 54 | return store 55 | } 56 | 57 | const store = configureStore() 58 | 59 | export default store 60 | -------------------------------------------------------------------------------- /src/services/AccountService.js: -------------------------------------------------------------------------------- 1 | import { baseUrl, backend } from './AxiosInstances' 2 | 3 | export const fetchMetaData = () => { 4 | const endpoint = '/meta' 5 | return backend.get(endpoint) 6 | } 7 | 8 | export const fetchUserData = userId => { 9 | const endpoint = `/users/${userId}` 10 | return backend.get(endpoint) 11 | } 12 | 13 | export const login = () => { 14 | window.location.href = `${baseUrl}/login` 15 | // const endpoint = '/login' 16 | // return backend.get(endpoint) 17 | } 18 | 19 | export const logout = () => { 20 | const endpoint = '/logout' 21 | return backend.get(endpoint) 22 | } 23 | -------------------------------------------------------------------------------- /src/services/Auth0Service.js: -------------------------------------------------------------------------------- 1 | import auth0 from 'auth0-js' 2 | 3 | class Auth { 4 | constructor() { 5 | this.auth0 = new auth0.WebAuth({ 6 | domain: '', 7 | audience: 'https:///userinfo', 8 | clientID: '', 9 | redirectUri: 'http://localhost:3000/callback', 10 | responseType: 'id_token', 11 | scope: 'openid profile' 12 | }) 13 | 14 | this.getProfile = this.getProfile.bind(this) 15 | this.handleAuthentication = this.handleAuthentication.bind(this) 16 | this.isAuthenticated = this.isAuthenticated.bind(this) 17 | this.signIn = this.signIn.bind(this) 18 | this.signOut = this.signOut.bind(this) 19 | } 20 | 21 | getProfile() { 22 | return this.profile 23 | } 24 | 25 | getIdToken() { 26 | return this.idToken 27 | } 28 | 29 | isAuthenticated() { 30 | return new Date().getTime() < this.expiresAt 31 | } 32 | 33 | signIn() { 34 | this.auth0.authorize() 35 | } 36 | 37 | handleAuthentication() { 38 | return new Promise((resolve, reject) => { 39 | this.auth0.parseHash((err, authResult) => { 40 | if (err) return reject(err) 41 | if (!authResult || !authResult.idToken) { 42 | return reject(err) 43 | } 44 | this.idToken = authResult.idToken 45 | this.profile = authResult.idTokenPayload 46 | // set the time that the id token will expire at 47 | this.expiresAt = authResult.idTokenPayload.exp * 1000 48 | resolve() 49 | }) 50 | }) 51 | } 52 | 53 | signOut() { 54 | // clear id token, profile, and expiration 55 | this.idToken = null 56 | this.profile = null 57 | this.expiresAt = null 58 | } 59 | } 60 | 61 | const auth0Client = new Auth() 62 | 63 | export default auth0Client 64 | -------------------------------------------------------------------------------- /src/services/AxiosInstances.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import camelCase from 'camelcase-keys-deep' 3 | 4 | import { deauthenticate } from '../redux/actions/account' 5 | import store from '../redux/store' 6 | 7 | /** GENERAL BACKEND (mainly for GET) */ 8 | 9 | const backendResponseInterceptor = error => { 10 | if (!error.response) { 11 | throw Error( 12 | 'A so called "CORS" error likely occurred. The request bounced.' 13 | ) 14 | } 15 | const { 16 | status, 17 | statusText, 18 | config: { method, url }, 19 | data: { message, msg } 20 | } = error.response 21 | 22 | if (status === 401) { 23 | store.dispatch(deauthenticate()) 24 | 25 | // [WithAuthentication] continue error chain to deauthenticate 26 | if (!localStorage.getItem('meta')) throw error 27 | 28 | return error 29 | } 30 | 31 | if (message !== 'Card already unlocked') 32 | alert(`${method.toUpperCase()} ${url} 33 | ${status} (${statusText}) 34 | ${message ?? msg ?? ''}`) 35 | return error 36 | } 37 | 38 | export const baseUrl = 'https://wongband.pythonanywhere.com/' 39 | // export const baseUrl = 'https://bit-backend-staging.herokuapp.com/' 40 | // const baseUrl = 'https://darlene-backend.herokuapp.com/' 41 | // export const baseUrl = 'https://214509c7.ngrok.io' 42 | // const baseUrl = 'http://localhost:5000/' 43 | 44 | export const backend = axios.create({ 45 | baseURL: baseUrl, 46 | withCredentials: true 47 | }) 48 | backend.interceptors.response.use( 49 | response => camelCase(response.data, { deep: true }), 50 | error => backendResponseInterceptor(error) 51 | ) 52 | 53 | /** BACKEND_SAVES (with CSRF, mainly for PUT, POST, DELETE) */ 54 | 55 | // window.onbeforeunload = e => { 56 | // e.preventDefault() 57 | // e.returnValue('Changes may not be saved. Continue?') 58 | // return 'Changes may not be saved. Continue?' 59 | // } 60 | export const backendSaves = axios.create({ 61 | baseURL: baseUrl, 62 | withCredentials: true 63 | }) 64 | backendSaves.interceptors.response.use( 65 | response => camelCase(response.data, { deep: true }), 66 | error => backendResponseInterceptor(error) 67 | ) 68 | 69 | const graderBaseURL = 'https://darlene-autograder.herokuapp.com/' 70 | export const grader = axios.create({ 71 | baseURL: graderBaseURL 72 | // withCredentials: true 73 | }) 74 | grader.interceptors.response.use(response => 75 | camelCase(response.data, { deep: true }) 76 | ) 77 | 78 | const cdnBaseUrl = 'https://d36nt3c422j20i.cloudfront.net/' 79 | export const cdn = axios.create({ 80 | baseURL: cdnBaseUrl 81 | }) 82 | cdn.interceptors.response.use(response => 83 | camelCase(response.data, { deep: true }) 84 | ) 85 | -------------------------------------------------------------------------------- /src/services/ContentService.js: -------------------------------------------------------------------------------- 1 | import { backend, cdn } from './AxiosInstances' 2 | 3 | const FETCH_FROM_CDN = true 4 | 5 | export const autoFetch = async (id, cacheType) => { 6 | const [type, params] = cacheType 7 | .replace('cached', '') 8 | .split(/(?=[A-Z])/) 9 | .map(w => w.toLowerCase()) 10 | 11 | /** 12 | * TEMP SPECIAL 13 | */ 14 | if (type === 'users') { 15 | return backend.get(`/users/${id}`) 16 | } 17 | if (type === 'students') { 18 | return backend.get(`/students/${id}`) 19 | } 20 | 21 | const endpoint = `/${type}/${id}` + (params ? `/${params}` : '') 22 | 23 | if (!params && FETCH_FROM_CDN) { 24 | try { 25 | const data = await cdn.get(`${endpoint}/data.json`) 26 | return data 27 | } catch (e) { 28 | console.log( 29 | '[ContentService] Error fetching from CDN. Moving to servers...' 30 | ) 31 | if (e.response?.status === 404) 32 | console.log( 33 | '[ContentService] The above error was due to missing content' 34 | ) 35 | } 36 | } 37 | return backend.get(endpoint) 38 | } 39 | -------------------------------------------------------------------------------- /src/services/ExploreService.js: -------------------------------------------------------------------------------- 1 | import { backend } from './AxiosInstances' 2 | -------------------------------------------------------------------------------- /src/services/LearnService.js: -------------------------------------------------------------------------------- 1 | import { backend, backendSaves, grader } from './AxiosInstances' 2 | 3 | /* ===== RUNTIME */ 4 | 5 | export const deleteActivityProgress = activityId => { 6 | const endpoint = `/activities/${activityId}/progress` 7 | return backendSaves.delete(endpoint) 8 | } 9 | 10 | export const unlockCard = (activityId, cardId) => { 11 | const endpoint = `/activities/${activityId}/cards/${cardId}` 12 | return backendSaves.put(endpoint) 13 | } 14 | 15 | export const unlockHint = (activityId, hintId) => { 16 | const endpoint = `/activities/${activityId}/hints/${hintId}` 17 | return backendSaves.put(endpoint) 18 | } 19 | 20 | export const submitCheckpointProgress = ( 21 | activityId, 22 | checkpointId, 23 | type, 24 | content 25 | ) => { 26 | const backendEndpoint = `checkpoints/${checkpointId}/progress` 27 | 28 | const formData = new FormData() 29 | formData.append('content', content) 30 | 31 | if (type !== 'Multiple Choice') { 32 | formData.append('comment', 'null') 33 | } 34 | 35 | if (type === 'Autograder') { 36 | formData.append('activity_id', activityId) 37 | formData.append('checkpoint_id', checkpointId) 38 | formData.append('username', 'Student@example.com') 39 | return grader.post('/uploader', formData) 40 | } 41 | return backendSaves.put(backendEndpoint, formData) 42 | } 43 | -------------------------------------------------------------------------------- /src/services/PusherService.js: -------------------------------------------------------------------------------- 1 | import Pusher from 'pusher-js' 2 | import { setPusherClient } from 'react-pusher' 3 | 4 | const pusherClient = new Pusher(process.env.PUSHER_KEY, { 5 | clustor: process.env.PUSHER_CLUSTER, 6 | authEndpoint: 'https://darlene-autograder.herokuapp.com/uploader/cli', 7 | forceTLS: true 8 | }) 9 | 10 | setPusherClient(pusherClient) 11 | -------------------------------------------------------------------------------- /src/services/StudentService.js: -------------------------------------------------------------------------------- 1 | import { backend, backendSaves } from './AxiosInstances' 2 | 3 | /** 4 | * GET request for getting Student data 5 | */ 6 | export const fetchStudentData = studentId => { 7 | const endpoint = `/students/${studentId}` 8 | return backend.get(endpoint) 9 | } 10 | 11 | /** ===== RUNTIME */ 12 | 13 | export const joinClassroom = classCode => { 14 | const endpoint = '/students/classrooms' 15 | return backendSaves.put(endpoint, { class_code: classCode }) 16 | } 17 | 18 | export const setSuggestedActivity = (studentId, id, moduleId) => 19 | updateStudentData(studentId, { 20 | suggested_activity: { 21 | id, 22 | module_id: moduleId 23 | } 24 | }) 25 | 26 | export const setChosenProjects = (moduleId, chosenProjects) => 27 | updateModuleProgress(moduleId, { 28 | chosen_project_ids: chosenProjects.map(p => p.id) 29 | }) 30 | 31 | const updateStudentData = (studentId, updates) => { 32 | const endpoint = `/students/${studentId}` 33 | return backendSaves.put(endpoint, updates) 34 | } 35 | 36 | const updateModuleProgress = (id, updates) => { 37 | const endpoint = `/modules/${id}/progress` 38 | return backendSaves.put(endpoint, updates) 39 | } 40 | 41 | // 42 | 43 | //@unused 44 | /** 45 | * GET request for getting track data 46 | * @param {String} trackID 47 | */ 48 | export const fetchTrack = trackID => { 49 | const endpoint = `tracks/${trackID}` 50 | return backend.get(endpoint) 51 | } 52 | 53 | export const fetchTrackProgress = trackID => { 54 | const endpoint = `tracks/${trackID}/progress` 55 | return backend.get(endpoint) 56 | } 57 | 58 | /** 59 | * GET request for getting topic data 60 | * @param {String} topicID 61 | */ 62 | export const fetchTopic = topicID => { 63 | const endpoint = `topics/${topicID}` 64 | return backend.get(endpoint) 65 | } 66 | -------------------------------------------------------------------------------- /src/services/TestService.js: -------------------------------------------------------------------------------- 1 | import { backend } from './AxiosInstances' 2 | 3 | export const ping = async () => { 4 | // await backend.get('/auth').catch(() => {}) 5 | 6 | const startTime = new Date() 7 | const ping = (await backend.get('ping')).message 8 | const endTime = new Date() 9 | console.log(ping + ':', endTime - startTime + 'ms') 10 | } 11 | -------------------------------------------------------------------------------- /src/styles/GlobalAnime.js: -------------------------------------------------------------------------------- 1 | import anime from 'animejs' 2 | 3 | export const fadeIn = (targets, options) => 4 | anime({ 5 | targets, 6 | opacity: [0, 1], 7 | easing: 'easeOutQuad', 8 | duration: 500, 9 | ...options 10 | }) 11 | 12 | export const slideIn = (targets, options) => 13 | anime({ 14 | targets, 15 | translateX: ['-1em', 0], 16 | easing: 'easeOutQuad', 17 | duration: 750, 18 | ...options 19 | }) 20 | 21 | /** 22 | * STATUS-BASED ANIMATIONS 23 | * - works closely with react-transition-group 24 | */ 25 | 26 | export const statusFadeOut = (status, targets, duration = 750) => { 27 | return anime({ 28 | targets, 29 | opacity: () => { 30 | switch (status) { 31 | case 'exiting': 32 | return 0 33 | default: 34 | return 1 35 | } 36 | }, 37 | easing: 'easeInQuad', 38 | duration 39 | }) 40 | } 41 | -------------------------------------------------------------------------------- /src/styles/media.js: -------------------------------------------------------------------------------- 1 | import { css } from 'styled-components' 2 | 3 | export const sizes = { 4 | massive: '1920', 5 | thicc: '1680', 6 | giant: '1440', 7 | bigDesktop: '1200', 8 | desktop: '1000', 9 | tablet: '768', 10 | thone: '600', 11 | phablet: '480', 12 | phone: '376', 13 | tiny: '330' 14 | } 15 | 16 | // iterate through the sizes and create a media template 17 | const media = Object.keys(sizes).reduce((accumulator, label) => { 18 | // use em in breakpoints to work properly cross-browser and support users 19 | // changing their browsers font-size: https://zellwk.com/blog/media-query-units/ 20 | const emSize = sizes[label] / 16 21 | accumulator[label] = (...args) => css` 22 | @media (max-width: ${emSize}em) { 23 | ${css(...args)}; 24 | } 25 | ` 26 | return accumulator 27 | }, {}) 28 | 29 | export default media 30 | -------------------------------------------------------------------------------- /src/styles/theme.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Make all colors normal hex (6 letters) for consistent 3 | * transparency usage across the app 4 | * 5 | * ex ${accent}77 6 | */ 7 | 8 | const constant = { 9 | font: '#000000', 10 | offFont: '#ebebeb', 11 | fontInvert: '#ffffff', 12 | // itemHoverShadow: "0px 4px 25px rgba(0, 0, 0, 0.15)" 13 | 14 | pastel: { 15 | red: '#e57373', 16 | orange: '#ffb74d', 17 | yellow: '#ffe14d', 18 | green: '#81c784', 19 | blue: '#64b5f6', 20 | purple: '#b6aaf0', 21 | magenta: '#bb91eb', 22 | pink: '#f2ace7', 23 | cyan: '#aaf0d7' 24 | }, 25 | muted: { 26 | red: '#ab5757', 27 | orange: '#c78f3e', 28 | yellow: '#f5d63d', 29 | green: '#609663', 30 | blue: '#4780ad', 31 | purple: '#7971a3', 32 | magenta: '#8164a3', 33 | pink: '#c484ba', 34 | cyan: '#5aada2' 35 | } 36 | } 37 | 38 | export const bitblue = { 39 | ...constant, 40 | accent: '#007bed', 41 | accentVariant: '#9acfff', //86c5ff 42 | bg: '#0a192f', 43 | bgVariant: '#172A45', 44 | bgPage: '#f5faff' 45 | } 46 | 47 | export const chakraTheme = { 48 | ...constant, 49 | accent: '#007bed', 50 | accentVariant: '#9acfff', //86c5ff 51 | bg: '#0a192f', 52 | bgVariant: '#172A45', 53 | bgPage: '#f5faff' 54 | } 55 | 56 | export const palepink = { 57 | ...constant, 58 | accent: '#db7093', 59 | accentVariant: '#ffb5cd', 60 | bg: '#320b18', 61 | bgVariant: '#451726', 62 | bgPage: '#fff5ff' 63 | } 64 | 65 | export const orange = { 66 | ...constant, 67 | accent: '#ED6800', 68 | accentVariant: '#FFC69A', 69 | bg: '#2F190A', 70 | bgVariant: '#462B18', 71 | bgPage: '#FFF9F4' 72 | } 73 | 74 | export const black = { 75 | ...constant, 76 | accent: '#565656', 77 | accentVariant: '#787878', 78 | bg: '#121212', 79 | bgVariant: '#232323', 80 | bgPage: '#efefef' 81 | } 82 | 83 | export default bitblue 84 | -------------------------------------------------------------------------------- /src/utils/DataStructures.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Implemented using arrays, use case is simple so who needs efficiency 3 | * @param {array} stack 4 | */ 5 | export function SafeStack(stack) { 6 | this.copy = () => new SafeStack([...stack]) 7 | this.push = item => { 8 | if (item && !this.has(item)) stack.push(item) 9 | return this 10 | } 11 | this.pop = item => { 12 | if (!this.isEmpty()) { 13 | if (this.peek() === item) stack.pop() 14 | else return stack.filter(i => i !== item) 15 | } 16 | return this 17 | } 18 | this.has = item => stack.includes(item) 19 | this.peek = () => (this.isEmpty() ? 'empty stack' : stack[stack.length - 1]) 20 | this.isEmpty = () => stack.length === 0 21 | this.get = () => stack // use only for testing 22 | } 23 | 24 | /** 25 | * Implemented using arrays, same as above 26 | * @param {array} queue 27 | */ 28 | export function SafeQueue(queue) { 29 | this.copy = () => new SafeQueue([...queue]) 30 | this.enqueue = item => { 31 | if (item && !this.has(item)) queue.push(item) 32 | return this 33 | } 34 | this.dequeue = item => { 35 | if (!this.isEmpty()) { 36 | if (this.front() === item) queue.shift() 37 | else return queue.filter(i => i !== item) 38 | } 39 | return this 40 | } 41 | this.has = item => queue.includes(item) 42 | this.front = () => (this.isEmpty() ? 'empty queue' : queue[0]) 43 | this.isEmpty = () => queue.length === 0 44 | this.get = () => queue // use only for testing 45 | } 46 | -------------------------------------------------------------------------------- /src/utils/customHooks.js: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from 'react' 2 | 3 | export const useDidUpdateEffect = (fn, dependencies, cleanup) => { 4 | const didMountRef = useRef(false) 5 | 6 | useEffect(() => { 7 | if (didMountRef.current) fn() 8 | else didMountRef.current = true 9 | return cleanup 10 | }, dependencies) // eslint-disable-line react-hooks/exhaustive-deps 11 | } 12 | 13 | // modified from https://stackoverflow.com/questions/41004631/trace-why-a-react-component-is-re-rendering 14 | export const useTraceUpdate = props => { 15 | const prev = usePreviousSafe(props) 16 | useEffect(() => { 17 | const changedProps = Object.entries(props).reduce((ps, [k, v]) => { 18 | if (prev[k] !== v) { 19 | ps[k] = [prev[k], v] 20 | } 21 | return ps 22 | }, {}) 23 | if (Object.keys(changedProps).length > 0) { 24 | console.log('Changed props:', changedProps) 25 | } 26 | }) 27 | } 28 | 29 | export const usePureTraceUpdate = props => { 30 | const prev = usePreviousSafe(props) 31 | const changedProps = Object.entries(props).reduce((ps, [k, v]) => { 32 | if (prev[k] !== v) { 33 | ps[k] = [prev[k], v] 34 | } 35 | return ps 36 | }, {}) 37 | console.log('Changed props:', changedProps) 38 | } 39 | 40 | export const usePrevious = value => { 41 | const ref = useRef() 42 | useEffect(() => { 43 | ref.current = value 44 | }) 45 | return ref.current 46 | } 47 | 48 | export const usePreviousSafe = value => { 49 | const ref = useRef(value) 50 | useEffect(() => { 51 | ref.current = value 52 | }) 53 | return ref.current 54 | } 55 | -------------------------------------------------------------------------------- /src/utils/objUtils.js: -------------------------------------------------------------------------------- 1 | export const objectArrayToObject = array => 2 | array.reduce((acc, item) => ({ ...acc, ...item }), {}) 3 | 4 | export const objArrayClone = objArray => { 5 | return objArray.map((_, i) => { 6 | return { ...objArray[i] } 7 | }) 8 | } 9 | 10 | // export const iterateNodes = (obj, callback, nestLevel = Infinity) => { 11 | // if (!obj) return undefined 12 | // if (nestLevel === 0) return 13 | 14 | // for (let property in obj) { 15 | // if (obj.hasOwnProperty(property) && obj[property] != null) { 16 | // if (obj[property].constructor === Object) { 17 | // iterateNodes(obj[property], callback, nestLevel - 1) 18 | // callback(obj[property]) 19 | // } else if (obj[property].constructor === Array) { 20 | // for (let i = 0; i < obj[property].length; i++) { 21 | // iterateNodes(obj[property][i], callback, nestLevel - 1) 22 | // callback(obj[property][i]) 23 | // } 24 | // } else { 25 | // // console.log(obj[property]) 26 | // } 27 | // } 28 | // } 29 | // return obj 30 | // } 31 | --------------------------------------------------------------------------------