├── .gitignore ├── README.md ├── craco.config.js ├── package.json ├── public ├── favicon.ico ├── index.html ├── logo.png ├── logo192.png ├── logo512.png ├── manifest.json └── robots.txt ├── src ├── App.js ├── App.test.js ├── components │ ├── IDE.css │ ├── IDE.js │ ├── Preview.js │ └── auth │ │ └── Auth0.js ├── images │ ├── icons │ │ ├── close.png │ │ ├── mute.svg │ │ ├── phone.svg │ │ ├── right-arrow.png │ │ ├── run.svg │ │ ├── up-arrow.svg │ │ └── video.svg │ └── logo.svg ├── index.css ├── index.js ├── logo.svg ├── reportWebVitals.js └── setupTests.js ├── tailwind.config.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Getting Started with Create React App 2 | 3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `yarn start` 10 | 11 | Runs the app in the development mode.\ 12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 13 | 14 | The page will reload if you make edits.\ 15 | You will also see any lint errors in the console. 16 | 17 | ### `yarn test` 18 | 19 | Launches the test runner in the interactive watch mode.\ 20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 21 | 22 | ### `yarn build` 23 | 24 | Builds the app for production to the `build` folder.\ 25 | It correctly bundles React in production mode and optimizes the build for the best performance. 26 | 27 | The build is minified and the filenames include the hashes.\ 28 | Your app is ready to be deployed! 29 | 30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 31 | 32 | ### `yarn eject` 33 | 34 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 35 | 36 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 37 | 38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 39 | 40 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 41 | 42 | ## Learn More 43 | 44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 45 | 46 | To learn React, check out the [React documentation](https://reactjs.org/). 47 | 48 | ### Code Splitting 49 | 50 | This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting) 51 | 52 | ### Analyzing the Bundle Size 53 | 54 | This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size) 55 | 56 | ### Making a Progressive Web App 57 | 58 | This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app) 59 | 60 | ### Advanced Configuration 61 | 62 | This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration) 63 | 64 | ### Deployment 65 | 66 | This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment) 67 | 68 | ### `yarn build` fails to minify 69 | 70 | This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify) 71 | -------------------------------------------------------------------------------- /craco.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | style: { 3 | postcss: { 4 | plugins: [ 5 | require('tailwindcss'), 6 | require('autoprefixer'), 7 | ], 8 | }, 9 | }, 10 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "codecolab-frontend", 3 | "version": "0.3.0", 4 | "private": true, 5 | "dependencies": { 6 | "@auth0/auth0-react": "^1.6.0", 7 | "@chakra-ui/react": "^1.6.7", 8 | "@craco/craco": "^6.2.0", 9 | "@emotion/react": "^11", 10 | "@emotion/styled": "^11", 11 | "@iconify/icons-bi": "^1.1.6", 12 | "@iconify/icons-fluent": "^1.1.23", 13 | "@iconify/react": "^3.0.0", 14 | "@testing-library/jest-dom": "^5.11.4", 15 | "@testing-library/react": "^11.1.0", 16 | "@testing-library/user-event": "^12.1.10", 17 | "axios": "^0.21.1", 18 | "codemirror": "^5.62.2", 19 | "framer-motion": "^4", 20 | "iconify": "^1.3.0", 21 | "peerjs": "^1.3.2", 22 | "react": "^17.0.2", 23 | "react-circular-progressbar": "^2.0.4", 24 | "react-codemirror2": "^7.2.1", 25 | "react-dom": "^17.0.2", 26 | "react-ga": "^3.3.0", 27 | "react-router": "^5.2.0", 28 | "react-router-dom": "^5.2.0", 29 | "react-scripts": "4.0.3", 30 | "socket.io-client": "^4.1.3", 31 | "web-vitals": "^1.0.1" 32 | }, 33 | "scripts": { 34 | "start": "craco start", 35 | "build": "craco build", 36 | "test": "craco test", 37 | "eject": "react-scripts eject" 38 | }, 39 | "eslintConfig": { 40 | "extends": [ 41 | "react-app", 42 | "react-app/jest" 43 | ] 44 | }, 45 | "browserslist": { 46 | "production": [ 47 | ">0.2%", 48 | "not dead", 49 | "not op_mini all" 50 | ], 51 | "development": [ 52 | "last 1 chrome version", 53 | "last 1 firefox version", 54 | "last 1 safari version" 55 | ] 56 | }, 57 | "devDependencies": { 58 | "autoprefixer": "^9.8.6", 59 | "postcss": "^7.0.36", 60 | "tailwindcss": "npm:@tailwindcss/postcss7-compat@^2.2.7" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vivek9patel/CodeConnect-frontend/c23db9879235258e8b8d18dfbaa299c2fe7dd6b7/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | CodeConnect - an online collaborative code editor 15 | 16 | 17 | 18 |
19 |
20 | 21 | 22 | -------------------------------------------------------------------------------- /public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vivek9patel/CodeConnect-frontend/c23db9879235258e8b8d18dfbaa299c2fe7dd6b7/public/logo.png -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vivek9patel/CodeConnect-frontend/c23db9879235258e8b8d18dfbaa299c2fe7dd6b7/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vivek9patel/CodeConnect-frontend/c23db9879235258e8b8d18dfbaa299c2fe7dd6b7/public/logo512.png -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react'; 2 | import IDE from "./components/IDE"; 3 | // import { Login, Logout } from "./components/auth/Auth0"; 4 | import { useAuth0 } from '@auth0/auth0-react' 5 | import { Tooltip, Avatar } from "@chakra-ui/react" 6 | import { Icon } from '@iconify/react'; 7 | import axios from 'axios'; 8 | import { v4 as uuidV4 } from 'uuid'; 9 | import ReactGA from 'react-ga'; 10 | import runIcon from './images/icons/run.svg'; 11 | import whiteboard24Regular from '@iconify/icons-fluent/whiteboard-24-regular'; 12 | import Preview from './components/Preview'; 13 | 14 | 15 | 16 | function App() { 17 | const [textEditor, setTextEditor] = useState('input'); 18 | const [processing, setProcessing] = useState(false); 19 | const [percentageStage, setPercentageStage] = useState(0); 20 | const [selected, setSelected] = useState('python'); 21 | const [input, setInput] = useState(''); 22 | const [output, setOutput] = useState(''); 23 | const [python, setpython] = useState(''); 24 | const [cpp, setcpp] = useState(''); 25 | const [java, setjava] = useState(''); 26 | const [js, setjs] = useState(''); 27 | const [pascal, setpascal] = useState(''); 28 | const [perl, setperl] = useState(''); 29 | const [php, setphp] = useState(''); 30 | const [ruby, setruby] = useState(''); 31 | const [modal, setModal] = useState(false); 32 | const [docId, setDocId] = useState(null); 33 | const [isDocId, setIsDocId] = useState(false); 34 | const { isAuthenticated, user } = useAuth0(); 35 | const [isInputBoxShown, setisInputBoxShown] = useState(true); 36 | 37 | 38 | useEffect(() => { 39 | if (window.location.pathname === "/") { 40 | const uid = uuidV4(); 41 | setDocId(uid) 42 | setIsDocId(false); 43 | } 44 | else { 45 | setDocId(window.location.pathname.split('/')[1]) 46 | setIsDocId(true); 47 | } 48 | if (isAuthenticated) { 49 | ReactGA.event({ 50 | category: `user.logged`, 51 | action: `Login`, 52 | label: `${user.email}` 53 | }); 54 | } 55 | // eslint-disable-next-line 56 | }, []); 57 | 58 | 59 | let statusLoop = null; 60 | 61 | const runCode = () => { 62 | 63 | ReactGA.event({ 64 | category: `button.clicked`, 65 | action: `Run Code`, 66 | lang: `${selected}` 67 | }); 68 | 69 | setOutput('') 70 | setTextEditor('output'); 71 | setProcessing(true); 72 | setPercentageStage(10); 73 | setisInputBoxShown(false); 74 | 75 | var lang = selected; 76 | const backend_url = process.env.REACT_APP_BACKEND_ENDPOINT_URL + "/runcode"; 77 | var source = "print(1)"; 78 | if (lang === "python") { 79 | source = python; 80 | } 81 | else if (lang === "cpp") { 82 | source = cpp; 83 | } 84 | else if (lang === "java") { 85 | source = java; 86 | } 87 | else if (lang === "javascript") { 88 | source = js; 89 | } 90 | else if (lang === "pascal") { 91 | source = pascal; 92 | } 93 | else if (lang === "perl") { 94 | source = perl; 95 | } 96 | else if (lang === "php") { 97 | source = php; 98 | } 99 | else if (lang === "ruby") { 100 | source = ruby; 101 | } 102 | if (lang === "javascript") lang = "javascript_node"; 103 | var data = { 104 | "lang": lang.toUpperCase(), 105 | "source": source, 106 | "input": input, 107 | "memory_limit": 243232, 108 | "time_limit": 5, 109 | "context": "{'id': 213121}", 110 | "callback": "https://client.com/callback/" 111 | } 112 | 113 | 114 | var status; 115 | var raw = JSON.stringify(data); 116 | 117 | var requestOptions = { 118 | method: 'POST', 119 | headers: { 120 | 'Content-Type': 'application/json', 121 | }, 122 | body: raw, 123 | redirect: 'follow' 124 | }; 125 | fetch(backend_url, requestOptions) 126 | .then((res) => res.json()) 127 | .then((data) => { 128 | status = data.status_update_url; 129 | const url = backend_url + "?url=" + status; 130 | 131 | setPercentageStage(25) 132 | 133 | statusLoop = setInterval(() => { 134 | fetch(url, { 135 | method: 'GET' 136 | }) 137 | .then((res) => res.json()) 138 | .then((data) => { 139 | setPercentageStage(75) 140 | // console.log(data); 141 | if (data.result.compile_status === 'OK') { 142 | if (data.result.run_status.status === 'AC') { 143 | getOutput(data.result.run_status.output); 144 | clearInterval(statusLoop); 145 | } 146 | else if (data.result.run_status.status === 'OLE') { 147 | setOutput(data.result.run_status.status_detail); 148 | setProcessing(false); 149 | clearInterval(statusLoop); 150 | } 151 | else if (data.result.run_status.status !== 'NA') { 152 | setOutput(data.result.run_status.stderr); 153 | setProcessing(false); 154 | clearInterval(statusLoop); 155 | } 156 | } 157 | else { 158 | setOutput(data.result.compile_status); 159 | setProcessing(false); 160 | clearInterval(statusLoop); 161 | return; 162 | } 163 | }) 164 | .catch(e => { 165 | setProcessing(false); 166 | clearInterval(statusLoop); 167 | console.log(e) 168 | }); 169 | }, 2000) 170 | }).catch(e => { 171 | console.log(e) 172 | });; 173 | 174 | }; 175 | 176 | const getOutput = (link) => { 177 | axios.get(link).then((res) => { 178 | setPercentageStage(100) 179 | setProcessing(false); 180 | setOutput(res.data); 181 | }).catch((err) => { 182 | console.log(err); 183 | }) 184 | } 185 | 186 | const toggleModal = () => { 187 | ReactGA.event({ 188 | category: `button.clicked`, 189 | action: `Whiteboard ${modal ? "Opened" : "Closed"}`, 190 | }); 191 | setModal(!modal); 192 | } 193 | 194 | return ( 195 |
196 | { 197 | isDocId ? 198 | <> 199 |
200 | 201 | 202 | : 203 | 204 | } 205 |
206 | ); 207 | } 208 | 209 | export default App; 210 | 211 | function Header({ runCode, toggleModal, isAuthenticated, isInputBoxShown, setisInputBoxShown }) { 212 | const [toolTip, showToolTip] = useState(false); 213 | const [userInfo, setUserInfo] = useState({}); 214 | const [isUserPresent, setIsUserPresent] = useState(false); 215 | 216 | useEffect(() => { 217 | try { 218 | const user = JSON.parse(localStorage.getItem('user')); 219 | setUserInfo(user); 220 | if (user) { 221 | setIsUserPresent(true); 222 | } 223 | else setIsUserPresent(false); 224 | } 225 | catch (e) { 226 | setIsUserPresent(false); 227 | } 228 | }, []) 229 | 230 | const toggleInputBox = () => { 231 | ReactGA.event({ 232 | category: `button.clicked`, 233 | action: `Input Box ${isInputBoxShown ? "Closed" : "Opened"}`, 234 | }); 235 | setisInputBoxShown(!isInputBoxShown); 236 | } 237 | 238 | return ( 239 |
240 |
241 |
242 | codeconnect logo 243 | CodeConnect 244 |
245 |
246 |
247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 258 | 259 | {/* { 260 | isAuthenticated ? 261 | : 262 | 263 | } */} 264 | { 265 | isUserPresent && 266 | 267 | 268 | 269 | } 270 |
271 | { 272 | isAuthenticated && 273 | { showToolTip(true) }} onMouseLeave={() => { showToolTip(false) }} className="h-7 w-7 rounded-full" src={userInfo.picture} alt="user icon" /> 274 | } 275 | { 276 | toolTip && isAuthenticated && 277 |
{userInfo.email}
278 | } 279 |
280 |
281 |
282 | ) 283 | } -------------------------------------------------------------------------------- /src/App.test.js: -------------------------------------------------------------------------------- 1 | import { render, screen } from '@testing-library/react'; 2 | import App from './App'; 3 | 4 | test('renders learn react link', () => { 5 | render(); 6 | const linkElement = screen.getByText(/learn react/i); 7 | expect(linkElement).toBeInTheDocument(); 8 | }); 9 | -------------------------------------------------------------------------------- /src/components/IDE.css: -------------------------------------------------------------------------------- 1 | 2 | .colors { 3 | position: fixed; 4 | } 5 | 6 | .color { 7 | display: inline-block; 8 | height: 48px; 9 | width: 48px; 10 | } 11 | 12 | .color.black { background-color: black; } 13 | .color.red { background-color: white; } 14 | -------------------------------------------------------------------------------- /src/components/IDE.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState, useRef, useCallback } from 'react'; 2 | import { io } from 'socket.io-client'; 3 | import { Controlled as CodeMirror } from 'react-codemirror2'; 4 | import ReactGA from 'react-ga'; 5 | import 'codemirror/lib/codemirror.css'; 6 | import 'codemirror/theme/material.css'; 7 | import 'codemirror/theme/dracula.css'; 8 | import 'codemirror/theme/panda-syntax.css'; 9 | import 'codemirror/mode/htmlmixed/htmlmixed'; 10 | import 'codemirror/mode/css/css'; 11 | import 'codemirror/mode/javascript/javascript'; 12 | import 'codemirror/mode/clike/clike'; 13 | import 'codemirror/mode/python/python'; 14 | import 'codemirror/mode/javascript/javascript'; 15 | import 'codemirror/mode/pascal/pascal'; 16 | import 'codemirror/mode/perl/perl'; 17 | import 'codemirror/mode/php/php'; 18 | import 'codemirror/mode/ruby/ruby'; 19 | 20 | import Peer from 'peerjs'; 21 | import closeIcon from '../images/icons/close.png'; 22 | import muteIcon from '../images/icons/mute.svg'; 23 | import videoIcon from '../images/icons/video.svg'; 24 | import phoneIcon from '../images/icons/phone.svg'; 25 | import { Icon } from '@iconify/react'; 26 | import eraser24Filled from '@iconify/icons-fluent/eraser-24-filled'; 27 | import penFill from '@iconify/icons-bi/pen-fill'; 28 | import { Tabs, TabList, TabPanels, Tab, TabPanel, Skeleton, Progress, Tag } from "@chakra-ui/react" 29 | import 'react-circular-progressbar/dist/styles.css'; 30 | 31 | 32 | export default function IDE({ docId, modal, toggleModal, cpp, setcpp, java, setjava, js, setjs, php, setphp, pascal, setpascal, perl, setperl, ruby, setruby, python, setpython, input, setInput, selected, setSelected, output, textEditor, setTextEditor, processing, percentageStage, isInputBoxShown, setOutput }) { 33 | const [socket, setSocket] = useState(null); 34 | 35 | const [peer, setPeer] = useState(null); 36 | const userName = 'smit' 37 | const videoGrid = document.getElementById('video-grid'); 38 | const myVideo = document.createElement('video'); 39 | const myVideoCont = document.createElement('div'); 40 | myVideoCont.appendChild(myVideo); 41 | myVideoCont.className = "videoContainer rounded mb-4" 42 | myVideo.muted = true; 43 | const [myStream, setMystream] = useState(null); 44 | const peers = {}; 45 | const colorsRef = useRef(null); 46 | const [userId, setUserId] = useState(null); 47 | const [myvideoon, setMyvideoon] = useState(true); 48 | const [pencilColor, setPencilColor] = useState('#000000'); 49 | 50 | 51 | useEffect(() => { 52 | ReactGA.pageview('IDE-screen'); 53 | var TempSocket = io(process.env.REACT_APP_BACKEND_ENDPOINT_URL); 54 | setSocket(TempSocket); 55 | const peer = new Peer(undefined, { 56 | host: process.env.REACT_APP_BACKEND_ENDPOINT, 57 | port: 443, 58 | path: '/' 59 | }); 60 | setPeer(peer); 61 | 62 | return () => { 63 | TempSocket.disconnect(); 64 | }; 65 | }, []); 66 | 67 | 68 | useEffect(() => { 69 | if (socket == null) return; 70 | socket.emit('get-document', docId); 71 | socket.once('load-document', (data) => { 72 | setcpp(data.cpp); 73 | setjava(data.java); 74 | setpython(data.python); 75 | setjs(data.js); 76 | setpascal(data.pascal); 77 | setphp(data.php); 78 | setperl(data.perl); 79 | setruby(data.ruby); 80 | setInput(data.input); 81 | setOutput(data.output); 82 | }); 83 | // eslint-disable-next-line 84 | }, [socket, docId]); 85 | 86 | 87 | 88 | useEffect(() => { 89 | if (socket === null) return; 90 | var updateC = (delta) => { 91 | setpython(delta.python); 92 | setjava(delta.java); 93 | setcpp(delta.cpp); 94 | setInput(delta.input); 95 | setOutput(delta.output); 96 | } 97 | socket.on('receive-changes', updateC); 98 | return () => { 99 | socket.off('receive-changes', updateC); 100 | } 101 | // eslint-disable-next-line 102 | }, [socket, cpp, java, python, input, output]); 103 | 104 | useEffect(() => { 105 | if (socket === null) return; 106 | 107 | var data = { 108 | 'cpp': cpp, 109 | 'java': java, 110 | 'python': python, 111 | 'js': js, 112 | 'perl': perl, 113 | 'php': php, 114 | 'ruby': ruby, 115 | 'pascal': pascal, 116 | 'input': input, 117 | 'output': output 118 | }; 119 | 120 | var savetodb = setTimeout(() => { socket.emit('save-document', data); socket.emit('changes', data); }, 2000); 121 | var updateC = (delta) => { 122 | setpython(delta.python); 123 | setjava(delta.java); 124 | setcpp(delta.cpp); 125 | setjs(delta.js); 126 | setpascal(delta.pascal); 127 | setphp(delta.php); 128 | setperl(delta.perl); 129 | setruby(delta.ruby); 130 | } 131 | socket.on('receive-changes', updateC); 132 | return () => { 133 | socket.off('receive-changes', updateC); 134 | clearTimeout(savetodb); 135 | }; 136 | 137 | }, [socket, cpp, java, js, pascal, php, perl, ruby, python, input, output]); 138 | 139 | 140 | 141 | function addVideoStream(videoCont, video, stream) { 142 | video.srcObject = stream; 143 | video.addEventListener('loadedmetadata', () => { 144 | video.play(); 145 | }) 146 | videoGrid.append(videoCont); 147 | }; 148 | 149 | useEffect(() => { 150 | if (socket == null) return; 151 | 152 | navigator.mediaDevices.getUserMedia({ 153 | video: true, 154 | audio: true 155 | }).then(stream => { 156 | addVideoStream(myVideoCont, myVideo, stream); 157 | setMyvideoon(true); 158 | setMystream(stream); 159 | peer.on('call', call => { 160 | call.answer(stream); 161 | const video = document.createElement('video'); 162 | const videoCont = document.createElement('div'); 163 | videoCont.appendChild(video); 164 | videoCont.id = call.peer; 165 | videoCont.dataset.name = call.metadata.name; 166 | videoCont.className = "videoContainer rounded mb-4"; 167 | call.on('stream', (anotherUserVideoStream) => { 168 | addVideoStream(videoCont, video, anotherUserVideoStream); 169 | }); 170 | 171 | call.on('close', () => { 172 | video.remove(); 173 | videoCont.remove(); 174 | }); 175 | peers[call.peer] = call; 176 | }); 177 | 178 | socket.on('user-connected', (userId) => { 179 | const call = peer.call(userId, stream, { metadata: { name: userName } }); 180 | const video = document.createElement('video') 181 | const videoCont = document.createElement('div'); 182 | videoCont.appendChild(video); 183 | videoCont.id = userId; 184 | videoCont.dataset.name = call.metadata.name; 185 | videoCont.className = "videoContainer rounded mb-4"; 186 | call.on('stream', (anotherUserVideoStream) => { 187 | addVideoStream(videoCont, video, anotherUserVideoStream); 188 | }); 189 | 190 | call.on('close', () => { 191 | video.remove(); 192 | videoCont.remove(); 193 | }); 194 | peers[userId] = call; 195 | }); 196 | 197 | 198 | }); 199 | 200 | socket.on('user-disconnected', userId => { 201 | if (peers[userId]) peers[userId].close(); 202 | }); 203 | 204 | peer.on('open', (id) => { 205 | setUserId(id); 206 | myVideoCont.id = id; 207 | myVideoCont.dataset.name = userName; 208 | socket.emit('join-room', docId, id); 209 | }); 210 | // eslint-disable-next-line 211 | }, [socket, docId, peer]); 212 | 213 | const addVideo = useCallback(() => { 214 | if (socket == null) return; 215 | 216 | navigator.mediaDevices.getUserMedia({ 217 | video: true, 218 | audio: true 219 | }).then(stream => { 220 | addVideoStream(myVideoCont, myVideo, stream); 221 | setMyvideoon(true); 222 | setMystream(stream); 223 | replaceStream(stream); 224 | peer.on('call', call => { 225 | call.answer(stream); 226 | const video = document.createElement('video'); 227 | const videoCont = document.createElement('div'); 228 | videoCont.className = "videoContainer rounded mb-4" 229 | videoCont.appendChild(video); 230 | videoCont.id = call.peer; 231 | videoCont.dataset.name = call.metadata.name; 232 | call.on('stream', (anotherUserVideoStream) => { 233 | addVideoStream(videoCont, video, anotherUserVideoStream); 234 | }); 235 | 236 | call.on('close', () => { 237 | video.remove(); 238 | videoCont.remove(); 239 | }); 240 | peers[call.peer] = call; 241 | }); 242 | 243 | socket.on('user-connected', (userId) => { 244 | const call = peer.call(userId, stream, { metadata: { name: userName } }); 245 | const video = document.createElement('video') 246 | const videoCont = document.createElement('div'); 247 | videoCont.className = "videoContainer rounded mb-4" 248 | videoCont.appendChild(video); 249 | videoCont.id = userId; 250 | videoCont.dataset.name = call.metadata.name; 251 | call.on('stream', (anotherUserVideoStream) => { 252 | addVideoStream(videoCont, video, anotherUserVideoStream); 253 | }); 254 | 255 | call.on('close', () => { 256 | video.remove(); 257 | videoCont.remove(); 258 | }); 259 | peers[userId] = call; 260 | }); 261 | 262 | 263 | }); 264 | 265 | socket.on('user-disconnected', userId => { 266 | if (peers[userId]) peers[userId].close(); 267 | }); 268 | 269 | peer.on('open', (id) => { 270 | setUserId(id); 271 | myVideoCont.id = id; 272 | myVideoCont.dataset.name = userName; 273 | 274 | socket.emit('join-room', docId, id); 275 | }); 276 | // eslint-disable-next-line 277 | }, [socket, docId, peer]); 278 | 279 | 280 | 281 | const muteMic = () => { 282 | myStream.getAudioTracks()[0].enabled = !(myStream.getAudioTracks()[0].enabled); 283 | const toggledVideo = document.getElementById(userId); 284 | if (myStream.getAudioTracks()[0].enabled) { 285 | toggledVideo.classList.remove("audio-off"); 286 | } 287 | else { 288 | toggledVideo.classList.add("audio-off"); 289 | } 290 | socket.emit('toggled', userId, myStream.getVideoTracks()[0].enabled, myStream.getAudioTracks()[0].enabled); 291 | } 292 | 293 | const muteCam = () => { 294 | 295 | if (socket === null) return; 296 | if (myStream && myvideoon) { 297 | myStream.getVideoTracks().forEach((track) => { 298 | if (track.kind === 'video') { 299 | track.stop(); 300 | } 301 | }); 302 | // console.log(myStream.getVideoTracks()[0].enabled); 303 | setMyvideoon(false); 304 | } 305 | else { 306 | addVideo(); 307 | setMyvideoon(true); 308 | } 309 | myStream.getVideoTracks()[0].enabled = !(myStream.getVideoTracks()[0].enabled); 310 | try { 311 | const toggledVideo = document.getElementById(userId); 312 | if (myStream.getVideoTracks()[0].enabled) { 313 | toggledVideo.classList.remove("video-off"); 314 | } 315 | else { 316 | toggledVideo.classList.add("video-off"); 317 | } 318 | } 319 | catch (err) { 320 | console.log(err); 321 | } 322 | 323 | 324 | // // toggle webcam tracks 325 | socket.emit('toggled', userId, myStream.getVideoTracks()[0].enabled, myStream.getAudioTracks()[0].enabled); 326 | } 327 | 328 | 329 | const replaceStream = (mediaStream) => { 330 | Object.values(peers).forEach((peer) => { 331 | peer.peerConnection?.getSenders().forEach((sender) => { 332 | if (sender.track.kind === "audio") { 333 | if (mediaStream.getAudioTracks().length > 0) { 334 | sender.replaceTrack(mediaStream.getAudioTracks()[0]); 335 | } 336 | } 337 | if (sender.track.kind === "video") { 338 | if (mediaStream.getVideoTracks().length > 0) { 339 | sender.replaceTrack(mediaStream.getVideoTracks()[0]); 340 | } 341 | } 342 | }); 343 | }) 344 | } 345 | 346 | useEffect(() => { 347 | if (socket === null) return; 348 | socket.on('received-toggled-events', (userId, video, audio) => { 349 | try { 350 | const toggledVideo = document.getElementById(userId); 351 | 352 | if (video) { 353 | toggledVideo.classList.remove("video-off"); 354 | } 355 | else { 356 | toggledVideo.classList.add("video-off"); 357 | } 358 | 359 | if (audio) { 360 | toggledVideo.classList.remove("audio-off"); 361 | } 362 | else { 363 | toggledVideo.classList.add("audio-off"); 364 | } 365 | } 366 | catch (err) { 367 | console.log(err); 368 | } 369 | }); 370 | }, [socket]); 371 | 372 | 373 | 374 | 375 | useEffect(() => { 376 | 377 | 378 | if (socket === null || colorsRef === null) return; 379 | const canvas = document.getElementById('whiteboard-canvas') 380 | const context = canvas.getContext('2d'); 381 | const colorPicker = document.getElementById('pencil-color-picker'); 382 | 383 | const colors = document.getElementsByClassName('color'); 384 | // console.log(colors, 'the colors'); 385 | // console.log(test); 386 | const current = { 387 | color: '#000000', 388 | width: 5, 389 | }; 390 | 391 | const onColorUpdate = (e) => { 392 | let objectColor; 393 | for (let i = 0; i < e.path.length; i++) { 394 | if (e.path[i].dataset.color) { 395 | if (e.path[i].dataset.color === "white") objectColor = "#ffffff" 396 | else objectColor = pencilColor; 397 | break; 398 | } 399 | } 400 | current.color = objectColor; 401 | if (current.color !== '#ffffff') current.width = 5; 402 | else current.width = 25; 403 | }; 404 | 405 | const onPencilColorChange = (e) => { 406 | setPencilColor(e.target.value); 407 | current.color = e.target.value; 408 | if (current.color !== '#ffffff') current.width = 5; 409 | else current.width = 25; 410 | } 411 | 412 | for (let i = 0; i < colors.length; i++) { 413 | colors[i].removeEventListener('click', onColorUpdate); 414 | colorPicker.removeEventListener('change', onPencilColorChange); 415 | colors[i].addEventListener('click', onColorUpdate, false); 416 | colorPicker.addEventListener('change', onPencilColorChange, false); 417 | } 418 | let drawing = false; 419 | 420 | 421 | const drawLine = (x0, y0, x1, y1, color, width, emit) => { 422 | context.beginPath(); 423 | context.strokeStyle = color; 424 | context.moveTo(x0, y0); 425 | context.lineTo(x1, y1); 426 | console.log(color); 427 | console.log(context.strokeStyle); 428 | context.lineWidth = width; 429 | context.stroke(); 430 | context.closePath(); 431 | 432 | if (!emit) { return; } 433 | const w = canvas.width; 434 | const h = canvas.height; 435 | // console.log(w, h, window.width, window.height); 436 | 437 | 438 | setPencilColor(current.color); 439 | 440 | socket.emit('drawing', { 441 | x0: x0 / w, 442 | y0: y0 / h, 443 | x1: x1 / w, 444 | y1: y1 / h, 445 | color: current.color, 446 | width 447 | }); 448 | }; 449 | 450 | 451 | 452 | const onMouseDown = (e) => { 453 | 454 | // console.log(drawing + ' d'); 455 | drawing = true; 456 | current.x = e.clientX || e.touches[0].clientX; 457 | current.y = e.clientY || e.touches[0].clientY; 458 | }; 459 | 460 | const onMouseMove = (e) => { 461 | if (!drawing) { return; } 462 | drawLine(current.x, current.y, e.clientX || e.touches[0].clientX, e.clientY || e.touches[0].clientY, current.color, current.width, true); 463 | current.x = e.clientX || e.touches[0].clientX; 464 | current.y = e.clientY || e.touches[0].clientY; 465 | }; 466 | 467 | const onMouseUp = (e) => { 468 | 469 | if (!drawing) { return; } 470 | drawing = false; 471 | drawLine(current.x, current.y, e.clientX || e.touches[0].clientX, e.clientY || e.touches[0].clientY, current.color, current.width, true); 472 | }; 473 | const throttle = (callback, delay) => { 474 | let previousCall = new Date().getTime(); 475 | return function () { 476 | const time = new Date().getTime(); 477 | 478 | if ((time - previousCall) >= delay) { 479 | previousCall = time; 480 | callback.apply(null, arguments); 481 | } 482 | }; 483 | }; 484 | 485 | 486 | canvas.addEventListener('mousedown', onMouseDown, false); 487 | canvas.addEventListener('mouseup', onMouseUp, false); 488 | canvas.addEventListener('mouseout', onMouseUp, false); 489 | canvas.addEventListener('mousemove', throttle(onMouseMove, 10), false); 490 | 491 | canvas.addEventListener('touchstart', onMouseDown, false); 492 | canvas.addEventListener('touchend', onMouseUp, false); 493 | canvas.addEventListener('touchcancel', onMouseUp, false); 494 | canvas.addEventListener('touchmove', throttle(onMouseMove, 10), false); 495 | 496 | 497 | const onResize = () => { 498 | canvas.width = window.innerWidth; 499 | canvas.height = window.innerHeight; 500 | }; 501 | 502 | window.addEventListener('resize', onResize, false); 503 | onResize(); 504 | const onDrawingEvent = (data) => { 505 | const w = canvas.width; 506 | const h = canvas.height; 507 | console.log(data.color); 508 | drawLine(data.x0 * w, data.y0 * h, data.x1 * w, data.y1 * h, data.color, data.width); 509 | } 510 | socket.on('drawing', onDrawingEvent); 511 | }, [socket]); 512 | 513 | 514 | const handleInputFileChange = () => { 515 | const input = document.getElementById('input-file-upload'); 516 | input.click(); 517 | }; 518 | 519 | const handleFileDataChange = (e) => { 520 | const file = e.target.files[0]; 521 | const reader = new FileReader(); 522 | reader.onload = (e) => { 523 | const textData = e.target.result; 524 | setInput(textData); 525 | } 526 | reader.readAsText(file); 527 | } 528 | 529 | return ( 530 | <> 531 |
532 |
533 |
534 |
535 | 536 |
537 |
538 | 539 | { 540 | 541 |
542 |
543 |
544 | 545 |
546 | { 547 | selected === 'python' && { 560 | setpython(changes); 561 | }} 562 | /> 563 | } 564 | { 565 | selected === 'cpp' && { 578 | setcpp(changes); 579 | }} 580 | /> 581 | } 582 | { 583 | selected === 'java' && { 596 | setjava(changes); 597 | }} 598 | /> 599 | } 600 | { 601 | selected === 'javascript' && { 614 | setjs(changes); 615 | }} 616 | /> 617 | } 618 | { 619 | selected === 'pascal' && { 632 | setpascal(changes); 633 | }} 634 | /> 635 | } 636 | { 637 | selected === 'ruby' && { 650 | setruby(changes); 651 | }} 652 | /> 653 | } 654 | { 655 | selected === 'php' && { 668 | setphp(changes); 669 | }} 670 | /> 671 | } 672 | { 673 | selected === 'perl' && { 686 | setperl(changes); 687 | }} 688 | /> 689 | } 690 | 691 |
692 |
693 | } 694 |
695 |
696 |
697 |
698 | 699 | 700 | {/* color picker input element */} 701 | 702 |
703 |
704 | close icon 705 |
706 | 707 | 708 |
709 |
710 |
711 | 712 | 713 | { 714 | setTextEditor("input"); 715 | }} className=" font-semibold ">Input 716 | { 717 | setTextEditor("output"); 718 | }} className=" font-semibold ">Output 719 | 720 | 721 | 722 | 724 | 725 | 726 | {processing && } 727 | 728 | 730 | 731 | 732 | 733 | 734 |
735 | 736 |
... or upload an file
737 | 738 |
739 |
740 | 741 |
742 |
743 |
744 | 745 | ) 746 | } 747 | 748 | 749 | 750 | function RightVideoPanel({ muteCam, muteMic }) { 751 | 752 | const [isMuteCam, setIsMuteCam] = useState(false) 753 | const [isMuteMic, setIsMuteMic] = useState(false) 754 | 755 | return ( 756 |
757 |
758 | 759 | People in room 760 | 761 |
762 |
763 |
764 |
765 | 771 | 777 | 782 |
783 |
784 |
785 |
786 | ) 787 | } 788 | 789 | function LanguageSelector({ language, setLanguage }) { 790 | return ( 791 | 803 | ) 804 | } 805 | 806 | function FileTabs({ files }) { 807 | return ( 808 |
809 | { 810 | files && files.map((file, index) => { 811 | return ( 812 |
813 |
814 |
815 | file icon 816 |
817 |
818 | {file.name} 819 |
820 |
821 |
822 | ) 823 | }) 824 | } 825 |
826 | ) 827 | } 828 | 829 | 830 | // function SidePanel() { 831 | // return ( 832 | //
833 | // Share Room ID 834 | //
835 | // Join Room 836 | //
837 | // Download 838 | //
839 | //
840 | // ) 841 | // } 842 | 843 | // function ShareRoomID() { 844 | // const currentURL = window.location.href; 845 | // return ( 846 | //
847 | // Share 848 | //
849 | // 850 | // {currentURL} 851 | // 852 | // navigator.clipboard.writeText(window.location.href)} className="bg-grey-standard bg-opacity-50 w-min px-3 py-1 rounded-r align-middle"> 853 | // Copy 854 | // 855 | //
856 | //
857 | // NOTE: Anyone with the link can join & edit the code 858 | //
859 | //
860 | // ) 861 | // } 862 | 863 | // function JoinRoom() { 864 | // const [input, setInput] = useState(''); 865 | // return ( 866 | //
867 | // Join 868 | //
869 | // setInput(e.target.value)} className="bg-grey-standard w-min rounded-l px-3 py-1 align-middle outline-none border-none" /> 870 | // 873 | //
874 | //
875 | // NOTE: Make sure you are entering correct URL 876 | //
877 | //
878 | // ) 879 | // } -------------------------------------------------------------------------------- /src/components/Preview.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import ReactGA from 'react-ga'; 3 | import { useAuth0 } from '@auth0/auth0-react' 4 | import { Login, Logout } from './auth/Auth0'; 5 | 6 | export default function Preview({ docId }) { 7 | const { isAuthenticated, user } = useAuth0(); 8 | useEffect(() => { 9 | ReactGA.pageview('preview-screen'); 10 | }, []) 11 | 12 | console.log('isAuthenticated', isAuthenticated, user); 13 | 14 | try { 15 | if (isAuthenticated) localStorage.setItem('user', JSON.stringify(user)); 16 | } 17 | catch (e) { 18 | console.log(e); 19 | } 20 | 21 | const joinRoomViaRoomId = () => { 22 | const roomId = document.getElementById("roomIdInput"); 23 | const roomIdValue = roomId.value; 24 | 25 | if (roomIdValue.includes("http") || roomIdValue.includes("https")) { 26 | const url = new URL(roomIdValue); 27 | const path = url.pathname; 28 | ReactGA.event({ 29 | category: `button.clicked`, 30 | action: `Join Room`, 31 | label: `from copied url` 32 | }); 33 | window.location.href = `${path}`; 34 | } 35 | else { 36 | ReactGA.event({ 37 | category: `button.clicked`, 38 | action: `Join Room`, 39 | label: `from input url` 40 | }); 41 | window.location.href = `/${roomIdValue}`; 42 | } 43 | } 44 | 45 | return ( 46 |
47 |
48 |
49 | <CodeConnect /> 50 |
51 |
52 | 59 | {/* */} 60 |
61 | 62 | 63 |
64 |
65 | { 66 | !isAuthenticated ? : 67 | } 68 | 69 |
70 |
71 |
72 |
73 | ) 74 | } 75 | -------------------------------------------------------------------------------- /src/components/auth/Auth0.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { useAuth0 } from '@auth0/auth0-react'; 3 | import ReactGA from 'react-ga'; 4 | import rightArrow from '../../images/icons/right-arrow.png' 5 | import { Tooltip } from '@chakra-ui/tooltip'; 6 | 7 | export function Login() { 8 | const { loginWithRedirect } = useAuth0(); 9 | return ( 10 |
{ 11 | ReactGA.event({ 12 | category: `button.clicked`, 13 | action: `Login`, 14 | }); 15 | loginWithRedirect() 16 | }} className=" w-full shadow-none hover:shadow-md flex justify-between duration-150 px-4 py-2 rounded-lg text-white bg-theme-teal-dark border border-transparent cursor-pointer font-semibold"> 17 | Login 18 | Right Arrow Login 19 |
20 | ) 21 | } 22 | 23 | export function Logout() { 24 | const { logout } = useAuth0(); 25 | return ( 26 |
{ 27 | ReactGA.event({ 28 | category: `button.clicked`, 29 | action: `Logout`, 30 | }); 31 | logout({ 32 | returnTo: window.location.origin 33 | }) 34 | }} className=" w-auto shadow-none hover:shadow-md flex justify-between duration-150 rounded-full text-center text-theme-teal-dark bg-white border-transparent cursor-pointer font-semibold"> 35 | 36 | Right Arrow Login 37 | 38 |
39 | ) 40 | } -------------------------------------------------------------------------------- /src/images/icons/close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vivek9patel/CodeConnect-frontend/c23db9879235258e8b8d18dfbaa299c2fe7dd6b7/src/images/icons/close.png -------------------------------------------------------------------------------- /src/images/icons/mute.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/images/icons/phone.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/images/icons/right-arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vivek9patel/CodeConnect-frontend/c23db9879235258e8b8d18dfbaa299c2fe7dd6b7/src/images/icons/right-arrow.png -------------------------------------------------------------------------------- /src/images/icons/run.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/images/icons/up-arrow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/images/icons/video.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/images/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 22 | 24 | image/svg+xml 25 | 27 | 28 | 29 | 30 | 31 | 33 | 53 | 60 | 61 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css2?family=Source+Code+Pro:wght@400;500;600;700&display=swap"); 2 | @import url("https://unpkg.com/firacode@1.205.0/distr/fira_code.css"); 3 | @tailwind base; 4 | @tailwind components; 5 | @tailwind utilities; 6 | 7 | body, 8 | html { 9 | overflow: hidden; 10 | } 11 | .CodeMirror * { 12 | font-family: "Fira Code", monospace; 13 | font-size: 20px; 14 | } 15 | 16 | .playground { 17 | height: 100%; 18 | background-color: #1e1e25; 19 | } 20 | 21 | .code-editor { 22 | height: 33%; 23 | overflow: hidden; 24 | position: relative; 25 | } 26 | 27 | .editor-header { 28 | height: 30px; 29 | content: attr(title); 30 | display: flex; 31 | align-items: center; 32 | padding-left: 20px; 33 | font-size: 18px; 34 | color: #fafafa; 35 | } 36 | 37 | .react-codemirror2 { 38 | max-height: calc(100% - 30px); 39 | overflow: auto; 40 | } 41 | 42 | .result { 43 | /* position: fixed; 44 | top: 0; 45 | right: 0; 46 | bottom: 0; 47 | left: 600px; */ 48 | /* overflow: hidden; */ 49 | } 50 | 51 | .videoContainer { 52 | height: 180px; 53 | width: 180px; 54 | } 55 | 56 | video { 57 | height: 180px !important; 58 | width: 180px; 59 | object-fit: cover; 60 | border-radius: 0.5rem; 61 | box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.3); 62 | } 63 | 64 | /* resizable panels */ 65 | 66 | .panel-container { 67 | display: flex; 68 | flex-direction: column; 69 | height: 100%; 70 | } 71 | 72 | .panel { 73 | background: white; 74 | border: 1px solid gray; 75 | } 76 | .panel:first-child { 77 | } 78 | 79 | .resizer { 80 | width: 100%; 81 | height: 8px; 82 | background: darkGray; 83 | position: relative; 84 | cursor: row-resize; 85 | flex-shrink: 0; 86 | -webkit-user-select: none; /* Chrome all / Safari all */ 87 | -moz-user-select: none; /* Firefox all */ 88 | -ms-user-select: none; /* IE 10+ */ 89 | user-select: none; /* Likely future */ 90 | } 91 | 92 | .resizer::after, 93 | .resizer::before { 94 | content: ""; 95 | border-left: 1px solid #333; 96 | position: absolute; 97 | top: 50%; 98 | transform: translateY(-100%); 99 | right: 0; 100 | display: inline-block; 101 | height: 20px; 102 | margin: 0 2px; 103 | } 104 | .resizer::before { 105 | left: 0; 106 | } 107 | 108 | /* override classes */ 109 | .CodeMirror { 110 | height: 100% !important; 111 | } 112 | 113 | .video-off { 114 | position: relative; 115 | height: 180px; 116 | width: 180px; 117 | background-color: #333; 118 | border-radius: 0.5rem; 119 | overflow: hidden; 120 | box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.3); 121 | transition: all 0.3s ease; 122 | } 123 | .video-off video { 124 | z-index: -1; 125 | } 126 | 127 | .video-off:before { 128 | content: ""; 129 | position: absolute; 130 | top: 0; 131 | left: 0; 132 | width: 100%; 133 | height: 100%; 134 | background-color: #333; 135 | opacity: 1; 136 | border-radius: 0.35rem; 137 | } 138 | 139 | .video-off:after { 140 | content: ""; 141 | position: absolute; 142 | top: 50%; 143 | left: 50%; 144 | transform: translate(-50%, -50%); 145 | width: 70%; 146 | height: 70%; 147 | background-image: url("https://source.boringavatars.com/beam/?colors=16cff7,EE9B00,0a6ab4,c8d8d7,e76f51"); 148 | background-repeat: no-repeat; 149 | background-size: cover; 150 | background-color: #333; 151 | opacity: 0.7; 152 | border-radius: 0.35rem; 153 | } 154 | 155 | .custom-shadow { 156 | box-shadow: 0px 5px 20px rgba(0, 0, 0, 0.7); 157 | } 158 | 159 | .custom-shadow-medium { 160 | box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.4); 161 | } 162 | 163 | .custom-shadow-light { 164 | box-shadow: 0px 5px 8px rgba(0, 0, 0, 0.6); 165 | } 166 | 167 | .video-grid-height { 168 | height: calc(100vh - 150px); 169 | } 170 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import reportWebVitals from './reportWebVitals'; 6 | import { Auth0Provider } from '@auth0/auth0-react'; 7 | import ReactGA from 'react-ga'; 8 | import { ChakraProvider } from "@chakra-ui/react" 9 | 10 | const trackingId = process.env.REACT_APP_TRACKING_ID 11 | const domain = process.env.REACT_APP_AUTH0_DOMAIN; 12 | const clientId = process.env.REACT_APP_AUTH0_CLIENT_ID; 13 | 14 | ReactGA.initialize(trackingId); 15 | 16 | ReactDOM.render( 17 | 18 | 23 | 24 | 25 | 26 | 27 | , 28 | document.getElementById('root') 29 | ); 30 | 31 | // If you want to start measuring performance in your app, pass a function 32 | // to log results (for example: reportWebVitals(console.log)) 33 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 34 | reportWebVitals(); 35 | -------------------------------------------------------------------------------- /src/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/reportWebVitals.js: -------------------------------------------------------------------------------- 1 | const reportWebVitals = onPerfEntry => { 2 | if (onPerfEntry && onPerfEntry instanceof Function) { 3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 4 | getCLS(onPerfEntry); 5 | getFID(onPerfEntry); 6 | getFCP(onPerfEntry); 7 | getLCP(onPerfEntry); 8 | getTTFB(onPerfEntry); 9 | }); 10 | } 11 | }; 12 | 13 | export default reportWebVitals; 14 | -------------------------------------------------------------------------------- /src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | purge: ['./src/**/*.{js,jsx,ts,tsx}', './public/index.html'], 3 | darkMode: false, // or 'media' or 'class' 4 | theme: { 5 | extend: { 6 | colors: { 7 | 'purple-standard': '#22223B', 8 | 'purple-dark': '#4A4E69', 9 | 'orange-standard': '#EE9B00', 10 | 'teal-standard': '#226d8a', 11 | 'gray-standard': '#1F2833', 12 | 'theme-dark-blue': '#05060a', 13 | 'theme-cyan': '#16cff7', 14 | 'theme-teal-light': '#14b0d5', 15 | 'theme-teal-dark': '#224f5c', 16 | 'theme-blue': '#0a6ab4', 17 | 'theme-orange': '#cd8951', 18 | 'theme-grey': '#c8d8d7', 19 | 'theme-dark-purple': '#1A202C', 20 | 'purple-6': '#753188' 21 | }, 22 | padding: { 23 | "54": '260px', 24 | } 25 | }, 26 | }, 27 | variants: { 28 | extend: {}, 29 | }, 30 | plugins: [], 31 | } --------------------------------------------------------------------------------