diff --git a/.env b/.env deleted file mode 100644 index c8587ae..0000000 --- a/.env +++ /dev/null @@ -1,18 +0,0 @@ -# Daksh Dummy Account Details: -REACT_APP_FIREBASE_API_KEY = "AIzaSyBC5RfffIrdsCG-mHVfRF0bUGiG8Sx5hGs" -REACT_APP_FIREBASE_AUTH_DOMAIN = "coderoom-aecd1.firebaseapp.com" -REACT_APP_FIREBASE_DATABASE_URL = "https://coderoom-aecd1.firebaseio.com" -REACT_APP_FIREBASE_PROJECT_ID = "coderoom-aecd1" -REACT_APP_FIREBASE_STORAGE_BUCKET = "coderoom-aecd1.appspot.com" -REACT_APP_FIREBASE_MESSAGING_SENDER_ID = "127836959872" -REACT_APP_FIREBASE_APP_ID = "1:127836959872:web:d48df173099d46aba76265" - -# Arpansac Account Details: -# REACT_APP_FIREBASE_API_KEY = "AIzaSyCyg3-TiRdd_8qP1q3TwY7EA_9iPOPpbE4", -# REACT_APP_FIREBASE_AUTH_DOMAIN = "coderoom-dev.firebaseapp.com", -# REACT_APP_FIREBASE_DATABASE_URL = "https://coderoom-dev.firebaseio.com" -# REACT_APP_FIREBASE_PROJECT_ID = "coderoom-dev" -# REACT_APP_FIREBASE_STORAGE_BUCKET = "coderoom-dev.appspot.com" -# REACT_APP_FIREBASE_MESSAGING_SENDER_ID = "694801682636" -# REACT_APP_FIREBASE_APP_ID = "1:694801682636:web:1f0b9ed0900cdd15a8f269" -# REACT_APP_FIREBASE_MEASUREMENT_ID = "G-2S2MQSLD75" \ No newline at end of file diff --git a/.firebase/hosting.YnVpbGQ.cache b/.firebase/hosting.YnVpbGQ.cache deleted file mode 100644 index 3a96652..0000000 --- a/.firebase/hosting.YnVpbGQ.cache +++ /dev/null @@ -1,17 +0,0 @@ -index.html,1584440977945,4a6e784316f8ddc0f0c81ad0182a50fdfffbbd7f1ebced0e66137dbcea2dae17 -asset-manifest.json,1584440977946,79ca0fa23d0a6a884eb7aca584e5e72ccbbc7444a12d4f3dfe9eaa1247bc06c8 -static/css/2.fba55a0b.chunk.css,1584440977992,924c60cbb1fca274040a2a2c55942950e947e8e61f69e8802b30d3476d8eacff -static/css/main.d8a4147f.chunk.css,1584440977989,bac81478843f9fbddbdff9a1d72d6be7dfb808043a4417a0cc00533b0005cdd7 -service-worker.js,1584440977945,8317aa7ad4218d9b6226af223cf1584702332500a77d3d27c592782fcab4d0d2 -precache-manifest.f72ce769d1b7e696ce0db0701320908d.js,1584440977945,d1f3ae731daa15217ebd16557b220acb73555b1c7c4e72631400600924c96c05 -static/css/main.d8a4147f.chunk.css.map,1584440977992,752746cdd91442746a96efafffd79bf909b679c2c2ba7b2d8d7be0e1c152a5ac -static/js/2.4584098a.chunk.js.LICENSE.txt,1584440977992,faf342c917769a205d99b2a900b027858d73851af8df1fa3c051475ebff59070 -static/css/2.fba55a0b.chunk.css.map,1584440977992,aef0708a2c016b136c003417e6a3741e4d99065ef70d706b0c67b676c5b1ae2f -static/js/main.8de944e0.chunk.js,1584440977946,fb673321c463c1ebc8f105050181afb889ff9b9ad3a8779d8e65dc5f91750444 -favicon_CN.ico,1584440960136,2f5e8db0721f2d0efe10f64b8099530466cedf37a9a92d57c3f7ed2b27ff8de5 -static/js/runtime-main.e13d1eff.js,1584440977990,1d2dbb0095a527cc8dcbb8746b42e14550f4543a28480671bc5821e47c552fd9 -static/js/main.8de944e0.chunk.js.map,1584440977992,d0a2215125f080537648641cc989625df5a404c5a5478cd20aa2870234adff87 -static/media/CNLOGO.c149ff8b.svg,1584440977986,bd340e9acd31b3bdf5732250d141ccb8e2e6247670834b5605a10e4e91a5fb9f -static/js/runtime-main.e13d1eff.js.map,1584440977990,1b8351c356a25c434cb32880147976019bcc9e1553fad7e6afb83bee61160729 -static/js/2.4584098a.chunk.js,1584440977993,8dba1b39dd2cf5fa8a42622c0ac5159ba41bbf1c132ba26bd7f03cc06e93f728 -static/js/2.4584098a.chunk.js.map,1584440977992,a615cf8a0bd5ad4ced78ca3f84c7a10b979aaeb3fcbe4a428a12be1f630a2268 diff --git a/.firebaserc b/.firebaserc index 865636a..c9627e4 100644 --- a/.firebaserc +++ b/.firebaserc @@ -1,5 +1,5 @@ { "projects": { - "default": "coderoom-aecd1" + "default": "coderoom-app" } } diff --git a/README.md b/README.md index a97e951..5608f3f 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,15 @@ # Coderoom | Coding Ninjas +### (http://coderoom-app.firebaseapp.com) A collaborative place for sharing code in realtime! -## To run the project: +## To run the project (locally on your machine): - Clone the project. - Run command `npm install` to install all the dependencies required. -- Run command `npm run build` in the terminal to build the project. + +#### For development mode: +- Run command `npm start` in the terminal to run the project in development mode. + +#### For production mode: +- Run command `npm run build` in the terminal to build the project for production mode. +- Initialise the project using Firebase command-line tools and run command `firebase init`. - Run command `firebase serve` in the terminal to start the Web App at `http://localhost:5000/`. \ No newline at end of file diff --git a/database.rules.json b/database.rules.json index f122d7b..b104e9c 100644 --- a/database.rules.json +++ b/database.rules.json @@ -1,5 +1,4 @@ { - /* Visit https://firebase.google.com/docs/database/security to learn more about security rules. */ "rules": { ".read": true, ".write": true diff --git a/package-lock.json b/package-lock.json index 069d56f..5e8d282 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1376,6 +1376,35 @@ "resolved": "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-0.2.35.tgz", "integrity": "sha512-7njiGBbFW0HCnuKNEJLcQt9EjfOzG8EJiXlFJwA3XfgiFxPVHmXrcF4d5yold2wfiwCwrXpeNTGZ854oRr6Hcw==" }, + "@fortawesome/fontawesome-common-types": { + "version": "0.2.27", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.27.tgz", + "integrity": "sha512-97GaByGaXDGMkzcJX7VmR/jRJd8h1mfhtA7RsxDBN61GnWE/PPCZhOdwG/8OZYktiRUF0CvFOr+VgRkJrt6TWg==" + }, + "@fortawesome/fontawesome-svg-core": { + "version": "1.2.27", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.27.tgz", + "integrity": "sha512-sOD3DKynocnHYpuw2sLPnTunDj7rLk91LYhi2axUYwuGe9cPCw7Bsu9EWtVdNJP+IYgTCZIbyARKXuy5K/nv+Q==", + "requires": { + "@fortawesome/fontawesome-common-types": "^0.2.27" + } + }, + "@fortawesome/free-solid-svg-icons": { + "version": "5.12.1", + "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.12.1.tgz", + "integrity": "sha512-k3MwRFFUhyL4cuCJSaHDA0YNYMELDXX0h8JKtWYxO5XD3Dn+maXOMrVAAiNGooUyM2v/wz/TOaM0jxYVKeXX7g==", + "requires": { + "@fortawesome/fontawesome-common-types": "^0.2.27" + } + }, + "@fortawesome/react-fontawesome": { + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.1.9.tgz", + "integrity": "sha512-49V3WNysLZU5fZ3sqSuys4nGRytsrxJktbv3vuaXkEoxv22C6T7TEG0TW6+nqVjMnkfCQd5xOnmJoZHMF78tOw==", + "requires": { + "prop-types": "^15.7.2" + } + }, "@google-cloud/paginator": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-2.0.3.tgz", @@ -1697,6 +1726,11 @@ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz", "integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==" }, + "@popperjs/core": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.1.1.tgz", + "integrity": "sha512-sLqWxCzC5/QHLhziXSCAksBxHfOnQlhPRVgPK0egEw+ktWvG75T2k+aYWVjVh9+WKeT3tlG3ZNbZQvZLmfuOIw==" + }, "@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", @@ -1751,6 +1785,20 @@ "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=" }, + "@restart/context": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@restart/context/-/context-2.1.4.tgz", + "integrity": "sha512-INJYZQJP7g+IoDUh/475NlGiTeMfwTXUEr3tmRneckHIxNolGOW9CTq83S8cxq0CgJwwcMzMJFchxvlwe7Rk8Q==" + }, + "@restart/hooks": { + "version": "0.3.22", + "resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.3.22.tgz", + "integrity": "sha512-tW0T3hP6emYNOc76/iC96rlu+f7JYLSVk/Wnn+7dj1gJUcw4CkQNLy16vx2mBLtVKsFMZ9miVEZXat8blruDHQ==", + "requires": { + "lodash": "^4.17.15", + "lodash-es": "^4.17.15" + } + }, "@sheerun/mutationobserver-shim": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@sheerun/mutationobserver-shim/-/mutationobserver-shim-0.3.2.tgz", @@ -3326,6 +3374,11 @@ "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" }, + "bootstrap": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.4.1.tgz", + "integrity": "sha512-tbx5cHubwE6e2ZG7nqM3g/FZ5PQEDMWmMGNrCUBVRPHXTJaH7CBDdsLeu3eCh3B1tzAxTnAbtmrzvWEvT2NNEA==" + }, "bowser": { "version": "1.9.4", "resolved": "https://registry.npmjs.org/bowser/-/bowser-1.9.4.tgz", @@ -10569,6 +10622,11 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" }, + "lodash-es": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.15.tgz", + "integrity": "sha512-rlrc3yU3+JNOpZ9zj5pQtxnx2THmvRykwL4Xlxoa8I9lHBlVbbyPhgyPMioxVZ4NqyxaVVtaJnzsyOidQIhyyQ==" + }, "lodash._isnative": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/lodash._isnative/-/lodash._isnative-2.4.1.tgz", @@ -13307,6 +13365,25 @@ "react-is": "^16.8.1" } }, + "prop-types-extra": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/prop-types-extra/-/prop-types-extra-1.1.1.tgz", + "integrity": "sha512-59+AHNnHYCdiC+vMwY52WmvP5dM3QLeoumYuEyceQDi9aEhtwN9zIQ2ZNo25sMyXnbh32h+P1ezDsUpUH3JAew==", + "requires": { + "react-is": "^16.3.2", + "warning": "^4.0.0" + }, + "dependencies": { + "warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "requires": { + "loose-envify": "^1.0.0" + } + } + } + }, "protobufjs": { "version": "6.8.8", "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.8.8.tgz", @@ -13534,6 +13611,65 @@ "whatwg-fetch": "^3.0.0" } }, + "react-bootstrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-1.0.0.tgz", + "integrity": "sha512-Ep6ZNH6wL5m9bytOS6T9mjSz0YE1bEkc+uHItvenRcA3amr5ApkpKYzAWgdglhRPZHPvm+pnqs1z5IPwv/2UZw==", + "requires": { + "@babel/runtime": "^7.4.2", + "@restart/context": "^2.1.4", + "@restart/hooks": "^0.3.21", + "@types/react": "^16.9.23", + "classnames": "^2.2.6", + "dom-helpers": "^5.1.2", + "invariant": "^2.2.4", + "prop-types": "^15.7.2", + "prop-types-extra": "^1.1.0", + "react-overlays": "^3.0.1", + "react-transition-group": "^4.0.0", + "uncontrollable": "^7.0.0", + "warning": "^4.0.3" + }, + "dependencies": { + "@types/react": { + "version": "16.9.26", + "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.26.tgz", + "integrity": "sha512-dGuSM+B0Pq1MKXYUMlUQWeS6Jj9IhSAUf9v8Ikaimj+YhkBcQrihWBkmyEhK/1fzkJTwZQkhZp5YhmWa2CH+Rw==", + "requires": { + "@types/prop-types": "*", + "csstype": "^2.2.0" + } + }, + "dom-helpers": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.1.3.tgz", + "integrity": "sha512-nZD1OtwfWGRBWlpANxacBEZrEuLa16o1nh7YopFWeoF68Zt8GGEmzHu6Xv4F3XaFIC+YXtTLrzgqKxFgLEe4jw==", + "requires": { + "@babel/runtime": "^7.6.3", + "csstype": "^2.6.7" + } + }, + "react-transition-group": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.3.0.tgz", + "integrity": "sha512-1qRV1ZuVSdxPlPf4O8t7inxUGpdyO5zG9IoNfJxSO0ImU2A1YWkEQvFPuIPZmMLkg5hYs7vv5mMOyfgSkvAwvw==", + "requires": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + } + }, + "warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "requires": { + "loose-envify": "^1.0.0" + } + } + } + }, "react-codemirror": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/react-codemirror/-/react-codemirror-1.0.0.tgz", @@ -13757,6 +13893,44 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.12.0.tgz", "integrity": "sha512-rPCkf/mWBtKc97aLL9/txD8DZdemK0vkA3JMLShjlJB3Pj3s+lpf1KaBzMfQrAmhMQB0n1cU/SUGgKKBCe837Q==" }, + "react-lifecycles-compat": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", + "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" + }, + "react-overlays": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/react-overlays/-/react-overlays-3.0.1.tgz", + "integrity": "sha512-QEt6I3Cjy06pe2FwY/tuWaXEzSVOuXfP8zsC6oWHJhMYpEJQgZV/TCwbCw5slMW6VcgwcWPc4HrBzN0yfxf5Xw==", + "requires": { + "@babel/runtime": "^7.4.5", + "@popperjs/core": "^2.0.0", + "@restart/hooks": "^0.3.12", + "dom-helpers": "^5.1.0", + "prop-types": "^15.7.2", + "uncontrollable": "^7.0.0", + "warning": "^4.0.3" + }, + "dependencies": { + "dom-helpers": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.1.3.tgz", + "integrity": "sha512-nZD1OtwfWGRBWlpANxacBEZrEuLa16o1nh7YopFWeoF68Zt8GGEmzHu6Xv4F3XaFIC+YXtTLrzgqKxFgLEe4jw==", + "requires": { + "@babel/runtime": "^7.6.3", + "csstype": "^2.6.7" + } + }, + "warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "requires": { + "loose-envify": "^1.0.0" + } + } + } + }, "react-router": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.1.2.tgz", @@ -16210,6 +16384,17 @@ "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.21.tgz", "integrity": "sha512-+O8/qh/Qj8CgC6eYBVBykMrNtp5Gebn4dlGD/kKXVkJNDwyrAwSIqwz8CDf+tsAIWVycKcku6gIXJ0qwx/ZXaQ==" }, + "uncontrollable": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-7.1.1.tgz", + "integrity": "sha512-EcPYhot3uWTS3w00R32R2+vS8Vr53tttrvMj/yA1uYRhf8hbTG2GyugGqWDY0qIskxn0uTTojVd6wPYW9ZEf8Q==", + "requires": { + "@babel/runtime": "^7.6.3", + "@types/react": "^16.9.11", + "invariant": "^2.2.4", + "react-lifecycles-compat": "^3.0.4" + } + }, "unicode-canonical-property-names-ecmascript": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", diff --git a/package.json b/package.json index e4d555c..68ac02a 100644 --- a/package.json +++ b/package.json @@ -3,15 +3,20 @@ "version": "0.1.0", "private": true, "dependencies": { + "@fortawesome/fontawesome-svg-core": "^1.2.27", + "@fortawesome/free-solid-svg-icons": "^5.12.1", + "@fortawesome/react-fontawesome": "^0.1.9", "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.3.2", "@testing-library/user-event": "^7.1.2", + "bootstrap": "^4.4.1", "env-cmd": "^10.1.0", "firebase": "^7.9.1", "firebase-tools": "^7.13.1", "material-ui": "^0.20.2", "random-key": "^0.3.2", "react": "^16.12.0", + "react-bootstrap": "^1.0.0", "react-codemirror": "^1.0.0", "react-dom": "^16.12.0", "react-helmet": "^5.2.1", diff --git a/public/index.html b/public/index.html index d9236a9..7c13a95 100644 --- a/public/index.html +++ b/public/index.html @@ -4,8 +4,40 @@ + Code Room | Coding Ninjas + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/src/components/Backdrop/Backdrop.css b/src/components/Backdrop/Backdrop.css new file mode 100644 index 0000000..4045a88 --- /dev/null +++ b/src/components/Backdrop/Backdrop.css @@ -0,0 +1,9 @@ +.backdrop { + position: fixed; + width: 100%; + height: 100%; + top: 0; + left: 0; + background: rgba(0,0,0,0.3); + z-index: 100; +} \ No newline at end of file diff --git a/src/components/Backdrop/Backdrop.js b/src/components/Backdrop/Backdrop.js new file mode 100644 index 0000000..8c03cbc --- /dev/null +++ b/src/components/Backdrop/Backdrop.js @@ -0,0 +1,9 @@ +import React from 'react'; + +import './Backdrop.css'; + +const backdrop = props => ( +
+); + +export default backdrop; \ No newline at end of file diff --git a/src/components/ClonePopup/CloneReceivePopUp.css b/src/components/ClonePopup/CloneReceivePopUp.css new file mode 100644 index 0000000..64a2dec --- /dev/null +++ b/src/components/ClonePopup/CloneReceivePopUp.css @@ -0,0 +1,14 @@ +.popup-heading { + font-size: 1.2rem; + text-align: center; +} + +.popup-heading a { + margin-left: 8px; + font-size: 1.7rem; +} + +.popup-heading a:hover { + color: red; + text-decoration: none; +} \ No newline at end of file diff --git a/src/components/ClonePopup/CloneReceivePopUp.js b/src/components/ClonePopup/CloneReceivePopUp.js new file mode 100644 index 0000000..1ec6c4c --- /dev/null +++ b/src/components/ClonePopup/CloneReceivePopUp.js @@ -0,0 +1,50 @@ +import React, { Component } from 'react'; +import { Modal, Button } from 'react-bootstrap'; +import 'bootstrap/dist/css/bootstrap.min.css'; +import './CloneReceivePopup.css'; +import { creatorInfo } from '../../containers/pages/Coding'; +import { newCloneSessionID } from '../SideDrawer/SideDrawer'; + +export default class CloneReceivePopup extends Component { + + render(){ + + var session_id = newCloneSessionID.session_id; + + return ( + + + + + Copy received from {creatorInfo.user_name}! + + + + +
+ Your key for the cloned session is : + + {session_id} + +
+
+ + + + + + +
+ ); + } + +} \ No newline at end of file diff --git a/src/components/ClonePopup/CloneSendPopup.css b/src/components/ClonePopup/CloneSendPopup.css new file mode 100644 index 0000000..b20b531 --- /dev/null +++ b/src/components/ClonePopup/CloneSendPopup.css @@ -0,0 +1,4 @@ +.popup-heading { + font-size: 1.2rem; + text-align: center; +} \ No newline at end of file diff --git a/src/components/ClonePopup/CloneSendPopup.js b/src/components/ClonePopup/CloneSendPopup.js new file mode 100644 index 0000000..603c9ee --- /dev/null +++ b/src/components/ClonePopup/CloneSendPopup.js @@ -0,0 +1,40 @@ +import React, { Component } from 'react'; +import { Modal, Button } from 'react-bootstrap'; +import 'bootstrap/dist/css/bootstrap.min.css'; +import './CloneSendPopup.css'; + +export default class CloneSendPopup extends Component { + + render(){ + + return ( + + + + + Clone sent! + + + + +
+ Created {this.props.num} clone(s). +
+
+ + + + + +
+ ); + } + +} \ No newline at end of file diff --git a/src/components/ClonePopup/CloneSendPopupSingle.js b/src/components/ClonePopup/CloneSendPopupSingle.js new file mode 100644 index 0000000..4ad6164 --- /dev/null +++ b/src/components/ClonePopup/CloneSendPopupSingle.js @@ -0,0 +1,40 @@ +import React, { Component } from 'react'; +import { Modal, Button } from 'react-bootstrap'; +import 'bootstrap/dist/css/bootstrap.min.css'; +import './CloneSendPopup.css'; + +export default class CloneSendPopupSingle extends Component { + + render(){ + + return ( + + + + + Clone sent! + + + + +
+ Created 1 clone. +
+
+ + + + + +
+ ); + } + +} \ No newline at end of file diff --git a/src/components/Header.js b/src/components/Header/Header.js similarity index 64% rename from src/components/Header.js rename to src/components/Header/Header.js index 7d74cbd..6673a3b 100644 --- a/src/components/Header.js +++ b/src/components/Header/Header.js @@ -1,17 +1,20 @@ import React from "react"; import { Link } from "react-router-dom"; +import logo from '../../images/CNLOGO.svg'; type Props = { style: React.CSSProperties, - extras: React.ReactHTML + title: React.ReactHTML, + extras: React.ReactHTML, }; const Header = (props: Props) => { return (
- Code Room | Coding Ninjas + Code Room | Coding Ninjas +
{props.title}
{props.extras}
); diff --git a/src/components/SideDrawer/SideDrawer.css b/src/components/SideDrawer/SideDrawer.css new file mode 100644 index 0000000..800d0b9 --- /dev/null +++ b/src/components/SideDrawer/SideDrawer.css @@ -0,0 +1,201 @@ +.side-drawer { + height: 100%; + background: white; + box-shadow: 1px 0px 7px rgba(0, 0, 0, 0.5); + position: fixed; + top: 0; + /* left: 0; */ + right: 0; + width: 70%; + max-width: 300px; + z-index: 200; + /* transform: translateX(-100%); */ + transform: translateX(100%); + transition: transform 0.3s ease-out; + overflow: auto; +} + +.side-drawer.open { + transform: translateX(0); +} + +.side-drawer .heading { + display: flex; + align-items: center; + justify-content: center; + margin-top: 8px; + padding: 6px 0px; + color: #000000; + font-size: 1.2rem; + text-decoration: none; + vertical-align: center; +} + +.side-drawer .divider { + margin: 8px auto; + height: 1px; + width: 80%; + background-color: #757575; +} + +.side-drawer .divider-thick { + margin: 18px auto; + height: 2px; + width: 90%; + background-color: #423939cc; +} + +.side-drawer .creator-info { + display: flex; + align-items: center; + margin: 3px 0; + padding: 0 40px; +} + +.side-drawer .creator-info img { + height: 2.3rem; + width: 2.3rem; + border-radius: 50%; + border: 1px solid #222; +} + +.side-drawer .creator-info .creator-name { + color: #000000; + font-size: 1rem; + margin-left: 10px; +} + +.side-drawer ul { + margin: 0; + padding: 0 40px; + height: 75%; + list-style: none; + display: flex; + flex-direction: column; +} + +.side-drawer ul li { + margin: 3px 0; + display: flex; + align-items: center; + justify-content: space-between; +} + +.side-drawer ul li img { + height: 2.3rem; + width: 2.3rem; + border-radius: 50%; + border: 1px solid #222; + margin: auto; +} + +.side-drawer ul .user-details-list { + color: #000000; + font-size: 1rem; + display: flex; +} + +.side-drawer ul li span .user-name { + color: #000000; + font-size: 1rem; + margin: auto; + margin-left: 10px; +} + +.side-drawer ul li .single-clone-btn { + margin: 0; + font-size: 0.85rem; +} + +.side-drawer ul li span:hover { + color: #800000; + cursor: default; +} + +.side-drawer ul li a { + text-decoration: none; + color: #000000; + font-size: 1rem; + margin: auto; + margin-left: 10px; +} + +.side-drawer ul li a:hover { + color: #800000; + cursor: default; +} + +.side-drawer .btn-sendClone { + display: flex; + align-items: center; + justify-content: center; + margin-top: -14px; + height: 30px; + background-color: #323030; + color: #e4e4e4; + font-size: 14px; + border: 1px solid black; + border-radius: 4px; + padding: 0 8px; + line-height: 30px; + -webkit-transition: all 0.3s cubic-bezier(0.215, 0.61, 0.355, 1); + -o-transition: all 0.3s cubic-bezier(0.215, 0.61, 0.355, 1); + transition: all 0.3s cubic-bezier(0.215, 0.61, 0.355, 1); +} + +.btn-sendClone:hover { + background: #5ce198; + color: black; +} + +.btn-sendClone:focus { + outline: none; +} + +.green-dot { + background-color: #41d641; + height: 10px; + width: 10px; + display: flex; + margin: auto; + margin-right: 10px; + margin-left: -20px; + border-radius: 50%; +} + +.red-dot { + background-color: #F44336; + height: 10px; + width: 10px; + display: flex; + margin: auto; + margin-right: 10px; + margin-left: -20px; + border-radius: 50%; +} + +.green-dot-creator { + background-color: #41d641; + height: 10px; + width: 10px; + display: flex; + margin-right: 10px; + margin-left: -20px; + border-radius: 50%; +} + +.red-dot-creator { + background-color: #F44336; + height: 10px; + width: 10px; + display: flex; + margin-right: 10px; + margin-left: -20px; + border-radius: 50%; +} + +/* @media (min-width: 769px) { + .side-drawer { + display: none; + } +} */ \ No newline at end of file diff --git a/src/components/SideDrawer/SideDrawer.js b/src/components/SideDrawer/SideDrawer.js new file mode 100644 index 0000000..e4e3eff --- /dev/null +++ b/src/components/SideDrawer/SideDrawer.js @@ -0,0 +1,799 @@ +import React from 'react'; +import random from "random-key"; +import './SideDrawer.css'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faFileExport } from '@fortawesome/free-solid-svg-icons'; +import { database } from "firebase/app"; +import { firebaseAuth } from "../../config/firebase-config"; +import CloneSendPopup from '../ClonePopup/CloneSendPopup'; +import CloneSendPopupSingle from '../ClonePopup/CloneSendPopupSingle'; +import CloneReceivePopup from '../ClonePopup/CloneReceivePopup'; + +const clonesCreated = {}; +const creatorInfo = {}; +const usersList = []; +const newCloneSessionID = {}; + +export default class sideDrawer extends React.Component { + + constructor(props) { + super(props); + + // setting initial state + this.state = { + cloneTrigger: random.generate(3), // for triggering clone feature + isCreator: false, + clonePopupShow: false, + clonePopupShowSingle: false, + }; + + this.sendSingleCloneHandler = this.sendSingleCloneHandler.bind(this); + + } + + // setting initial state + state = { + clonePopupShow: false, + clonePopupShowSingle: false, + }; + + componentDidMount = () => { + + const session_id = this.props.session_id; + var userConnectedRef = database().ref(`code-sessions/${session_id}/users-connected`); + + // to fetch currently signed-in user + firebaseAuth().onAuthStateChanged(async (user) => { + try { + if (user) { + + var creator_uid; + + // fetch session creator's uid from database + // const creator_uid = await database() + await database() + .ref(`code-sessions/${session_id}/creator`) + .once("value") + .then(snapshot => { + var creatorData = snapshot.val(); + creator_uid = creatorData.user_id; + creatorInfo.user_name = creatorData.user_name; + creatorInfo.user_photo = creatorData.user_photo; + // console.log(creatorData); + }) + .catch(e => { + // no session found corresponding to "sessionid" passed in the params + }); + + if (creator_uid === undefined ){ + // console.log("No Session Found!"); + return; + } + + if (user.uid === creator_uid){ + // console.log("Current user is the session creator"); + this.setState({ isCreator: true }); + } + + // for updating creator's presence status + database() + .ref(`code-sessions/${session_id}/creator`) + .on('value', snapshot => { + let creatorData = snapshot.val(); + if(user.uid === creatorData.user_id){ + let creatorRef = database().ref(`code-sessions/${session_id}/creator/user_status`); + // monitor connection state on browser tab + database().ref(".info/connected") + .on("value", snapshot => { + let userStatus = snapshot.val(); + if(userStatus) { + // set session creator's online status + creatorRef.onDisconnect().set('offline'); + creatorRef.set('online'); + } else { + // session creator has lost network + } + }); + } + }); + + // for updating creator's presence status + database() + .ref(`code-sessions/${session_id}/creator/user_status`) + .on('value', snapshot => { + let creatorStatus = snapshot.val(); + if(creatorStatus === "online"){ + // session creator is online + creatorInfo.user_status = "online"; + } else if (creatorStatus === "offline"){ + // session creator is offline + creatorInfo.user_status = "offline"; + } + // in order to re-render the sidebar component everytime there's a change + this.setState(this.state); + }); + + // setting states of 'cloneTrigger' & 'isFirstLoad' in database + database() + .ref(`code-sessions/${session_id}/cloneHelper`) + .update({ + cloneTrigger: this.state.cloneTrigger, + isFirstLoad: true, + }); + + // setting states of 'cloneTrigger' & 'isFirstLoad' in database + database() + .ref(`code-sessions/${session_id}/singleCloneHelper`) + .update({ + cloneTrigger: this.state.cloneTrigger, + isFirstLoad: true, + }); + + // for updating user's presence status + database() + .ref(`code-sessions/${session_id}/users-connected`) + .on('value', snapshot => { + snapshot.forEach(function(childSnapshot){ + let userData = childSnapshot.val(); + if(user.uid === userData.user_id){ + let userKey = childSnapshot.key; + let userRef = database().ref(`code-sessions/${session_id}/users-connected/${userKey}/user_status`); + // monitor connection state on browser tab + database().ref(".info/connected") + .on("value", snapshot => { + let userStatus = snapshot.val(); + if(userStatus) { + // set user's online status + userRef.onDisconnect().set('offline'); + userRef.set('online'); + } else { + // user has lost network + } + }); + return true; + } + }); + }); + + // displaying users-connected from database + database() + .ref(`code-sessions/${session_id}/users-connected`) + .on('value', snapshot => { + clonesCreated.numOfClonesCreated = snapshot.numChildren(); + console.log(`\nConnected users: ${snapshot.numChildren()}`); + usersList.splice(0, usersList.length); + var i = 0; + + let self = this; + let isCreator = this.state.isCreator; + + snapshot.forEach(function(childSnapshot){ + + var userData = childSnapshot.val(); + console.log(userData.user_name + " - " + userData.user_email); + // console.log(userData.cloneSessionID); + + let cloneLink; + if(userData.cloneSessionID === undefined){ + cloneLink = `/404`; + } else { + cloneLink = `/home/${userData.cloneSessionID}`; + } + + let displayUserNameCloneLink; + if(isCreator){ + displayUserNameCloneLink = + + {userData.user_name} + + } else { + displayUserNameCloneLink = + {userData.user_name} + } + + let sendSingleCloneButton; + if(isCreator) { + sendSingleCloneButton = + + + + } + + let userStatus = userData.user_status; + let userPresenceStatus; + if(userStatus === "online"){ + // user is online + // console.log(userData.user_name + " is ONLINE."); + userPresenceStatus = + + } else if (userStatus === "offline"){ + // user is offline + // console.log(userData.user_name + " is OFFLINE."); + userPresenceStatus = + + } + + usersList.push( +
  • + + { userPresenceStatus } + Avatar + { displayUserNameCloneLink } + + { sendSingleCloneButton } +
  • + ); + i++; + + }); + console.log("\n"); + + // in order to re-render the sidebar component everytime there's a change + this.setState(this.state); + + }); + + // 'received-clone' pop-up handler + if(!this.state.isCreator){ + // fetching and then setting state of 'clonePopupShow' from database + database() + .ref(`code-sessions/${session_id}/cloneHelper/clonePopupShow`) + .on('value', snapshot => { + // console.log("'clonePopupShow' state changed!"); + let clonePopupShowState = snapshot.val(); + if(clonePopupShowState){ + this.setState({ + clonePopupShow: true, + }); + } + + // setting state of 'clonePopupShow' to false in database + // after delay of 500 milliseconds + function clonePopupShowStateDelay(){ + database() + .ref(`code-sessions/${session_id}/cloneHelper/clonePopupShow`) + .set(false); + } + setTimeout(clonePopupShowStateDelay, 500); + + }); + + // fetching and then setting state of 'clonePopupShow' from database + database() + .ref(`code-sessions/${session_id}/singleCloneHelper/clonePopupShow`) + .on('value', snapshot => { + // console.log("'clonePopupShow' state changed!"); + + database() + .ref(`code-sessions/${session_id}/singleCloneHelper`) + .once("value") + .then(snapshot => { + let isFirstLoad = snapshot.val().isFirstLoad; + + // does not execute on first load of page + if(isFirstLoad === false){ + let user_uid = snapshot.val().user_uid; + + if(user.uid === user_uid){ + + let clonePopupShowState = snapshot.val(); + if(clonePopupShowState){ + this.setState({ + clonePopupShow: true, + }); + } + + // // setting state of 'clonePopupShow' to false in database + // // after delay of 500 milliseconds + // function clonePopupShowStateDelay(){ + // database() + // .ref(`code-sessions/${session_id}/singleCloneHelper/clonePopupShow`) + // .set(false); + // } + // setTimeout(clonePopupShowStateDelay, 500); + + } + + } + + }) + .catch(e => { + console.log(e); + }); + + }); + + } + + var date; + + // 'send-single-clone' button functionality + // executes when 'send-single-clone' button is clicked + // i.e. when 'cloneTrigger' key is changed in database + database() + .ref(`code-sessions/${session_id}/singleCloneHelper/cloneTrigger`) + .on('value', snapshot => { + + database() + .ref(`code-sessions/${session_id}/singleCloneHelper`) + .once("value") + .then(snapshot => { + let isFirstLoad = snapshot.val().isFirstLoad; + + // does not execute on first load of page + if(isFirstLoad === false){ + + // console.log("sendSingleCloneButton clicked!"); + + let user_uid = snapshot.val().user_uid; + + date = Date(); + + // fetching existing code and session-title in this session ID to be cloned + var existingContent; + var existingSessionTitle; + database() + .ref(`code-sessions/${session_id}`) + .once("value") + .then(snapshot => { + existingContent = snapshot.val().content; + existingSessionTitle = snapshot.val().title; + }) + .catch(e => { + console.log(e); + }); + + // only creator can create new session with cloned code + if(this.state.isCreator){ + + database() + .ref(`code-sessions/${session_id}/users-connected`) + .once("value") + .then(snapshot => { + // console.log("New clone being created."); + + snapshot.forEach(function(childSnapshot){ + + // console.log(existingContent); + + if(childSnapshot.val().user_id === user_uid){ + let newSessionKey = random.generate(5); + let userData = childSnapshot.val(); + + // creating new session with cloned code + database() + .ref("code-sessions/" + newSessionKey) + .set({ + cloneSentBy: creator_uid, + clonedFromSession: session_id, + content: existingContent, + createdon: date, + readOnly: false, + title: existingSessionTitle, + }); + + // adding details of the user as creator to the database + database() + .ref("code-sessions/" + newSessionKey + "/creator") + .set({ + user_id: userData.user_id, + user_name: userData.user_name, + user_email: userData.user_email, + user_photo: userData.user_photo + }); + + return true; + } + + }); + + console.log(`Clone sent!`); + + }) + .catch(e => { + console.log(e); + }); + } + + // finding new session id with cloned content + // for currently signed-in user + // and storing that session id in 'newCloneSessionID' object + if(!this.state.isCreator){ + + // console.log(date); + // comparing date of session created exluding the 'seconds' time + var dateCompressed = date.substring(0, 21); + + database() + .ref(`code-sessions/`) + .once("value") + .then(snapshot => { + snapshot.forEach(function(childSnapshot){ + + console.clear(); + console.log("Searching..."); + + let cloneSentBy = childSnapshot.child("cloneSentBy").val(); + let user_id = childSnapshot.child("creator").child("user_id").val(); + let content = childSnapshot.child("content").val(); + let sessionTitle = childSnapshot.child("title").val(); + let createdOn = childSnapshot.child("createdon").val(); + let createdOnCompressed = createdOn.substring(0, 21); + + // Comparing: content (code), user id (uid), + // cloneSentBy (creator id), date (createOn) & session-title + if(cloneSentBy === creator_uid + && user_id === user.uid + && content === existingContent + && sessionTitle === existingSessionTitle + && createdOnCompressed === dateCompressed) { + + let cloneSessionID = childSnapshot.key; + newCloneSessionID.session_id = cloneSessionID; + + console.log(`Clone found at session ID: ${cloneSessionID}`); + + userConnectedRef + .once("value") + .then(snapshot => { + snapshot.forEach(function(childSnapshot){ + let user_id = childSnapshot.val().user_id; + if(user_id === user.uid) { + console.log(`User details stored under "${childSnapshot.key}" in database`); + database() + .ref(`code-sessions/${session_id}/users-connected/${childSnapshot.key}`) + .update({ + cloneSessionID: cloneSessionID + }); + return true; + } + }); + }) + .catch(e => { + console.log(e); + }); + + return true; + } + + }); + + }); + } + + } + + }) + .catch(e => { + console.log(e); + }); + + }); + + // 'send-clone' button functionality + // executes when 'send-clone' button is clicked + // i.e. when 'cloneTrigger' key is changed in database + database() + .ref(`code-sessions/${session_id}/cloneHelper/cloneTrigger`) + .on('value', snapshot => { + + database() + .ref(`code-sessions/${session_id}/cloneHelper`) + .once("value") + .then(snapshot => { + let isFirstLoad = snapshot.val().isFirstLoad; + + // does not execute on first load of page + if(isFirstLoad === false){ + + // console.log("sendCloneButton clicked!"); + + date = Date(); + + // fetching existing code and session-title in this session ID to be cloned + var existingContent; + var existingSessionTitle; + database() + .ref(`code-sessions/${session_id}`) + .once("value") + .then(snapshot => { + existingContent = snapshot.val().content; + existingSessionTitle = snapshot.val().title; + }) + .catch(e => { + console.log(e); + }); + + // only creator can create new sessions with cloned code + if(this.state.isCreator){ + + database() + .ref(`code-sessions/${session_id}/users-connected`) + .once("value") + .then(snapshot => { + // console.log("New clone being created."); + + snapshot.forEach(function(childSnapshot){ + + let newSessionKey = random.generate(5); + let userData = childSnapshot.val(); + + // creating new session with cloned code + database() + .ref("code-sessions/" + newSessionKey) + .set({ + cloneSentBy: creator_uid, + clonedFromSession: session_id, + content: existingContent, + createdon: date, + readOnly: false, + title: existingSessionTitle, + }); + + // adding details of the user as creator to the database + database() + .ref("code-sessions/" + newSessionKey + "/creator") + .set({ + user_id: userData.user_id, + user_name: userData.user_name, + user_email: userData.user_email, + user_photo: userData.user_photo + }); + + }); + + // setting 'numOfClonesCreated' to be passed in props of 'CloneSendPopup' + clonesCreated.numOfClonesCreated = snapshot.numChildren(); + console.log(`Created ${snapshot.numChildren()} clone(s)`); + + }) + .catch(e => { + console.log(e); + }); + } + + // finding new session id with cloned content + // for currently signed-in user + // and storing that session id in 'newCloneSessionID' object + if(!this.state.isCreator){ + + // console.log(date); + // comparing date of session created exluding the 'seconds' time + var dateCompressed = date.substring(0, 21); + + database() + .ref(`code-sessions/`) + .once("value") + .then(snapshot => { + + snapshot.forEach(function(childSnapshot){ + + console.clear(); + console.log("Searching..."); + + let cloneSentBy = childSnapshot.child("cloneSentBy").val(); + let user_id = childSnapshot.child("creator").child("user_id").val(); + let content = childSnapshot.child("content").val(); + let sessionTitle = childSnapshot.child("title").val(); + let createdOn = childSnapshot.child("createdon").val(); + let createdOnCompressed = createdOn.substring(0, 21); + + // Comparing: content (code), user id (uid), + // cloneSentBy (creator id), date (createOn) & session-title + if(cloneSentBy === creator_uid + && user_id === user.uid + && content === existingContent + && sessionTitle === existingSessionTitle + && createdOnCompressed === dateCompressed) { + + let cloneSessionID = childSnapshot.key; + newCloneSessionID.session_id = cloneSessionID; + console.log(`Clone found at session ID: ${cloneSessionID} \n`); + + userConnectedRef + .once("value") + .then(snapshot => { + snapshot.forEach(function(childSnapshot){ + let user_id = childSnapshot.val().user_id; + if(user_id === user.uid) { + console.log(`User details stored under "${childSnapshot.key}" in database`); + database() + .ref(`code-sessions/${session_id}/users-connected/${childSnapshot.key}`) + .update({ + cloneSessionID: cloneSessionID + }); + return true; + } + }); + }) + .catch(e => { + console.log(e); + }); + + return true; + } + }); + + }); + } + + } + + }) + .catch(e => { + console.log(e); + }); + + }); + + } else { + console.log("Cannot fetch currently signed-in user!"); + } + } catch(error) { + console.log("Error in authentication:", error); + } + }); + + this.codeRef = database().ref(`code-sessions/${session_id}`); + + }; + + // single user 'send-clone' button handler + sendSingleCloneHandler = (e) => { + + let user_uid = e.currentTarget.value; + // console.log(user_uid); + + let newCloneTrigger = random.generate(3); + this.setState({ + clonePopupShowSingle: true, + cloneTrigger: newCloneTrigger, + }); + + // adding corresponding user uid into database + this.codeRef.child("singleCloneHelper").child("user_uid").set(user_uid); + // setting 'isFirstLoad' to false in database + this.codeRef.child("singleCloneHelper").child("isFirstLoad").set(false); + // setting 'clonePopupShow' state to true in database + this.codeRef.child("singleCloneHelper").child("clonePopupShow").set(true); + // updating 'cloneTrigger' value and 'clonePopupShow' state in database + // indicating 'send-clone' button is clicked + this.codeRef.child("singleCloneHelper").child("cloneTrigger").set(newCloneTrigger); + + const session_id = this.props.session_id; + // setting state of 'clonePopupShow' to false in database + // after delay of 500 milliseconds + function clonePopupShowStateDelay(){ + database() + .ref(`code-sessions/${session_id}/singleCloneHelper/clonePopupShow`) + .set(false); + } + setTimeout(clonePopupShowStateDelay, 800); + + }; + + // 'send-clone' button handler + sendCloneHandler = () => { + let newCloneTrigger = random.generate(3); + this.setState({ + clonePopupShow: true, + cloneTrigger: newCloneTrigger, + }); + // setting 'isFirstLoad' to false in database + this.codeRef.child("cloneHelper").child("isFirstLoad").set(false); + // setting 'clonePopupShow' state to true in database + this.codeRef.child("cloneHelper").child("clonePopupShow").set(true); + // updating 'cloneTrigger' value and 'clonePopupShow' state in database + // indicating 'send-clone' button is clicked + this.codeRef.child("cloneHelper").child("cloneTrigger").set(newCloneTrigger); + }; + + render() { + + // 'send-clone' button is only displayed when current user is the creator of session + let sendCloneButton; + if (this.state.isCreator) { + sendCloneButton = + + + + } + + // to close the send/receive clone popup + let clonePopupClose = () => { + this.setState({clonePopupShow: false}); + this.setState({clonePopupShowSingle: false}); + } + + let clonePopUp; + if (!this.state.isCreator) { + // render 'clone-received' pop-up component + clonePopUp = + + } else { + // render 'clone-sent' pop-up component + clonePopUp = + + } + + let singleClonePopUp; + if (this.state.isCreator) { + // render 'clone-sent' pop-up (for single user) component + singleClonePopUp = + + } + + let creatorStatus = creatorInfo.user_status; + let creatorPresenceStatus; + if(creatorStatus === "online"){ + // user is online + creatorPresenceStatus = + + } else if (creatorStatus === "offline"){ + // user is offline + creatorPresenceStatus = + + } + + let drawerClasses = 'side-drawer'; + if (this.props.show) { + drawerClasses = 'side-drawer open'; + } + + return ( + + ); + } + +} + +// exporting new session ID with cloned code to each user-connected +export {newCloneSessionID}; \ No newline at end of file diff --git a/src/config/firebase-config.js b/src/config/firebase-config.js index 31737d5..07c315d 100644 --- a/src/config/firebase-config.js +++ b/src/config/firebase-config.js @@ -1,4 +1,7 @@ -import firebase from 'firebase'; +import firebase from 'firebase/app'; +import 'firebase/database'; +import 'firebase/auth'; +window.firebase = firebase; const firebaseConfig = { apiKey: process.env.REACT_APP_FIREBASE_API_KEY, diff --git a/src/containers/App.css b/src/containers/App.css index 82cfccc..fc1833b 100644 --- a/src/containers/App.css +++ b/src/containers/App.css @@ -1,5 +1,5 @@ .App { - font-family: "Nunito", sans-serif; + font-family: "Poppins", sans-serif; background-color: #000000; color: #fff; } @@ -9,6 +9,11 @@ width: auto; } +.logo-header { + height: 40px; + padding-bottom: 3px; +} + .App-header { background-color: #000000; height: 80px; @@ -33,6 +38,45 @@ .App-title:active, .App-title:hover { color: #fff; + text-decoration: none; +} + +.session-title-input { + height: 30px; + width: 15rem; + margin-left: 5px; + padding: 5px; + background-color: #1d1f27; + color: #fff; + font-size: 19px; + border: 1px solid #1d1f27; + border-radius: 6px; +} + +.session-title-input:focus { + outline: none; +} + +.session-title-input:hover { + background-color: #222; + color: #fff; + border: 1px solid #ffffff6e; + border-radius: 6px; +} + +.session-title { + margin-left: 50px; + color: #ffffffd6; + font-size: 17px; + text-decoration: none; +} + +.session-title .session-title-name { + margin-left: 50px; + color: #fff; + font-size: 18px; + text-decoration: none; + border-bottom: 1px solid #fff; } .homepage { @@ -43,6 +87,15 @@ height: calc(100vh - 80px); } +.firepad { + height: calc(100vh - 80px); +} + +.powered-by-firepad { + display: none; + z-index: 0; +} + .codingpage { height: calc(100vh - 80px); } @@ -78,7 +131,7 @@ p { color: #5ce198; } -.btn { +.btn-home { display: inline-block; margin-bottom: 0; font-weight: 500; @@ -99,11 +152,15 @@ p { transition: all 0.3s cubic-bezier(0.215, 0.61, 0.355, 1); } -.btn:hover { +.btn-home:hover { background: #5ce198; color: black; } +.btn-home:focus { + outline: none; +} + .btn-coding { display: inline-block; margin-bottom: 0; @@ -130,6 +187,36 @@ p { color: black; } +.btn-coding:focus { + outline: none; +} + +.btn-user-editing { + display: inline-block; + margin-bottom: 0; + font-weight: 500; + text-align: center; + cursor: pointer; + border: 1px solid transparent; + line-height: 36px; + padding: 0 10px; + font-size: 14px; + border-radius: 58px; + text-decoration: none; + height: 36px; + background: transparent; + color: #e4e4e4; + border-color: #5ce198; + cursor: default; + -webkit-transition: all 0.3s cubic-bezier(0.215, 0.61, 0.355, 1); + -o-transition: all 0.3s cubic-bezier(0.215, 0.61, 0.355, 1); + transition: all 0.3s cubic-bezier(0.215, 0.61, 0.355, 1); +} + +.btn-user-editing:focus { + outline: none; +} + /**** Google Login Page ****/ .header-loginPage { @@ -144,9 +231,13 @@ p { font-family: "Lucida Sans Unicode", "Lucida Grande", sans-serif; color: #222; margin-top: 5%; + margin-bottom: 1.3rem; + line-height: 1.5; + font-size: 2em; + font-weight: bold; } -/**** Spinning Logo ****/ +/**** Login Page Spinning Icon ****/ .lds-ring { display: inline-block; @@ -186,21 +277,66 @@ p { } } -/**** Quick Access Classes ****/ +/**** Coding Page Spinning Icon ****/ -.margin-l-15 { - margin-left: 15px; +.lds-ring-coding { + display: inline-block; + width: 90px; + height: 90px; + position: absolute; + top: 40vh; + left: 46.5%; + z-index: 50; + text-align: center; +} +.lds-ring-coding div { + box-sizing: border-box; + display: block; + position: absolute; + width: 80px; + height: 80px; + margin: 8px; + border: 8px solid #fff; + border-radius: 50%; + animation: lds-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite; + border-color: #fff transparent transparent transparent; +} +.lds-ring-coding div:nth-child(1) { + animation-delay: -0.45s; +} +.lds-ring-coding div:nth-child(2) { + animation-delay: -0.3s; +} +.lds-ring-coding div:nth-child(3) { + animation-delay: -0.15s; +} +@keyframes lds-ring-coding { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } } -.margin-l-10 { - margin-left: 10px; +/**** Extra ****/ + +.mode-dropdown { + height: 30px; + font-size: 13px; } -.text-center { - text-align: center; +.toggle-btn { + /* vertical-align: bottom; */ + vertical-align: middle; + font-size: 1.4rem; } -/**** Extra ****/ +.clone-btn { + vertical-align: bottom; + border-radius: 11px; + font-size: 1.2rem; +} .spotlight { position: absolute; @@ -211,4 +347,163 @@ p { transparent 160px, rgba(0, 0, 0, 0.85) 200px ); +} + +/*** Dashboard ***/ + +.dashboard { + font-family: "Roboto", sans-serif; + background-color: #fff; + color: #222; + height: calc(100vh - 80px); + padding: 0px 64px; +} + +.dashboard .sessions-title { + font-family: 'Montserrat', sans-serif; + color: #222; + font-size: 34px; + font-weight: 400; + margin: 0; + margin-bottom: 20px; + padding-top: 15px; +} + +.dashboard .clones-title { + font-family: 'Montserrat', sans-serif; + color: #222; + font-size: 34px; + font-weight: 400; + margin: 0; + margin-bottom: 20px; + margin-top: 40px; +} + +.clickable { + cursor: pointer; +} + +.aligncenter { + text-align: center; +} + +.loading-text { + display: flex; + justify-content: center; + font-size: 18px; + font-family: monospace; + margin-top: 15px; +} + +.view-clones-help-text { + display: flex; + justify-content: center; + font-size: 15px; + font-family: monospace; + margin-top: 15px; +} + +table { + /* background-color: white; */ + width: 80%; + text-align: center; + border-collapse: collapse; + border-spacing: 0; +} + +table td, table th { + padding:7px; + text-align: center; +} + +table th { + border-bottom: 1px solid #ccc; + border-top: 1px solid #ccc; +} + +.table-collection { + margin: auto; +} + +.table-collection th { + border-bottom: 1px solid #b3b5b8; + border-top: none; + text-transform: uppercase; +} + +table tr { + border-bottom: 1px solid #ccc; + border-top: 1px solid #ccc; +} + +.table-collection tr { + border-bottom: 1px solid #b3b5b8; + border-top: none; +} + +.table-collection tbody tr:hover { + background-color: #f3f3f4; +} + +.table-collection a { + color: #007bff; + font-weight: 700; + text-decoration: none; +} + +.table-collection a:hover { + text-decoration: none; +} + +.table-collection .title-color { + color: #a70a19; + font-weight: 500; +} + +.view-clone-btn { + font-size: 14px; + border-radius: 5px; + border: 1px solid #007aff; +} + +.view-clone-btn:hover { + background-color: #187deb; + color: #fff; +} + +.view-clone-btn:focus { + outline: none; +} + +/* Coding Page */ + +.session-details { + display: inline-flex; + flex-direction: column; +} + +.header-menu { + vertical-align: text-top; +} + +/**** Quick Access Classes ****/ + +.padding-0-14 { + padding: 0 14px; +} + +.margin-l-20 { + margin-left: 20px; +} + +.margin-l-15 { + margin-left: 15px; +} + +.margin-l-10 { + margin-left: 10px; +} + +.text-center { + text-align: center; } \ No newline at end of file diff --git a/src/containers/App.js b/src/containers/App.js index cc9ca58..7aff957 100644 --- a/src/containers/App.js +++ b/src/containers/App.js @@ -1,12 +1,13 @@ import React, { Component } from "react"; import { BrowserRouter as Router, Route } from "react-router-dom"; import "./App.css"; +import MuiThemeProvider from "material-ui/styles/MuiThemeProvider"; +import getMuiTheme from "material-ui/styles/getMuiTheme"; +import { createBrowserHistory } from "history"; import Login from "./pages/Login"; import Coding from "./pages/Coding"; import Home from './pages/Home'; -import MuiThemeProvider from "material-ui/styles/MuiThemeProvider"; -import getMuiTheme from "material-ui/styles/getMuiTheme"; -import createBrowserHistory from "history/createBrowserHistory"; +import Dashboard from './pages/Dashboard'; const muiTheme = getMuiTheme({ appBar: { @@ -26,6 +27,7 @@ class App extends Component {
    +
    diff --git a/src/containers/pages/Coding.js b/src/containers/pages/Coding.js index fb93aff..9f90b24 100644 --- a/src/containers/pages/Coding.js +++ b/src/containers/pages/Coding.js @@ -1,123 +1,344 @@ import React from "react"; -import CodeMirror from "react-codemirror"; -import Header from "../../components/Header"; -import { database } from "firebase"; +import random from "random-key"; +import Header from "../../components/Header/Header"; +import SideDrawer from '../../components/SideDrawer/SideDrawer'; +import Backdrop from '../../components/Backdrop/Backdrop'; +import { database } from "firebase/app"; +import { firebaseAuth } from "../../config/firebase-config"; import { logout } from "../../helpers/auth"; - -import "codemirror/lib/codemirror"; -import "codemirror/lib/codemirror.css"; -import 'codemirror/mode/xml/xml'; -import "codemirror/mode/javascript/javascript"; -import "codemirror/theme/dracula.css"; -import 'codemirror/addon/edit/closetag'; -import 'codemirror/addon/edit/matchbrackets'; -import 'codemirror/addon/edit/closebrackets'; -import 'codemirror/addon/edit/matchtags'; -import 'codemirror/addon/edit/trailingspace'; -import 'codemirror/addon/hint/show-hint'; -import 'codemirror/addon/hint/show-hint.css'; -import 'codemirror/addon/hint/javascript-hint'; -import 'codemirror/addon/comment/comment'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faBars } from '@fortawesome/free-solid-svg-icons'; +import { faToggleOn, faToggleOff } from '@fortawesome/free-solid-svg-icons'; const appTokenKey = "appToken"; const sessionID = "sessionID"; +const creatorInfo = {}; export default class CodingPage extends React.Component { constructor(props) { super(props); + // favicon script + (function() { + var link = document.querySelector("link[rel*='icon']") || document.createElement('link'); + link.type = 'image/x-icon'; + link.rel = 'shortcut icon'; + link.href = '../favicon_CN.ico'; + document.getElementsByTagName('head')[0].appendChild(link); + })(); + const session_id = this.props.match.params.sessionid; - // console.log(session_id); + // if appTokenKey is not found in localStorage, + // then redirect to login page + if (!localStorage.getItem(appTokenKey)) { + localStorage.setItem("sessionID", session_id); + this.props.history.push(`/login`); + return; + } + + // setting initial state this.state = { - code: "Loading..." - // firebaseUser: JSON.parse(localStorage.getItem("firebaseUser")) + key: random.generate(3), // for storing connected-users + isLoading: true, + readOnly: false, + isCreator: false, + sideDrawerOpen: false, + mode: 'xml', + code: "Loading...", }; - // console.log("User:", this.state.firebaseUser); + // set 'readOnly' state and session title from database + database() + .ref(`code-sessions/${session_id}`) + .once("value") + .then(snapshot => { + let readOnly = snapshot.val().readOnly; + this.setState({ readOnly: readOnly }); + // setting session title in the header + let sessionTitle = snapshot.val().title; + if(sessionTitle === undefined || sessionTitle === ""){ + this.sessionTitle.value = "Untitled"; + } else { + this.sessionTitle.value = sessionTitle; + } + }) + .catch(e => { + // no session found corresponding to "sessionid" passed in the params + }); this.handleLogout = this.handleLogout.bind(this); + this.sessionTitleHandler = this.sessionTitleHandler.bind(this); - // if appTokenKey is not found in localStorage, - // then redirect to login page - if (!localStorage.getItem(appTokenKey)) { - localStorage.setItem("sessionID", session_id); - this.props.history.push(`/login`); - return; - } } - + // setting initial state state = { + sideDrawerOpen: false, + mode: 'xml', code: "Loading...", cursorPosition: { line: 0, ch: 0 - } + }, }; componentDidMount = () => { + const { params } = this.props.match; - let self = this; - database() - .ref("/code-sessions/" + params.sessionid) - .once("value") - .then(snapshot => { + const session_id = params.sessionid; - // trimmimg the Date() to remove unnecessary add-ons - var createdOn = snapshot.val().createdon; - var createdOnCompressed = createdOn.substring(0, 25); - self.setState({ code: snapshot.val().content + "", createdon: createdOnCompressed }, () => { - // fetching content from db and setting on the editor - let content = snapshot.val().content; - self.codemirror.getCodeMirror().setValue(content); - // console.log(this.codemirror.getCodeMirror()); - }); - - this.codeRef = database().ref("code-sessions/" + params.sessionid); - // whenever changes are made: - // "code" is updated from the db - // cursor position is updated (changeCursorPos() is called) - // code on the editor screen is updated from the db - this.codeRef.on("value", function(snapshot) { - self.setState({ - code: snapshot.val().content + // to fetch currently signed-in user + firebaseAuth().onAuthStateChanged(async (user) => { + try { + if (user) { + + var creator_uid; + + // fetch session creator's uid from database + // const creator_uid = await database() + await database() + .ref(`code-sessions/${session_id}/creator`) + .once("value") + .then(snapshot => { + var creatorData = snapshot.val(); + creator_uid = creatorData.user_id; + creatorInfo.user_name = creatorData.user_name; + creatorInfo.user_photo = creatorData.user_photo; + // console.log(creatorData); + }) + .catch(e => { + // no session found corresponding to "sessionid" passed in the params }); - var currentCursorPos = self.state.cursorPosition; - self.codemirror.getCodeMirror().setValue(snapshot.val().content); - self.setState({ cursorPosition: currentCursorPos }); - self.changeCursorPos(); + + if (creator_uid === undefined ){ + console.log("No Session Found!"); + return; + } + + // do not add user details to 'users-connected' + // if that user is the creator of session + if (user.uid !== creator_uid){ + + console.log("Current user is NOT the session creator"); + console.log("User added to 'users-connected'"); + + var newUser; + + // checking if user already present in database + await database() + .ref(`code-sessions/${session_id}/users-connected`) + .once("value") + .then(snapshot => { + snapshot.forEach(function(childSnapshot){ + var userData = childSnapshot.val(); + if(userData.user_id === user.uid){ + newUser = false; + console.log("User details already present in the database"); + } + }); + if(newUser !== false){ + newUser = true; + } + }) + .catch(e => { + console.log(e); + }); + + // adding new user details in database + if(newUser === true){ + console.log("User details added to the database"); + await database() + .ref(`code-sessions/${session_id}/users-connected/user-` + this.state.key) + .set({ + user_id: user.uid, + user_name: user.displayName, + user_email: user.email, + user_photo: user.photoURL + }); + } + + } else { + console.log("Current user is the session creator"); + console.log("User NOT added to 'users-connected'"); + this.setState({ isCreator: true }); + } + + // making the users-editing-state button dynamic + database() + .ref(`code-sessions/${session_id}`) + .on('value', snapshot => { + // console.log("readOnly changed!"); + if(this.userEditingToggleBtn !== null){ + let readOnlyState = snapshot.val().readOnly; + if(readOnlyState){ + this.userEditingToggleBtn.innerHTML = "Editing: Disabled"; + } else { + this.userEditingToggleBtn.innerHTML = "Editing: Enabled"; + } + } + }); + + // prevent space as input in session-title + this.sessionTitle.addEventListener('keypress', function(event) { + let key = event.keyCode; + if (key === 32) { + event.preventDefault(); + } + }); + + // setting session-tile readOnly state + if(this.state.isCreator){ + if(this.sessionTitle !== null && this.sessionTitle !== undefined){ + this.sessionTitle.readOnly = false; + } + } else { + if(this.sessionTitle !== null && this.sessionTitle !== undefined){ + this.sessionTitle.readOnly = true; + } + } + + // updating the session title dynamically + database() + .ref(`code-sessions/${session_id}/title`) + .on('value', snapshot => { + // console.log("session-title modified!"); + if(this.sessionTitle !== null && this.sessionTitle !== undefined){ + let sessionTitle = snapshot.val(); + this.sessionTitle.value = sessionTitle; + } + }); + + } else { + + console.log("Cannot fetch currently signed-in user!"); + console.log("Signing out... please login again."); + if (localStorage.getItem(appTokenKey)) { + localStorage.setItem("sessionID", session_id); + logout().then(function () { + localStorage.removeItem(appTokenKey); + this.props.history.push("/login"); + }.bind(this)); + return; + } + + } + } catch(error) { + console.log("Error in authentication:", error); + } + }); + + this.firepadRef = database().ref(`code-sessions/${session_id}/firepad`); + + this.codemirror = window.CodeMirror(this.firepadDiv, { + mode: this.state.mode, + theme: "dracula", + lineNumbers: true, + lineWrapping: true, + autoCloseTags: true, + matchBrackets: true, + autoCloseBrackets: true, + matchTags: true, + showTrailingSpace: true, + extraKeys: { + 'Ctrl-Space' : 'autocomplete', + 'Cmd-/' : 'toggleComment', + 'Ctrl-/' : 'toggleComment' + }, + }); + + this.firepad = window.Firepad.fromCodeMirror(this.firepadRef, this.codemirror, { + // richTextShortcuts: true, + // richTextToolbar: true, + // defaultText: "Loading...", + }); + + database() + .ref(`code-sessions/${session_id}/firepad/history`) + // .orderByKey() + .on("value", snapshot => { + // console.log("Number of revisions:", snapshot.numChildren()); + let count = 0; + // keeping record (history) of last 150 changes in database + if(snapshot.numChildren() > 150){ + let minCount = snapshot.numChildren() - 150; + snapshot.forEach(function(childSnapshot){ + if(count === minCount){ + return true; + } + // console.log("Deleted! " + count + " " + childSnapshot.key); + childSnapshot.ref.remove(); + count++; + }); + } + + database() + .ref(`code-sessions/${session_id}/firepad/history`) + .once("value") + .then(snapshot => { + // console.log("New number of revisions:", snapshot.numChildren()); }); - }) - .catch(e => { - // no session found corresponding to "sessionid" passed in the params - self.codemirror.getCodeMirror().setValue("No Sessions Found!"); + + }); + + let self = this; + database() + .ref(`code-sessions/${session_id}`) + .once("value") + .then(snapshot => { + + // trimmimg the Date() to remove unnecessary add-ons + var createdOn = snapshot.val().createdon; + var createdOnCompressed = createdOn.substring(0, 25); + self.setState({ + code: snapshot.val().content + "", + createdon: createdOnCompressed }); - }; - // updating cursor position - changeCursorPos = () => { - const { line, ch } = this.state.cursorPosition; - this.codemirror.getCodeMirror().doc.setCursor(line, ch); - }; + this.firepad.on('ready', function() { + self.setState({isLoading: false}); + if (self.firepad.isHistoryEmpty()) { + self.firepad.setText(self.state.code); + } + // self.firepad.setUserId(userId) + }); - // called whenever code is changed - onChange = (newVal, change) => { - // console.log(newVal, change); - this.setState({ - cursorPosition: { - line: this.codemirror.getCodeMirror().doc.getCursor().line, - ch: this.codemirror.getCodeMirror().doc.getCursor().ch + // called whenever changes are made in editor + this.firepad.on('synced', function(isSynced) { + + // console.log(firepad.getText()); + let newVal = self.firepad.getText(); + + // updating data in database + let codeRef = database().ref(`code-sessions/${session_id}`); + codeRef.child("content").set(newVal); + + }); + + // changing 'readOnly' state of editor + this.codeRef = database().ref(`code-sessions/${session_id}`); + this.codeRef.on("value", function(snapshot) { + // setting 'readOnly' option + if(self.state.isCreator){ + self.codemirror.setOption("readOnly", false); + } else { + self.codemirror.setOption("readOnly", snapshot.val().readOnly); } - }, - () => {} - ); - // updating data - this.codeRef.child("content").set(newVal); + }); + + }) + .catch(e => { + // no session found corresponding to "sessionid" passed in the params + this.firepad.dispose(); + this.setState({isLoading: false}); + this.sessionTitle.value = " "; + self.codemirror.setValue("No Session Found!"); + }); + }; - // sign-out functionality: + // sign-out functionality handleLogout() { logout().then(function () { localStorage.removeItem(appTokenKey); @@ -127,48 +348,165 @@ export default class CodingPage extends React.Component { }.bind(this)); } + // sidebar toggle handler + drawerToggleClickHandler = () => { + this.setState((prevState) => { + return { sideDrawerOpen: !prevState.sideDrawerOpen }; + }); + }; + + // backdrop (dull background) handler + backdropClickHandler = () => { + this.setState({ sideDrawerOpen: false }); + }; + + // read only toggle handler + toggleReadOnly = () => { + this.setState({ + readOnly: !this.state.readOnly + }, () => this.codemirror.focus()); + // updating readOnly in database + this.codeRef.child("readOnly").set(!this.state.readOnly); + }; + + // editor language handler + changeMode = (e) => { + var mode = e.target.value; + this.setState({ + mode: mode + }); + // setting new language mode in the editor + this.codemirror.setOption("mode", mode); + }; + + // executes whenever session-title is changed + sessionTitleHandler = (e) => { + let sessionTitle = e.target.value; + this.codeRef.child("title").set(sessionTitle); + }; + render() { + + let backdrop; + if (this.state.sideDrawerOpen) { + backdrop = + } + + // readOnly toggle is only displayed when current user is the creator of session + let readOnlyToggle; + if (this.state.isCreator) { + readOnlyToggle = + + } + + // this button is only displayed to the users-connected (not the creator) + let userEditingToggle; + if (!this.state.isCreator) { + userEditingToggle = + + } + + // spinning loading icon + let spinningLogo; + if (this.state.isLoading) { + spinningLogo = +
    + } + return ( +
    + + Title: + + (this.sessionTitle = r)} + className="session-title-input" + type="text" + readOnly={true} + placeholder="Enter title..." + defaultValue="Untitled" + minLength="1" + maxLength="30" + onChange={this.sessionTitleHandler} /> +
    + } extras={
    - {this.state.createdon - ? `Created On: ${this.state.createdon}` - : ""} - + + + + {this.state.createdon + ? `Created On: ${this.state.createdon}` + : ""} + + + {this.state.createdon + ? `Created By: ${creatorInfo.user_name}` + : ""} + + + + + + + + { readOnlyToggle } + { userEditingToggle } + + + + + + +
    } /> -
    - (this.codemirror = r)} - className="code-mirror-container" - value={this.state.code} - onChange={this.onChange} - options={{ - mode: "xml", - theme: "dracula", - lineNumbers: true, - readOnly: false, - autoCloseTags: true, - matchBrackets: true, - autoCloseBrackets: true, - matchTags: true, - showTrailingSpace: true, - extraKeys: { - 'Ctrl-Space' : 'autocomplete', - 'Cmd-/' : 'toggleComment', - 'Ctrl-/' : 'toggleComment' - } - }} - /> -
    + + { spinningLogo } + + + { backdrop } + +
    (this.firepadDiv = r)}>
    + ); } -} \ No newline at end of file +} + +// exporting creator info object +export {creatorInfo}; \ No newline at end of file diff --git a/src/containers/pages/Dashboard.js b/src/containers/pages/Dashboard.js new file mode 100644 index 0000000..4c87756 --- /dev/null +++ b/src/containers/pages/Dashboard.js @@ -0,0 +1,324 @@ +import React from "react"; +import Header from "../../components/Header/Header"; +import { database } from "firebase/app"; +import { firebaseAuth } from "../../config/firebase-config"; +import { logout } from "../../helpers/auth"; + +const appTokenKey = "appToken"; +const sessionID = "sessionID"; +const userSessionsList = []; +const sessionClonesList = []; +const userDisplayName = {}; + +export default class Dashboard extends React.Component { + + constructor(props) { + super(props); + + this.state = { + helper: true, + isLoadingUserSession: true, + viewClonesHelpText: false, + isLoadingSessionClones: false, + noSessionFoundText: false, + noCloneFoundText: false, + }; + + if (localStorage.getItem(sessionID)){ + localStorage.removeItem(sessionID); + } + + if (!localStorage.getItem(appTokenKey)) { + this.props.history.push(`/login`); + return; + } + + this.handleLogout = this.handleLogout.bind(this); + this.viewClonesHandler = this.viewClonesHandler.bind(this); + } + + componentDidMount = () => { + + // to fetch currently signed-in user + firebaseAuth().onAuthStateChanged(user => { + try { + if (user) { + + // storing current user's name + userDisplayName.name = user.displayName; + + // to reset/hide the session clones list whenever dashboard page is opened + sessionClonesList.splice(0, sessionClonesList.length); + + // database().ref(`/${session_id}/creator`).orderByChild("user_id").equalTo(user.uid).on('value', function (snapshot) {}); + + database() + .ref(`code-sessions/`) + .once("value") + .then(snapshot => { + // console.log("Searching for all sessions created by current user..."); + userSessionsList.splice(0, userSessionsList.length); + let found = false; + let self = this; + let i = 0; + + snapshot.forEach(function(childSnapshot){ + + let creator_id = childSnapshot.child("creator").child("user_id").val(); + let sessionsTitle = childSnapshot.child("title").val(); + let createdOn = childSnapshot.child("createdon").val(); + let createdOnCompressed = createdOn.substring(0, 24); + + if(creator_id === user.uid) { + + self.setState({ + isLoadingUserSession: false, + viewClonesHelpText: true, + }); + + found = true; + + let sessionid = childSnapshot.key; + // console.log(`User session id: ${sessionid}`); + + userSessionsList.push( + + + + {sessionid} + + + + + { sessionsTitle } + + + { createdOnCompressed } + + + + + ); + i++; + + self.setState(self.state); + + } + }); + + if(!found){ + this.setState({ + isLoadingUserSession: false, + noSessionFoundText: true, + }); + // console.log("No clones found!"); + } + + // this.setState(this.state); + }); + + } else { + + console.log("Cannot fetch currently signed-in user!"); + console.log("Signing out... please login again."); + if (localStorage.getItem(appTokenKey)) { + // localStorage.setItem("sessionID", session_id); + logout().then(function () { + localStorage.removeItem(appTokenKey); + this.props.history.push("/login"); + }.bind(this)); + return; + } + + } + } catch(error) { + console.log("Error in authentication:", error); + } + }); + } + + viewClonesHandler = (e) => { + + this.setState({ + viewClonesHelpText: false, + isLoadingSessionClones: true, + noCloneFoundText: false, + }); + + sessionClonesList.splice(0, sessionClonesList.length); + + let correspondingSessionID = e.currentTarget.value; + // console.log(`correspondingSessionID: ${correspondingSessionID}`); + + database() + .ref(`code-sessions/`) + .once("value") + .then(snapshot => { + + // sessionClonesList.splice(0, sessionClonesList.length); + let found = false; + let self = this; + let i = 0; + + snapshot.forEach(function(childSnapshot){ + + let clonedFromSessionID = childSnapshot.child("clonedFromSession").val(); + let userName = childSnapshot.child("creator").child("user_name").val(); + let userEmail = childSnapshot.child("creator").child("user_email").val(); + let createdOn = childSnapshot.child("createdon").val(); + let createdOnCompressed = createdOn.substring(0, 24); + + if(clonedFromSessionID === correspondingSessionID) { + self.setState({isLoadingSessionClones: false}); + found = true; + let cloneSesssionID = childSnapshot.key; + // console.log(`cloneSesssionID: ${cloneSesssionID}`); + + sessionClonesList.push( + + + + {cloneSesssionID} + + + + { userName } + + { userEmail } + { createdOnCompressed } + + ); + i++; + + self.setState(self.state); + } + + }); + + if(!found){ + this.setState({ + isLoadingSessionClones: false, + noCloneFoundText: true, + }); + // console.log("No clones found!"); + } + + this.setState(this.state); + + }); + }; + + // sign-out functionality + handleLogout() { + logout().then(function () { + localStorage.removeItem(appTokenKey); + localStorage.removeItem(sessionID); + this.props.history.push("/login"); + console.log("User signed-out from firebase."); + }.bind(this)); + } + + render() { + + let loadingTextUserSession; + if(this.state.isLoadingUserSession){ + loadingTextUserSession = Loading...; + } + + let loadingTextSessionClones; + if(this.state.isLoadingSessionClones){ + loadingTextSessionClones = Loading...; + } + + let viewClonesHelpText; + if(!this.state.isLoadingUserSession && this.state.viewClonesHelpText){ + viewClonesHelpText = + (Click on the 'View Clones' button + corresponding to a session ID above); + } + + let noCloneFoundText; + if(this.state.noCloneFoundText){ + noCloneFoundText = + No Clones Found!; + } + + let noSessionFoundText; + if(this.state.noSessionFoundText){ + noSessionFoundText = + No Sessions Found!; + } + + let usersSessionHeading; + let usersCloneHeading; + if(userDisplayName.name !== undefined){ + let name = userDisplayName.name; + usersSessionHeading = name + "'s sessions"; + usersCloneHeading = "Session clones"; + } + + return ( + +
    + Dashboard + + + } + /> +
    +

    + { usersSessionHeading } +

    + + + + + + + + + + + { userSessionsList } + +
    Session IDTitleCreated OnActions
    + { loadingTextUserSession } + { noSessionFoundText } +

    + { usersCloneHeading } +

    + + + + + + + + + + + { sessionClonesList } + +
    Session IDSent ToEmailCreated On
    + { viewClonesHelpText } + { loadingTextSessionClones } + { noCloneFoundText } +
    + + ); + } + +} \ No newline at end of file diff --git a/src/containers/pages/Home.js b/src/containers/pages/Home.js index a7d4665..a5bd9c5 100644 --- a/src/containers/pages/Home.js +++ b/src/containers/pages/Home.js @@ -1,7 +1,8 @@ import React from "react"; import random from "random-key"; -import Header from "../../components/Header"; -import { database } from "firebase"; +import Header from "../../components/Header/Header"; +import { database } from "firebase/app"; +import { firebaseAuth } from "../../config/firebase-config"; import { logout } from "../../helpers/auth"; const appTokenKey = "appToken"; @@ -12,11 +13,6 @@ export default class HomePage extends React.Component { constructor(props) { super(props); - // this.state = { - // //firebaseUser: JSON.parse(localStorage.getItem("firebaseUser")) - // }; - - //console.log("User:", this.state.firebaseUser); this.handleLogout = this.handleLogout.bind(this); if(localStorage.getItem(sessionID)){ @@ -44,18 +40,72 @@ export default class HomePage extends React.Component { }); }; - // when new session is created (Share Code button is clicked) + // when new session is created ('Share Code' button is clicked) onNewGround = () => { - database() - .ref("code-sessions/" + this.state.key) - .set({ - content: "

    I ♥ Coding!

    ", - createdon: Date() - }); - this.props.history.push("/home/" + this.state.key); + + // to fetch currently signed-in user + firebaseAuth().onAuthStateChanged(user => { + // firebaseAuth().onIdTokenChanged(user => { + try { + if (user) { + + database() + .ref("code-sessions/" + this.state.key) + .set({ + content: "

    I ♥ Coding!

    ", + createdon: Date(), + title: "Untitled", + readOnly: false, // by default 'false' + }); + + // adding details of the user to the database + database() + .ref("code-sessions/" + this.state.key + "/creator") + .set({ + user_id: user.uid, + user_name: user.displayName, + user_email: user.email, + user_photo: user.photoURL + }); + + this.props.history.push("/home/" + this.state.key); + + } else { + console.log("Cannot fetch currently signed-in user!"); + console.log("Signing out... please login again."); + this.handleLogout(); + } + } catch(error) { + console.log("Error in authentication:", error); + } + }); + + }; + + // when 'Dashboard' button is clicked + goToDashboard = () => { + + // to fetch currently signed-in user + firebaseAuth().onAuthStateChanged(user => { + // firebaseAuth().onIdTokenChanged(user => { + try { + if (user) { + + this.props.history.push("/dashboard"); + + } else { + console.log("Cannot fetch currently signed-in user!"); + console.log("Signing out... please login again."); + this.handleLogout(); + } + } catch(error) { + console.log("Error in authentication:", error); + } + }); + }; - // sign-out functionality: + // sign-out functionality handleLogout() { logout().then(function () { localStorage.removeItem(appTokenKey); @@ -69,13 +119,17 @@ export default class HomePage extends React.Component { return (
    + {this.state.num ? `Total ${this.state.num}+ Shares` : null} + + + } />

    - {/* Coderoom | Coding Ninjas */} - {/*
    */} - {/*
    */}
    Share Code within Realtime.
    @@ -86,11 +140,11 @@ export default class HomePage extends React.Component { Simple Realtime Code Sharing Editor App.

    - -
    diff --git a/src/containers/pages/Login.js b/src/containers/pages/Login.js index a07f4df..0b635dd 100644 --- a/src/containers/pages/Login.js +++ b/src/containers/pages/Login.js @@ -1,9 +1,9 @@ import React from "react"; +import logo from '../../images/CNLOGO.svg'; import { Helmet } from 'react-helmet'; import { FontIcon, RaisedButton } from "material-ui"; import { loginWithGoogle } from "../../helpers/auth"; import { firebaseAuth } from "../../config/firebase-config"; -import logo from '../../images/CNLOGO.svg'; const firebaseAuthKey = "firebaseAuthInProgress"; const appTokenKey = "appToken"; @@ -16,7 +16,7 @@ export default class Login extends React.Component { // console.log(this.props.match.params); this.state = { - splashScreen: false + splashScreen: false, }; this.handleGoogleLogin = this.handleGoogleLogin.bind(this); @@ -43,7 +43,7 @@ export default class Login extends React.Component { const session_id = localStorage.getItem(sessionID); // redirect to /home/:session_id this.props.history.push(`/home/${session_id}`); - localStorage.removeItem(sessionID); + // localStorage.removeItem(sessionID); } else { // console.log("** sessionID absent **"); @@ -56,26 +56,32 @@ export default class Login extends React.Component { // called whenever user sign-in/sign-out or token expires firebaseAuth().onAuthStateChanged(user => { - if (user) { - console.log("User signed in: ", user); + try { + if (user) { + + // console.log("User signed in: ", JSON.stringify(user)); + console.log("User signed in:", user); + + localStorage.removeItem(firebaseAuthKey); + + // setting appTokenKey as "uid" of signed-in user + localStorage.setItem(appTokenKey, user.uid); + + if (localStorage.getItem(sessionID)) { + const session_id = localStorage.getItem(sessionID); + // console.log("** sessionID present **"); + // redirect to /home/:session_id + this.props.history.push(`/home/${session_id}`); + localStorage.removeItem(sessionID); + } else { + // console.log("** sessionID absent **"); + // redirect to /home + this.props.history.push(`/home`); + } - localStorage.removeItem(firebaseAuthKey); - - // setting appTokenKey as "uid" of signed-in user - localStorage.setItem(appTokenKey, user.uid); - - if (localStorage.getItem(sessionID)) { - const session_id = localStorage.getItem(sessionID); - // console.log("** sessionID present **"); - // redirect to /home/:session_id - this.props.history.push(`/home/${session_id}`); - localStorage.removeItem(sessionID); - } else { - // console.log("** sessionID absent **"); - // redirect to /home - this.props.history.push(`/home`); } - + } catch(error) { + console.log("Error in authentication:", error); } }); }