diff --git a/.env.example b/.env.example new file mode 100644 index 0000000000000000000000000000000000000000..c4b486ca536e715d97822e973f4924dee4379af7 --- /dev/null +++ b/.env.example @@ -0,0 +1,8 @@ +FAST_REFRESH=false +REACT_APP_API_URL= +REACT_APP_FIREBASE_API_KEY= +REACT_APP_FIREBASE_PROJECT_ID= +REACT_APP_STORAGE_BUCKET= +REACT_APP_FIREBASE_APP_ID= +REACT_APP_MEASUREMENT_ID= +REACT_APP_MESSAGING_SENDER_ID= \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index c37cf7856e3d82faeb98cc3e1192f47500ccd011..d2dc72aef2a556f3bd6a3c56619f466269969ffd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,15 +21,18 @@ "bootstrap": "^5.2.3", "dayjs": "^1.11.7", "enzyme": "^3.11.0", + "firebase": "^9.19.1", "formik": "^2.2.9", "history": "^5.3.0", "jest-junit": "^15.0.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-icons": "^4.7.1", + "react-player": "^2.12.0", "react-router-dom": "^6.8.2", "react-scripts": "5.0.1", "react-select": "^5.7.0", + "styled-breakpoints": "^11.1.1", "styled-components": "^5.3.8", "web-vitals": "^2.1.4", "yup": "^1.0.2" @@ -2540,6 +2543,634 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@firebase/analytics": { + "version": "0.9.5", + "resolved": "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.9.5.tgz", + "integrity": "sha512-hJTVs2jLxPXE7hs7D/jaEsgGivrm7tSEl65kb5NkDBWV7QQBUnRfVML/xra9nTFLLJhAdbExZPHg6HfIuMSYEQ==", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/installations": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/analytics-compat": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@firebase/analytics-compat/-/analytics-compat-0.2.5.tgz", + "integrity": "sha512-ohKUrwSoXvyUJdSLuDr82mOqrzgWKyHMUt9/TfYKkyDXnFjNlBcFBpkpl/UHMAOJe0M60YYXiVCZoGQYldCslA==", + "dependencies": { + "@firebase/analytics": "0.9.5", + "@firebase/analytics-types": "0.8.0", + "@firebase/component": "0.6.4", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/analytics-compat/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" + }, + "node_modules/@firebase/analytics-types": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@firebase/analytics-types/-/analytics-types-0.8.0.tgz", + "integrity": "sha512-iRP+QKI2+oz3UAh4nPEq14CsEjrjD6a5+fuypjScisAh9kXKFvdJOZJDwk7kikLvWVLGEs9+kIUS4LPQV7VZVw==" + }, + "node_modules/@firebase/analytics/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" + }, + "node_modules/@firebase/app": { + "version": "0.9.7", + "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.9.7.tgz", + "integrity": "sha512-ADnRXaW4XQF11QYYhZQEJEtOGnmLkGl2FCixCxPighLrmJmGwCZrzSFtwITd8w/EU3dRYaU5Og37VfnY+gKxGw==", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "idb": "7.0.1", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/app-check": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/@firebase/app-check/-/app-check-0.6.4.tgz", + "integrity": "sha512-M9qyVTWkEkHXmgwGtObvXQqKcOe9iKAOPqm0pCe74mzgKVTNq157ff39+fxHPb4nFbipToY+GuvtabLUzkHehQ==", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/app-check-compat": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@firebase/app-check-compat/-/app-check-compat-0.3.4.tgz", + "integrity": "sha512-s6ON0ixPKe99M1DNYMI2eR5aLwQZgy0z8fuW1tnEbzg5p/N/GKFmqiIHSV4gfp8+X7Fw5NLm7qMfh4xrcPgQCw==", + "dependencies": { + "@firebase/app-check": "0.6.4", + "@firebase/app-check-types": "0.5.0", + "@firebase/component": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/app-check-compat/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" + }, + "node_modules/@firebase/app-check-interop-types": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@firebase/app-check-interop-types/-/app-check-interop-types-0.2.0.tgz", + "integrity": "sha512-+3PQIeX6/eiVK+x/yg8r6xTNR97fN7MahFDm+jiQmDjcyvSefoGuTTNQuuMScGyx3vYUBeZn+Cp9kC0yY/9uxQ==" + }, + "node_modules/@firebase/app-check-types": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@firebase/app-check-types/-/app-check-types-0.5.0.tgz", + "integrity": "sha512-uwSUj32Mlubybw7tedRzR24RP8M8JUVR3NPiMk3/Z4bCmgEKTlQBwMXrehDAZ2wF+TsBq0SN1c6ema71U/JPyQ==" + }, + "node_modules/@firebase/app-check/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" + }, + "node_modules/@firebase/app-compat": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.2.7.tgz", + "integrity": "sha512-KYBUKoRrvSGW8jqKgARRsma0lJie9M0zyWhPF3PNjqc9pYsw7SZXp5s5SzsheeCXzIDFydP5uEA4f1Z87D7CxQ==", + "dependencies": { + "@firebase/app": "0.9.7", + "@firebase/component": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/app-compat/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" + }, + "node_modules/@firebase/app-types": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.0.tgz", + "integrity": "sha512-AeweANOIo0Mb8GiYm3xhTEBVCmPwTYAu9Hcd2qSkLuga/6+j9b1Jskl5bpiSQWy9eJ/j5pavxj6eYogmnuzm+Q==" + }, + "node_modules/@firebase/app/node_modules/idb": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/idb/-/idb-7.0.1.tgz", + "integrity": "sha512-UUxlE7vGWK5RfB/fDwEGgRf84DY/ieqNha6msMV99UsEMQhJ1RwbCd8AYBj3QMgnE3VZnfQvm4oKVCJTYlqIgg==" + }, + "node_modules/@firebase/app/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" + }, + "node_modules/@firebase/auth": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-0.22.0.tgz", + "integrity": "sha512-4PiaDJEhJ7FNo48WG0TAlqHiCuRBXxUow2q+0emh+PhmM0cLT1UdqK1EuWWGc5CY+ztNQZUh+Yzeh+nv9tZL0w==", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "node-fetch": "2.6.7", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/auth-compat": { + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/@firebase/auth-compat/-/auth-compat-0.3.7.tgz", + "integrity": "sha512-+r8/++hYZLA/to6Iq8A70LTUsZvhkdT2R4mB4oJGxryJ7vNjpuP5m5hfAd42h/VvX8eT1OXJCENCfEZoDyhksA==", + "dependencies": { + "@firebase/auth": "0.22.0", + "@firebase/auth-types": "0.12.0", + "@firebase/component": "0.6.4", + "@firebase/util": "1.9.3", + "node-fetch": "2.6.7", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/auth-compat/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" + }, + "node_modules/@firebase/auth-interop-types": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.2.1.tgz", + "integrity": "sha512-VOaGzKp65MY6P5FI84TfYKBXEPi6LmOCSMMzys6o2BN2LOsqy7pCuZCup7NYnfbk5OkkQKzvIfHOzTm0UDpkyg==" + }, + "node_modules/@firebase/auth-types": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@firebase/auth-types/-/auth-types-0.12.0.tgz", + "integrity": "sha512-pPwaZt+SPOshK8xNoiQlK5XIrS97kFYc3Rc7xmy373QsOJ9MmqXxLaYssP5Kcds4wd2qK//amx/c+A8O2fVeZA==", + "peerDependencies": { + "@firebase/app-types": "0.x", + "@firebase/util": "1.x" + } + }, + "node_modules/@firebase/auth/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" + }, + "node_modules/@firebase/component": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.6.4.tgz", + "integrity": "sha512-rLMyrXuO9jcAUCaQXCMjCMUsWrba5fzHlNK24xz5j2W6A/SRmK8mZJ/hn7V0fViLbxC0lPMtrK1eYzk6Fg03jA==", + "dependencies": { + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/component/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" + }, + "node_modules/@firebase/database": { + "version": "0.14.4", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.14.4.tgz", + "integrity": "sha512-+Ea/IKGwh42jwdjCyzTmeZeLM3oy1h0mFPsTy6OqCWzcu/KFqRAr5Tt1HRCOBlNOdbh84JPZC47WLU18n2VbxQ==", + "dependencies": { + "@firebase/auth-interop-types": "0.2.1", + "@firebase/component": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "faye-websocket": "0.11.4", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/database-compat": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-0.3.4.tgz", + "integrity": "sha512-kuAW+l+sLMUKBThnvxvUZ+Q1ZrF/vFJ58iUY9kAcbX48U03nVzIF6Tmkf0p3WVQwMqiXguSgtOPIB6ZCeF+5Gg==", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/database": "0.14.4", + "@firebase/database-types": "0.10.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/database-compat/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" + }, + "node_modules/@firebase/database-types": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.10.4.tgz", + "integrity": "sha512-dPySn0vJ/89ZeBac70T+2tWWPiJXWbmRygYv0smT5TfE3hDrQ09eKMF3Y+vMlTdrMWq7mUdYW5REWPSGH4kAZQ==", + "dependencies": { + "@firebase/app-types": "0.9.0", + "@firebase/util": "1.9.3" + } + }, + "node_modules/@firebase/database/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" + }, + "node_modules/@firebase/firestore": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-3.10.0.tgz", + "integrity": "sha512-St6yy2r7zYxJiAEiI19aQJqxVV8LDvlmeK52R9KMn2nZsgdDVOurch1cH7bBl0OxEgfiVxBmAQJLYvZc+qwAgw==", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "@firebase/webchannel-wrapper": "0.9.0", + "@grpc/grpc-js": "~1.7.0", + "@grpc/proto-loader": "^0.6.13", + "node-fetch": "2.6.7", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=10.10.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/firestore-compat": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@firebase/firestore-compat/-/firestore-compat-0.3.6.tgz", + "integrity": "sha512-svS8oV0nwTyoHW5mslFV0gRb3FLpRQGjz2F7nc5imnPUTjSJmAfXECtgs5HG5MSJM/laSimfAeGuQVh5FM1AEw==", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/firestore": "3.10.0", + "@firebase/firestore-types": "2.5.1", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/firestore-compat/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" + }, + "node_modules/@firebase/firestore-types": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@firebase/firestore-types/-/firestore-types-2.5.1.tgz", + "integrity": "sha512-xG0CA6EMfYo8YeUxC8FeDzf6W3FX1cLlcAGBYV6Cku12sZRI81oWcu61RSKM66K6kUENP+78Qm8mvroBcm1whw==", + "peerDependencies": { + "@firebase/app-types": "0.x", + "@firebase/util": "1.x" + } + }, + "node_modules/@firebase/firestore/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" + }, + "node_modules/@firebase/functions": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/@firebase/functions/-/functions-0.9.4.tgz", + "integrity": "sha512-3H2qh6U+q+nepO5Hds+Ddl6J0pS+zisuBLqqQMRBHv9XpWfu0PnDHklNmE8rZ+ccTEXvBj6zjkPfdxt6NisvlQ==", + "dependencies": { + "@firebase/app-check-interop-types": "0.2.0", + "@firebase/auth-interop-types": "0.2.1", + "@firebase/component": "0.6.4", + "@firebase/messaging-interop-types": "0.2.0", + "@firebase/util": "1.9.3", + "node-fetch": "2.6.7", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/functions-compat": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@firebase/functions-compat/-/functions-compat-0.3.4.tgz", + "integrity": "sha512-kxVxTGyLV1MBR3sp3mI+eQ6JBqz0G5bk310F8eX4HzDFk4xjk5xY0KdHktMH+edM2xs1BOg0vwvvsAHczIjB+w==", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/functions": "0.9.4", + "@firebase/functions-types": "0.6.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/functions-compat/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" + }, + "node_modules/@firebase/functions-types": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@firebase/functions-types/-/functions-types-0.6.0.tgz", + "integrity": "sha512-hfEw5VJtgWXIRf92ImLkgENqpL6IWpYaXVYiRkFY1jJ9+6tIhWM7IzzwbevwIIud/jaxKVdRzD7QBWfPmkwCYw==" + }, + "node_modules/@firebase/functions/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" + }, + "node_modules/@firebase/installations": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/@firebase/installations/-/installations-0.6.4.tgz", + "integrity": "sha512-u5y88rtsp7NYkCHC3ElbFBrPtieUybZluXyzl7+4BsIz4sqb4vSAuwHEUgCgCeaQhvsnxDEU6icly8U9zsJigA==", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/util": "1.9.3", + "idb": "7.0.1", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/installations-compat": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@firebase/installations-compat/-/installations-compat-0.2.4.tgz", + "integrity": "sha512-LI9dYjp0aT9Njkn9U4JRrDqQ6KXeAmFbRC0E7jI7+hxl5YmRWysq5qgQl22hcWpTk+cm3es66d/apoDU/A9n6Q==", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/installations": "0.6.4", + "@firebase/installations-types": "0.5.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/installations-compat/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" + }, + "node_modules/@firebase/installations-types": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@firebase/installations-types/-/installations-types-0.5.0.tgz", + "integrity": "sha512-9DP+RGfzoI2jH7gY4SlzqvZ+hr7gYzPODrbzVD82Y12kScZ6ZpRg/i3j6rleto8vTFC8n6Len4560FnV1w2IRg==", + "peerDependencies": { + "@firebase/app-types": "0.x" + } + }, + "node_modules/@firebase/installations/node_modules/idb": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/idb/-/idb-7.0.1.tgz", + "integrity": "sha512-UUxlE7vGWK5RfB/fDwEGgRf84DY/ieqNha6msMV99UsEMQhJ1RwbCd8AYBj3QMgnE3VZnfQvm4oKVCJTYlqIgg==" + }, + "node_modules/@firebase/installations/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" + }, + "node_modules/@firebase/logger": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.4.0.tgz", + "integrity": "sha512-eRKSeykumZ5+cJPdxxJRgAC3G5NknY2GwEbKfymdnXtnT0Ucm4pspfR6GT4MUQEDuJwRVbVcSx85kgJulMoFFA==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/logger/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" + }, + "node_modules/@firebase/messaging": { + "version": "0.12.4", + "resolved": "https://registry.npmjs.org/@firebase/messaging/-/messaging-0.12.4.tgz", + "integrity": "sha512-6JLZct6zUaex4g7HI3QbzeUrg9xcnmDAPTWpkoMpd/GoSVWH98zDoWXMGrcvHeCAIsLpFMe4MPoZkJbrPhaASw==", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/installations": "0.6.4", + "@firebase/messaging-interop-types": "0.2.0", + "@firebase/util": "1.9.3", + "idb": "7.0.1", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/messaging-compat": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@firebase/messaging-compat/-/messaging-compat-0.2.4.tgz", + "integrity": "sha512-lyFjeUhIsPRYDPNIkYX1LcZMpoVbBWXX4rPl7c/rqc7G+EUea7IEtSt4MxTvh6fDfPuzLn7+FZADfscC+tNMfg==", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/messaging": "0.12.4", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/messaging-compat/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" + }, + "node_modules/@firebase/messaging-interop-types": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@firebase/messaging-interop-types/-/messaging-interop-types-0.2.0.tgz", + "integrity": "sha512-ujA8dcRuVeBixGR9CtegfpU4YmZf3Lt7QYkcj693FFannwNuZgfAYaTmbJ40dtjB81SAu6tbFPL9YLNT15KmOQ==" + }, + "node_modules/@firebase/messaging/node_modules/idb": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/idb/-/idb-7.0.1.tgz", + "integrity": "sha512-UUxlE7vGWK5RfB/fDwEGgRf84DY/ieqNha6msMV99UsEMQhJ1RwbCd8AYBj3QMgnE3VZnfQvm4oKVCJTYlqIgg==" + }, + "node_modules/@firebase/messaging/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" + }, + "node_modules/@firebase/performance": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/@firebase/performance/-/performance-0.6.4.tgz", + "integrity": "sha512-HfTn/bd8mfy/61vEqaBelNiNnvAbUtME2S25A67Nb34zVuCSCRIX4SseXY6zBnOFj3oLisaEqhVcJmVPAej67g==", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/installations": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/performance-compat": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@firebase/performance-compat/-/performance-compat-0.2.4.tgz", + "integrity": "sha512-nnHUb8uP9G8islzcld/k6Bg5RhX62VpbAb/Anj7IXs/hp32Eb2LqFPZK4sy3pKkBUO5wcrlRWQa6wKOxqlUqsg==", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/performance": "0.6.4", + "@firebase/performance-types": "0.2.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/performance-compat/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" + }, + "node_modules/@firebase/performance-types": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@firebase/performance-types/-/performance-types-0.2.0.tgz", + "integrity": "sha512-kYrbr8e/CYr1KLrLYZZt2noNnf+pRwDq2KK9Au9jHrBMnb0/C9X9yWSXmZkFt4UIdsQknBq8uBB7fsybZdOBTA==" + }, + "node_modules/@firebase/performance/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" + }, + "node_modules/@firebase/remote-config": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/@firebase/remote-config/-/remote-config-0.4.4.tgz", + "integrity": "sha512-x1ioTHGX8ZwDSTOVp8PBLv2/wfwKzb4pxi0gFezS5GCJwbLlloUH4YYZHHS83IPxnua8b6l0IXUaWd0RgbWwzQ==", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/installations": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/remote-config-compat": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@firebase/remote-config-compat/-/remote-config-compat-0.2.4.tgz", + "integrity": "sha512-FKiki53jZirrDFkBHglB3C07j5wBpitAaj8kLME6g8Mx+aq7u9P7qfmuSRytiOItADhWUj7O1JIv7n9q87SuwA==", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/remote-config": "0.4.4", + "@firebase/remote-config-types": "0.3.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/remote-config-compat/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" + }, + "node_modules/@firebase/remote-config-types": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@firebase/remote-config-types/-/remote-config-types-0.3.0.tgz", + "integrity": "sha512-RtEH4vdcbXZuZWRZbIRmQVBNsE7VDQpet2qFvq6vwKLBIQRQR5Kh58M4ok3A3US8Sr3rubYnaGqZSurCwI8uMA==" + }, + "node_modules/@firebase/remote-config/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" + }, + "node_modules/@firebase/storage": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/@firebase/storage/-/storage-0.11.2.tgz", + "integrity": "sha512-CtvoFaBI4hGXlXbaCHf8humajkbXhs39Nbh6MbNxtwJiCqxPy9iH3D3CCfXAvP0QvAAwmJUTK3+z9a++Kc4nkA==", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/util": "1.9.3", + "node-fetch": "2.6.7", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/storage-compat": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@firebase/storage-compat/-/storage-compat-0.3.2.tgz", + "integrity": "sha512-wvsXlLa9DVOMQJckbDNhXKKxRNNewyUhhbXev3t8kSgoCotd1v3MmqhKKz93ePhDnhHnDs7bYHy+Qa8dRY6BXw==", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/storage": "0.11.2", + "@firebase/storage-types": "0.8.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/storage-compat/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" + }, + "node_modules/@firebase/storage-types": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@firebase/storage-types/-/storage-types-0.8.0.tgz", + "integrity": "sha512-isRHcGrTs9kITJC0AVehHfpraWFui39MPaU7Eo8QfWlqW7YPymBmRgjDrlOgFdURh6Cdeg07zmkLP5tzTKRSpg==", + "peerDependencies": { + "@firebase/app-types": "0.x", + "@firebase/util": "1.x" + } + }, + "node_modules/@firebase/storage/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" + }, + "node_modules/@firebase/util": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.9.3.tgz", + "integrity": "sha512-DY02CRhOZwpzO36fHpuVysz6JZrscPiBXD0fXp6qSrL9oNOx5KWICKdR95C0lSITzxp0TZosVyHqzatE8JbcjA==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/util/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" + }, + "node_modules/@firebase/webchannel-wrapper": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-0.9.0.tgz", + "integrity": "sha512-BpiZLBWdLFw+qFel9p3Zs1jD6QmH7Ii4aTDu6+vx8ShdidChZUXqDhYJly4ZjSgQh54miXbBgBrk0S+jTIh/Qg==" + }, "node_modules/@floating-ui/core": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.2.2.tgz", @@ -2553,6 +3184,82 @@ "@floating-ui/core": "^1.2.2" } }, + "node_modules/@grpc/grpc-js": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.7.3.tgz", + "integrity": "sha512-H9l79u4kJ2PVSxUNA08HMYAnUBLj9v6KjYQ7SQ71hOZcEXhShE/y5iQCesP8+6/Ik/7i2O0a10bPquIcYfufog==", + "dependencies": { + "@grpc/proto-loader": "^0.7.0", + "@types/node": ">=12.12.47" + }, + "engines": { + "node": "^8.13.0 || >=10.10.0" + } + }, + "node_modules/@grpc/grpc-js/node_modules/@grpc/proto-loader": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.6.tgz", + "integrity": "sha512-QyAXR8Hyh7uMDmveWxDSUcJr9NAWaZ2I6IXgAYvQmfflwouTM+rArE2eEaCtLlRqO81j7pRLCt81IefUei6Zbw==", + "dependencies": { + "@types/long": "^4.0.1", + "lodash.camelcase": "^4.3.0", + "long": "^4.0.0", + "protobufjs": "^7.0.0", + "yargs": "^16.2.0" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@grpc/grpc-js/node_modules/protobufjs": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.3.tgz", + "integrity": "sha512-TtpvOqwB5Gdz/PQmOjgsrGH1nHjAQVCN7JG4A6r1sXRWESL5rNMAiRcBQlCAdKxZcAbstExQePYG8xof/JVRgg==", + "hasInstallScript": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@grpc/grpc-js/node_modules/protobufjs/node_modules/long": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.1.tgz", + "integrity": "sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A==" + }, + "node_modules/@grpc/proto-loader": { + "version": "0.6.13", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.6.13.tgz", + "integrity": "sha512-FjxPYDRTn6Ec3V0arm1FtSpmP6V50wuph2yILpyvTKzjc76oDdoihXqM1DzOW5ubvCC8GivfCnNtfaRE8myJ7g==", + "dependencies": { + "@types/long": "^4.0.1", + "lodash.camelcase": "^4.3.0", + "long": "^4.0.0", + "protobufjs": "^6.11.3", + "yargs": "^16.2.0" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/@heroicons/react": { "version": "2.0.17", "resolved": "https://registry.npmjs.org/@heroicons/react/-/react-2.0.17.tgz", @@ -4031,6 +4738,60 @@ "url": "https://opencollective.com/popperjs" } }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" + }, "node_modules/@rc-component/context": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@rc-component/context/-/context-1.3.0.tgz", @@ -4767,6 +5528,11 @@ "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==" }, + "node_modules/@types/long": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", + "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==" + }, "node_modules/@types/mime": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz", @@ -9216,6 +9982,39 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/firebase": { + "version": "9.19.1", + "resolved": "https://registry.npmjs.org/firebase/-/firebase-9.19.1.tgz", + "integrity": "sha512-MeukV4NIk6usV1ZbnoA36gumK62JzxOZmF6OZyqkFwJ7edpAEyYNZXvExQlFsvnx2ery0UwNIvu4pKIZS3HiEQ==", + "dependencies": { + "@firebase/analytics": "0.9.5", + "@firebase/analytics-compat": "0.2.5", + "@firebase/app": "0.9.7", + "@firebase/app-check": "0.6.4", + "@firebase/app-check-compat": "0.3.4", + "@firebase/app-compat": "0.2.7", + "@firebase/app-types": "0.9.0", + "@firebase/auth": "0.22.0", + "@firebase/auth-compat": "0.3.7", + "@firebase/database": "0.14.4", + "@firebase/database-compat": "0.3.4", + "@firebase/firestore": "3.10.0", + "@firebase/firestore-compat": "0.3.6", + "@firebase/functions": "0.9.4", + "@firebase/functions-compat": "0.3.4", + "@firebase/installations": "0.6.4", + "@firebase/installations-compat": "0.2.4", + "@firebase/messaging": "0.12.4", + "@firebase/messaging-compat": "0.2.4", + "@firebase/performance": "0.6.4", + "@firebase/performance-compat": "0.2.4", + "@firebase/remote-config": "0.4.4", + "@firebase/remote-config-compat": "0.2.4", + "@firebase/storage": "0.11.2", + "@firebase/storage-compat": "0.3.2", + "@firebase/util": "1.9.3" + } + }, "node_modules/flat-cache": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", @@ -13384,6 +14183,11 @@ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" }, + "node_modules/load-script": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/load-script/-/load-script-1.0.0.tgz", + "integrity": "sha512-kPEjMFtZvwL9TaZo0uZ2ml+Ye9HUMmPwbYRJ324qF9tqMejwykJ5ggTyvzmrbBeapCAbk98BSbTeovHEEP1uCA==" + }, "node_modules/loader-runner": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", @@ -13429,6 +14233,11 @@ "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==" + }, "node_modules/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", @@ -13469,6 +14278,11 @@ "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==" }, + "node_modules/long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -13838,6 +14652,44 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" }, + "node_modules/node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-fetch/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/node-fetch/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/node-fetch/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/node-forge": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", @@ -15834,6 +16686,31 @@ "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.5.tgz", "integrity": "sha512-IJUkICM5dP5znhCckHSv30Q4b5/JA5enCtkRHYaOVOAocnH/1BQEYTC5NMfT3AVl/iXKdr3aqQbQn9DxyWknwA==" }, + "node_modules/protobufjs": { + "version": "6.11.3", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz", + "integrity": "sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg==", + "hasInstallScript": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/long": "^4.0.1", + "@types/node": ">=13.7.0", + "long": "^4.0.0" + }, + "bin": { + "pbjs": "bin/pbjs", + "pbts": "bin/pbts" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -16738,6 +17615,31 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" }, + "node_modules/react-player": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/react-player/-/react-player-2.12.0.tgz", + "integrity": "sha512-rymLRz/2GJJD+Wc01S7S+i9pGMFYnNmQibR2gVE3KmHJCBNN8BhPAlOPTGZtn1uKpJ6p4RPLlzPQ1OLreXd8gw==", + "dependencies": { + "deepmerge": "^4.0.0", + "load-script": "^1.0.0", + "memoize-one": "^5.1.1", + "prop-types": "^15.7.2", + "react-fast-compare": "^3.0.1" + }, + "peerDependencies": { + "react": ">=16.6.0" + } + }, + "node_modules/react-player/node_modules/memoize-one": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", + "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==" + }, + "node_modules/react-player/node_modules/react-fast-compare": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.1.tgz", + "integrity": "sha512-xTYf9zFim2pEif/Fw16dBiXpe0hoy5PxcD8+OwBnTtNLfIm3g6WxhKNurY+6OmdH1u6Ta/W/Vl6vjbYP1MFnDg==" + }, "node_modules/react-refresh": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", @@ -18149,6 +19051,27 @@ "webpack": "^5.0.0" } }, + "node_modules/styled-breakpoints": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/styled-breakpoints/-/styled-breakpoints-11.1.1.tgz", + "integrity": "sha512-KUpoMNPoNji5zyB/AyrY+KO/eHj87smfGMOgrAM8cH/fN2wtEW9tnwzPlQimxMyma0Y849XUnChbJBk2zws9Ig==", + "peerDependencies": { + "@emotion/react": "^11.0.0", + "react": "^18.x.x", + "styled-components": "^5.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "react": { + "optional": true + }, + "styled-components": { + "optional": true + } + } + }, "node_modules/styled-components": { "version": "5.3.8", "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.8.tgz", diff --git a/package.json b/package.json index 1b59f0ae76a655dd9f89bd941b7037800d43ae51..afe55cfb7e30f332616b66243614ac579eb2f21c 100644 --- a/package.json +++ b/package.json @@ -16,15 +16,18 @@ "bootstrap": "^5.2.3", "dayjs": "^1.11.7", "enzyme": "^3.11.0", + "firebase": "^9.19.1", "formik": "^2.2.9", "history": "^5.3.0", "jest-junit": "^15.0.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-icons": "^4.7.1", + "react-player": "^2.12.0", "react-router-dom": "^6.8.2", "react-scripts": "5.0.1", "react-select": "^5.7.0", + "styled-breakpoints": "^11.1.1", "styled-components": "^5.3.8", "web-vitals": "^2.1.4", "yup": "^1.0.2" diff --git a/public/user_pic_placeholder.png b/public/user_pic_placeholder.png new file mode 100644 index 0000000000000000000000000000000000000000..e7bdbefd2fb2b16ba52802312f822a1b69d587f4 Binary files /dev/null and b/public/user_pic_placeholder.png differ diff --git a/sonar-project.properties b/sonar-project.properties index 90b109355a3cb8b58550ea615f23a7a73b3a9fc9..64be1169c368eeaef2cb0ac95ac591e97a12f142 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -9,7 +9,7 @@ sonar.projectKey=$SONAR_PROJECT_KEY ## Path to sources sonar.sources=src -sonar.exclusions=coverage/*,public/*,**/test/**,**/reportWebVitals.js,**/index.js,**/api/**,**/__test__/**,**/*StyledComponents.*,**/tests/**,**/_test_/**,*.test.js,*.test.jsx,**/*.test.js,**/*.test.jsx +sonar.exclusions=coverage/*,public/*,**/test/**,**/reportWebVitals.js,**/index.js,**/api/**,**/__test__/**,**/*StyledComponents.*,**/tests/**,**/_test_/**,*.test.js,*.test.jsx,**/*.test.js,**/*.test.jsx,src/firebase.js,src/axiosInstance.js,src/setupTests.js #JS coverage report sonar.coverage.cobertura.reportPaths=coverage/cobertura-coverage.xml diff --git a/src/App.js b/src/App.js index ae88791f3e04fcfc77a68b9411ac6fd548ea3c04..78c3aece46cc58104ef507c88afb7044c8544fbd 100644 --- a/src/App.js +++ b/src/App.js @@ -9,7 +9,6 @@ import AuthRoutes from "./routes/AuthRoutes"; function App() { const { state } = React.useContext(AuthContext); - return ( <div className="App"> <Router> diff --git a/src/App.test.js b/src/App.test.js index 36134c27151e7b954b4be8e14d1abb219f66bfc3..a8a7e0c95e88687a82ae29bf47de66dd484c4a11 100644 --- a/src/App.test.js +++ b/src/App.test.js @@ -2,7 +2,7 @@ import { render, screen, fireEvent } from '@testing-library/react'; import App from './App'; import Sidebar from './components/Sidebar'; import { BrowserRouter as Router } from "react-router-dom"; -import Chat from './pages/Chat'; +import Chat from './pages/Chat/Chat'; import FindTutor from './pages/FindTutor'; import NotFound from './pages/NotFound'; import React from 'react'; @@ -12,6 +12,16 @@ import Profile from './pages/Profile'; import AuthContextProvider from "./contexts/AuthContext"; import Verification from '../src/pages/Verification'; +window.matchMedia = (query) => ({ + matches: false, + media: query, + onchange: null, + addListener: jest.fn(), // deprecated + removeListener: jest.fn(), // deprecated + addEventListener: jest.fn(), + removeEventListener: jest.fn(), + dispatchEvent: jest.fn(), +}) test('renders app, scroll and click buttons', () => { window.HTMLElement.prototype.scrollIntoView = function () { }; @@ -80,7 +90,11 @@ test('renders sidebar', () => { test('renders chat', () => { render( - <Chat /> + <AuthContextProvider> + <Router> + <Chat/> + </Router> + </AuthContextProvider> ); const linkElement = screen.getByText(/Chat/i); expect(linkElement).toBeInTheDocument(); diff --git a/src/components/__test__/ImageFull.test.jsx b/src/components/__test__/ImageFull.test.jsx new file mode 100644 index 0000000000000000000000000000000000000000..5c19885c5fdb7a5cf5e1d4df4d65cbb9173c12bf --- /dev/null +++ b/src/components/__test__/ImageFull.test.jsx @@ -0,0 +1,33 @@ +import React, { useState } from 'react'; +import { render, fireEvent, screen } from '@testing-library/react'; +import ImageFull from '../image_fullscreen/ImageFull'; + +const TestingWrapper = ({url}) => { + const [open, setOpen] = useState(false); + return ( + <ImageFull open={open} url={url} onClick={() => setOpen(!open)}/> + ) +} + +const url = "https://example.com/image.jpg"; +describe('ImageFull component', () => { + test('renders correctly', () => { + render(<ImageFull open={true} url={url} />); + expect(screen.getByRole("img")).toHaveAttribute("src", url); + }); + + test('displays full image when clicked', () => { + render( + <TestingWrapper url={url}/> + ); + const image = screen.getByAltText(url); + const imageContainer = screen.getByTestId('image_container'); + + expect(image).toBeInTheDocument(); + expect(imageContainer).not.toHaveClass('full'); + + fireEvent.click(image); + + expect(imageContainer).toHaveClass('full'); + }); +}); diff --git a/src/components/chat/BlankChatBox.jsx b/src/components/chat/BlankChatBox.jsx new file mode 100644 index 0000000000000000000000000000000000000000..1e38d84082368faf0181e8c5efe29f7b99504e4d --- /dev/null +++ b/src/components/chat/BlankChatBox.jsx @@ -0,0 +1,13 @@ +import React from "react"; +import "./ChatComponents.css"; +import { BlankChatContainer } from "./styled/chatBoxStyled"; +const BlankChatBox = () => { + return ( + <BlankChatContainer data-testid="blank-chat-container"> + <h1>Start Chatting Now!</h1> + <img src="app-icon.png" alt="peers" /> + </BlankChatContainer> + ); +}; + +export default BlankChatBox; diff --git a/src/components/chat/ChatBox.jsx b/src/components/chat/ChatBox.jsx new file mode 100644 index 0000000000000000000000000000000000000000..3aea48a904f1dca4f8b6f9f99182677cf904af5a --- /dev/null +++ b/src/components/chat/ChatBox.jsx @@ -0,0 +1,71 @@ +import React from "react"; +import BlankChatBox from "./BlankChatBox"; +import ReportOutlinedIcon from "@mui/icons-material/ReportOutlined"; +import Input from "./Input"; +import Messages from "./Messages"; +import ArrowBackIosIcon from "@mui/icons-material/ArrowBackIos"; +import { down } from "styled-breakpoints"; +import { useBreakpoint } from "styled-breakpoints/react-styled"; +import { + ChatBoxContainer, + ChatBoxHeader, + ChatBoxTop, + CloseBtn, + Img, + Profile, + ReportBtnContainer, + UserInfo, + UserInfoProfile, + Username, +} from "./styled/chatBoxStyled"; + +const ChatBox = ({ data, onClose, onClickBack, open, back }) => { + let { profile_pic, username } = data || {}; + const isMobile = useBreakpoint(down("md")); + + return ( + <ChatBoxContainer back={back} data-testid="chat_box"> + {open || isMobile ? ( + <> + <ChatBoxTop> + <ChatBoxHeader> + <UserInfo> + <UserInfoProfile> + {isMobile && <ArrowBackIosIcon + className="back_btn" + onClick={onClickBack} + />} + <Profile> + <Img + src={profile_pic || "user_pic_placeholder.png"} + alt="profile_picture" + /> + <Username>{username || "Unknown"}</Username> + </Profile> + </UserInfoProfile> + </UserInfo> + <ReportBtnContainer> + <ReportOutlinedIcon + sx={{ + color: "orange", + fontSize: "30px", + cursor: "pointer", + }} + /> + <CloseBtn data-testid="close_btn" onClick={onClose}> + Close + </CloseBtn> + </ReportBtnContainer> + </ChatBoxHeader> + </ChatBoxTop> + <Messages /> + <Input /> + </> + ) : ( + <BlankChatBox /> + )} + </ChatBoxContainer> + ); +}; + +export default ChatBox; diff --git a/src/components/chat/ChatComponents.css b/src/components/chat/ChatComponents.css new file mode 100644 index 0000000000000000000000000000000000000000..9d43e954e8fd87bc52e673615cd77a804839835b --- /dev/null +++ b/src/components/chat/ChatComponents.css @@ -0,0 +1,224 @@ +/* ChatBox */ +.chat_box { + +} + +.blank_chat_container > img { + +} + +.blank_chat_container > h1 { + +} + +.blank_chat_container { + +} +.chat_box_top { + +} + +.chat_box_header { + +} +.chat_box_header > .user_info { + +} + +.user_info_profile { + +} +.user_info_profile > .profile { + +} + +.user_info_profile > .back_btn { + cursor: pointer; + display: none; +} +.profile > span { + +} + +.chat_box_header_button_container { + +} +.chat_box_header_button_container > span { + +} + +.profile > img { + width: 50px; + height: 50px; + border-radius: 50%; + object-fit: cover; +} + + +/* Search */ +/* Input */ +.input { + display: flex; + width: 100%; + align-items: center; + justify-content: space-between; + max-height: 60px; + min-height: 60px; + overflow: hidden; + background-color: #1d7a6e; +} + +.input > textarea { + flex: 8; + width: 100%; + height: 100%; + padding: 10px; + border: none; + outline: none; + color: white; + font-size: 18px; + background-color: transparent; + overflow: hidden; +} + +.input > textarea::placeholder { + color: white; + opacity: 0.5; +} + +.input > textarea::-webkit-scrollbar { + display: none; +} + +.input_btn { + display: flex; + flex: 1; + align-items: center; + gap: 10px; + height: 100%; + cursor: pointer; +} +.input_btn > label { + cursor: pointer; +} + + + +/* Messages */ +.messagesss { + background-color: #ddddf7; + padding: 10px; + width: 100%; + height: 100%; + overflow-y: scroll; + -ms-overflow-style: none; /* IE and Edge */ + scrollbar-width: none; /* Firefox */ +} + +.messages::-webkit-scrollbar { + display: none; +} + +/* Message */ +.message { + +} + +.messageInfo { + +} + +.messageInfo > span { + font-size: 0.8rem; +} +.messageInfo > img { + width: 40px; + height: 40px; + object-fit: cover; + border-radius: 50%; +} + +.messageContent { + max-width: 80%; + display: flex; + flex-direction: column; + gap: 10px; +} +.messageContent > p { + background-color: white; + text-align: justify; + hyphens: auto; + -webkit-hyphens: auto; + word-break: break-all; + max-width: max-content; + margin: 0; + padding: 10px 20px; + border-radius: 0px 10px 10px 10px; +} + +.messageContent > img { + width: 50%; +} + +.message.owner { + flex-direction: row-reverse; +} + +.message.owner > .messageContent { + align-items: flex-end; +} + +.message.owner > .messageContent > p { + background-color: #8da4f1; + color: white; + border-radius: 10px 0px 10px 10px; +} + +/* Mobile */ +@media only screen and (max-width: 780px) { + .hidden { + flex: 0; + } + + .input_btn { + padding-right: 10px; + } + + .sendIcon { + padding: 2px; + } + + .addIcon { + padding: 2px; + } + .chat_info_container { + padding-bottom: 10px; + max-height: 80px; + } + + .user_info_profile > .profile { + gap: 0.5rem; + } + + .chat_box_header_button_container > span { + display: none; + } + + .profile > img { + width: 40px; + height: 40px; + } + + .user_info_profile > .back_btn { + cursor: pointer; + display: block; + } + + .messageInfo > span { + font-size: 10px; + } + + .messageContent > p { + padding: 10px; + } +} diff --git a/src/components/chat/ChatInfo.jsx b/src/components/chat/ChatInfo.jsx new file mode 100644 index 0000000000000000000000000000000000000000..f14f580ba06e32e9318ae7d827812aa7c020b2f0 --- /dev/null +++ b/src/components/chat/ChatInfo.jsx @@ -0,0 +1,37 @@ +import React from "react"; +import { + ChatInfoContainer, + ChatInfoStyled, + ImgContainer, + Img, + UserChatInfo, + ChatInfoHeader, + Username, + Time, + LatestMessage, +} from "./styled/chatInfoStyled"; + +const ChatInfo = ({ onClick, data }) => { + let { profile_pic, username, latest_message, time } = data || {}; + return ( + <ChatInfoContainer onClick={onClick} data-testid="chat_info_container"> + <ChatInfoStyled> + <ImgContainer> + <Img + src={profile_pic || "user_pic_placeholder.png"} + alt="profile_picture" + /> + </ImgContainer> + </ChatInfoStyled> + <UserChatInfo> + <ChatInfoHeader> + <Username onClick={onClick}>{username || "Unknown"}</Username> + <Time>{time || "13.00"}</Time> + </ChatInfoHeader> + <LatestMessage>{latest_message || ""}</LatestMessage> + </UserChatInfo> + </ChatInfoContainer> + ); +}; + +export default ChatInfo; diff --git a/src/components/chat/ChatSidebar.jsx b/src/components/chat/ChatSidebar.jsx new file mode 100644 index 0000000000000000000000000000000000000000..16cf691b314df1d8ca7fb86bea3b27a156132fd6 --- /dev/null +++ b/src/components/chat/ChatSidebar.jsx @@ -0,0 +1,30 @@ +import ArrowBackIosIcon from "@mui/icons-material/ArrowBackIos"; +import React from "react"; +import { useNavigate } from "react-router-dom"; +import ChatInfo from "./ChatInfo"; +import { + BackBtn, + ChatSidebarContainer, + SidebarBottom, + SidebarTop, +} from "./styled/chatSidebarStyled"; + +const ChatSidebar = ({ onClickChat, data, back }) => { + const navigate = useNavigate(); + + return ( + <ChatSidebarContainer back={back} data-testid="chat_sidebar"> + <SidebarTop> + <ArrowBackIosIcon sx={{cursor: 'pointer'}} /> + <BackBtn onClick={() => navigate("/")}>Back</BackBtn> + </SidebarTop> + <SidebarBottom> + {data?.map((user, idx) => ( + <ChatInfo key={idx} data={user} onClick={() => onClickChat(user)} /> + ))} + </SidebarBottom> + </ChatSidebarContainer> + ); +}; + +export default ChatSidebar; diff --git a/src/components/chat/Input.jsx b/src/components/chat/Input.jsx new file mode 100644 index 0000000000000000000000000000000000000000..9ad64f05b013129b802704eec93db3c148e3cd69 --- /dev/null +++ b/src/components/chat/Input.jsx @@ -0,0 +1,22 @@ +import React from "react"; +import "./ChatComponents.css"; +import AddCircleOutlineOutlinedIcon from "@mui/icons-material/AddCircleOutlineOutlined"; +import SendIcon from "@mui/icons-material/Send"; + +const Input = () => { + return ( + <div className="input"> + <textarea placeholder="Type a message"></textarea> + <div className="input_btn"> + <input type="file" id="file" style={{display: 'none'}} /> + <label htmlFor="file"> + <AddCircleOutlineOutlinedIcon className="addIcon" sx={{fontSize: '30px', color: 'white'}}/> + </label> + <SendIcon className="sendIcon" sx={{fontSize: '30px', color: 'white'}}/> + </div> + + </div> + ); +}; + +export default Input; diff --git a/src/components/chat/Message.jsx b/src/components/chat/Message.jsx new file mode 100644 index 0000000000000000000000000000000000000000..0d1f05c300732be5622df559bdb162d73732a1a1 --- /dev/null +++ b/src/components/chat/Message.jsx @@ -0,0 +1,33 @@ +import React, { useEffect, useRef, useState } from "react"; +import ImageFull from "../image_fullscreen/ImageFull"; +import { MessageContainer, Video } from "./styled/messageStyled"; +import {MessageImg, Img, MessageContent, MessageInfo, Span, Text} from "./styled/messageStyled"; + +const Message = ({ data }) => { + const { profile_pic, message, time, message_img, isOwner, message_vid } = data || {}; + const ref = useRef(); + const [open, setOpen] = useState(false); + + useEffect(() => { + ref.current?.scrollIntoView({ behavior: "smooth" }); + }, []); + return ( + <MessageContainer ref={ref} owner={isOwner}> + <ImageFull open={open} url={message_img} onClick={() => setOpen(!open)} /> + <MessageInfo> + <Img src={profile_pic || "user_pic_placeholder.png"} + alt="profile_pic"/> + <Span>{time || "Just Now"}</Span> + </MessageInfo> + <MessageContent> + {message && <Text>{message}</Text>} + {message_img && ( + <MessageImg src={message_img} alt="msg_pic" onClick={() => setOpen(!open)}/> + )} + {message_vid && <Video url={message_vid} light controls />} + </MessageContent> + </MessageContainer> + ); +}; + +export default Message; diff --git a/src/components/chat/Messages.jsx b/src/components/chat/Messages.jsx new file mode 100644 index 0000000000000000000000000000000000000000..7b7460d72f478175f9b2794b806b07b777a31326 --- /dev/null +++ b/src/components/chat/Messages.jsx @@ -0,0 +1,98 @@ +import React from "react"; +import Message from "./Message"; +import { MessagesContainer } from "./styled/messagesStyled"; + +const Messages = () => { + const getData = () => [ + { + profile_pic: "https://photos.hancinema.net/photos/largephoto1636274.jpg", + username: "Ahn Go Eun", + isOwner: true, + message_vid: "https://youtu.be/oxCt4V8HwxU", + message_img: + "https://imgx.parapuan.co/crop/11x0:1249x640/945x630/photo/2022/01/26/kekerasan-pada-perempuan2jpg-20220126042709.jpg", + message: "Hello, how are you doing? Have you watched this?", + }, + { + profile_pic: + "https://assets.pikiran-rakyat.com/crop/0x0:0x0/x/photo/2021/09/04/4102259019.jpg", + username: "Mawar Eva", + isOwner: false, + message_vid: "", + message_img: "", + message: "Long time no see", + }, + { + profile_pic: "https://photos.hancinema.net/photos/largephoto1636274.jpg", + username: "Ahn Go Eun", + isOwner: true, + message_vid: "https://youtu.be/oxCt4V8HwxU", + message_img: "", + message: "Hello, how are you doing?", + }, + { + profile_pic: + "https://assets.pikiran-rakyat.com/crop/0x0:0x0/x/photo/2021/09/04/4102259019.jpg", + username: "Mawar Eva", + isOwner: false, + message_img: "", + message: "Long time no see", + }, + { + profile_pic: "https://photos.hancinema.net/photos/largephoto1636274.jpg", + username: "Ahn Go Eun", + isOwner: true, + message_vid: "", + message_img: "", + message: "Hello, how are you doing?", + }, + { + profile_pic: "https://photos.hancinema.net/photos/largephoto1636274.jpg", + username: "Ahn Go Eun", + isOwner: true, + message_img: "", + message: "Hello, how are you doing?", + }, + { + profile_pic: + "https://assets.pikiran-rakyat.com/crop/0x0:0x0/x/photo/2021/09/04/4102259019.jpg", + username: "Mawar Eva", + isOwner: false, + message_vid: "", + message_img: "", + message: "Long time no see", + }, + { + profile_pic: "https://photos.hancinema.net/photos/largephoto1636274.jpg", + username: "Ahn Go Eun", + isOwner: true, + message_img: "", + message: "Hello, how are you doing?", + }, + { + profile_pic: "https://photos.hancinema.net/photos/largephoto1636274.jpg", + username: "Ahn Go Eun", + isOwner: true, + message_img: + "https://www.viu.com/ott/id/articles/wp-content/uploads/2023/03/preview-taxi-driver-2-episode-6-sub-indo-viu.jpg", + message: "Hello, how are you doing?", + }, + { + profile_pic: "https://photos.hancinema.net/photos/largephoto1636274.jpg", + username: "Ahn Go Eun", + isOwner: true, + message_img: + "https://jabarekspres.com/wp-content/uploads/2023/02/TX2.png", + message: "I wish you all the best", + }, + ]; + return ( + <MessagesContainer data-testid="messages"> + {getData()?.map((msg, idx) => ( + <Message data={msg} key={idx} /> + ))} + </MessagesContainer> + ); +}; + +export default Messages; diff --git a/src/components/chat/__test__/BlankChatBox.test.jsx b/src/components/chat/__test__/BlankChatBox.test.jsx new file mode 100644 index 0000000000000000000000000000000000000000..fd17f29282335cd0b6a7b7038b941dd9916d25c9 --- /dev/null +++ b/src/components/chat/__test__/BlankChatBox.test.jsx @@ -0,0 +1,13 @@ +import { render, screen } from '@testing-library/react'; +import React from 'react'; +import BlankChatBox from '../BlankChatBox'; + +describe('BlankChatBox component', () => { + it('renders a message and an image', () => { + render(<BlankChatBox />); + const messageElement = screen.getByText('Start Chatting Now!'); + const imageElement = screen.getByAltText('peers'); + expect(messageElement).toBeInTheDocument(); + expect(imageElement).toBeInTheDocument(); + }); +}); diff --git a/src/components/chat/__test__/ChatBox.test.jsx b/src/components/chat/__test__/ChatBox.test.jsx new file mode 100644 index 0000000000000000000000000000000000000000..77f2898c6ba0dc92479ac5059e7dbc22529d45df --- /dev/null +++ b/src/components/chat/__test__/ChatBox.test.jsx @@ -0,0 +1,44 @@ +import { render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import React from "react"; +import ChatBox from "../ChatBox"; + +describe("ChatBox", () => { + const data = { + profile_pic: "user_pic.png", + username: "John Doe", + }; + + beforeEach(()=> { + window.HTMLElement.prototype.scrollIntoView = jest.fn() + }) + + it("renders blank chat box when closed", () => { + render(<ChatBox open={false} />); + expect(screen.getByText("Start Chatting Now!")).toBeInTheDocument(); + }); + + it("renders chat box when open", () => { + render(<ChatBox data={data} open />); + expect(screen.getByText("John Doe")).toBeInTheDocument(); + expect(screen.getByTestId("SendIcon")).toBeInTheDocument(); + expect(screen.getByTestId("AddCircleOutlineOutlinedIcon")).toBeInTheDocument(); + expect(screen.getByTestId("ReportOutlinedIcon")).toBeInTheDocument(); + }); + + it("calls onClose when close button is clicked", () => { + const onClose = jest.fn(); + render(<ChatBox data={data} open onClose={onClose} />); + userEvent.click(screen.getByText("Close")); + expect(onClose).toHaveBeenCalled(); + }); + + it("displays default values when no data is provided", () => { + render(<ChatBox open />); + expect(screen.getByText("Unknown")).toBeInTheDocument(); + expect(screen.getByAltText("profile_picture")).toHaveAttribute( + "src", + "user_pic_placeholder.png" + ); + }); +}); diff --git a/src/components/chat/__test__/ChatInfo.test.jsx b/src/components/chat/__test__/ChatInfo.test.jsx new file mode 100644 index 0000000000000000000000000000000000000000..0f13e0fd3be06aa6b3311713fc875001a52e3cf3 --- /dev/null +++ b/src/components/chat/__test__/ChatInfo.test.jsx @@ -0,0 +1,41 @@ +import React from "react"; +import { render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import ChatInfo from "../ChatInfo"; + +describe("ChatInfo", () => { + const data = { + profile_pic: "user_pic.png", + username: "John Doe", + latest_message: "Hello world!", + time: "13:00", + }; + + it("renders default values when no data is provided", () => { + render(<ChatInfo />); + expect(screen.getByAltText("profile_picture")).toHaveAttribute( + "src", + "user_pic_placeholder.png" + ); + expect(screen.getByText("Unknown")).toBeInTheDocument(); + expect(screen.getByText("13.00")).toBeInTheDocument(); + }); + + it("renders chat info when data is provided", () => { + render(<ChatInfo data={data} />); + expect(screen.getByText("John Doe")).toBeInTheDocument(); + expect(screen.getByText("13:00")).toBeInTheDocument(); + expect(screen.getByText("Hello world!")).toBeInTheDocument(); + expect(screen.getByAltText("profile_picture")).toHaveAttribute( + "src", + "user_pic.png" + ); + }); + + it("calls onClick when container is clicked", () => { + const onClick = jest.fn(); + render(<ChatInfo data={data} onClick={onClick} />); + userEvent.click(screen.getByTestId("chat_info_container")); + expect(onClick).toHaveBeenCalled(); + }); +}); diff --git a/src/components/chat/__test__/ChatSidebar.test.jsx b/src/components/chat/__test__/ChatSidebar.test.jsx new file mode 100644 index 0000000000000000000000000000000000000000..d858d54885fd92f2e5acdda594e25b04846881c7 --- /dev/null +++ b/src/components/chat/__test__/ChatSidebar.test.jsx @@ -0,0 +1,55 @@ +import { fireEvent, render, screen } from "@testing-library/react"; +import React from "react"; +import { useNavigate } from "react-router-dom"; +import ChatSidebar from "../ChatSidebar"; + +jest.mock("react-router-dom", () => ({ + useNavigate: jest.fn(), +})); + +describe("ChatSidebar component", () => { + const mockData = [ + { + profile_pic: "https://photos.hancinema.net/photos/largephoto1636274.jpg", + username: "Ahn Go Eun", + latest_message: "Hello, how are you doing?", + }, + { + profile_pic: + "https://assets.pikiran-rakyat.com/crop/0x0:0x0/x/photo/2021/09/04/4102259019.jpg", + username: "Mawar Eva", + latest_message: "Long time no see", + }, + { + profile_pic: "", + username: "", + latest_message: "", + }, + ]; + + it("renders data correctly", () => { + render(<ChatSidebar data={mockData} />); + expect(screen.getByText("Ahn Go Eun")).toBeInTheDocument(); + expect(screen.getByText("Hello, how are you doing?")).toBeInTheDocument(); + expect(screen.getByText("Mawar Eva")).toBeInTheDocument(); + expect(screen.getByText("Long time no see")).toBeInTheDocument(); + }); + + it("calls onClickChat when chat info is clicked", () => { + const mockOnClickChat = jest.fn(); + render(<ChatSidebar data={mockData} onClickChat={mockOnClickChat} />); + // eslint-disable-next-line testing-library/no-node-access + const chatInfo = screen.getByText("Ahn Go Eun"); + fireEvent.click(chatInfo); + expect(mockOnClickChat).toHaveBeenCalledWith(mockData[0]); + }); + + it("navigates to home when Back is clicked", () => { + const navigateMock = jest.fn(); + useNavigate.mockReturnValue(navigateMock); + render(<ChatSidebar data={mockData} />); + const backButton = screen.getByText("Back"); + fireEvent.click(backButton); + expect(navigateMock).toHaveBeenCalledWith("/"); + }); +}); diff --git a/src/components/chat/__test__/Message.test.jsx b/src/components/chat/__test__/Message.test.jsx new file mode 100644 index 0000000000000000000000000000000000000000..df7d8e72258be9c3c7acc3f820c9984e59d07b49 --- /dev/null +++ b/src/components/chat/__test__/Message.test.jsx @@ -0,0 +1,83 @@ +import { fireEvent, render, screen } from "@testing-library/react"; +import React from "react"; +import Message from "../Message"; + +describe("Message", () => { + const data = { + profile_pic: "https://example.com/profile.jpg", + message: "Hello, world!", + time: "2022-04-05T10:30:00Z", + message_img: "https://example.com/image.jpg", + isOwner: true, + }; + + it("renders message content", () => { + render(<Message data={data} />); + expect(screen.getByText(data.message)).toBeInTheDocument(); + }); + + it("renders message image", () => { + render(<Message data={data} />); + expect(screen.getByAltText("msg_pic")).toHaveAttribute( + "src", + data.message_img + ); + }); + + it("renders message profile picture", () => { + render(<Message data={data} />); + expect(screen.getByAltText("profile_pic")).toHaveAttribute( + "src", + data.profile_pic + ); + }); + + it("renders message time", () => { + render(<Message data={data} />); + expect(screen.getByText(data.time)).toBeInTheDocument(); + }); + + it("scrolls into view on mount", () => { + const scrollIntoViewMock = jest.fn(); + window.HTMLElement.prototype.scrollIntoView = scrollIntoViewMock; + render(<Message data={data} />); + expect(scrollIntoViewMock).toHaveBeenCalled(); + }); + + it("should show full image when the image of the message clicked", () =>{ + render(<Message data={data}/>) + + const image = screen.getByAltText("msg_pic"); + const imageContainer = screen.getByTestId("image_container"); + + expect(screen.getByTestId("image_container")).not.toHaveClass("full"); + + fireEvent.click(image); + + expect(screen.getByTestId("image_container")).toHaveClass("full"); + expect(screen.getByAltText(data.message_img)).toHaveAttribute("src", data.message_img); + expect(screen.getByText(data.message)).toBeInTheDocument(); + expect(screen.getByText(data.time)).toBeInTheDocument(); + expect(screen.getByAltText("profile_pic")).toBeInTheDocument(); + + fireEvent.click(imageContainer); + expect(screen.getByTestId("image_container")).not.toHaveClass("full"); + }) + + it("should render default profile image when profile_pic not available", () => { + render(<Message data={{...data, profile_pic: ""}}/>) + + const profile_pic = screen.getByAltText("profile_pic"); + + expect(profile_pic).toHaveAttribute("src", "user_pic_placeholder.png"); + }) + + it("should render properly when no data passed", () => { + render(<Message/>) + const profile_pic = screen.getByAltText("profile_pic"); + + expect(profile_pic).toHaveAttribute("src", "user_pic_placeholder.png"); + expect(screen.queryAllByAltText("msg_pic")).toHaveLength(0); + expect(screen.queryAllByTestId("message_text")).toHaveLength(0); + }) +}); diff --git a/src/components/chat/index.js b/src/components/chat/index.js new file mode 100644 index 0000000000000000000000000000000000000000..755e26b42be2d8af68619e8842ad32302172bcba --- /dev/null +++ b/src/components/chat/index.js @@ -0,0 +1,7 @@ +/* istanbul ignore file */ +export {default as ChatBox} from "./ChatBox" +export {default as ChatSidebar} from "./ChatSidebar" +export {default as Input} from "./Input" +export {default as Message} from "./Message" +export {default as ChatInfo} from "./ChatInfo" +export {default as Messages} from "./Messages" \ No newline at end of file diff --git a/src/components/chat/styled/chatBoxStyled.js b/src/components/chat/styled/chatBoxStyled.js new file mode 100644 index 0000000000000000000000000000000000000000..b8ff9ae82c15f2b975fa33877d322b671bb8c207 --- /dev/null +++ b/src/components/chat/styled/chatBoxStyled.js @@ -0,0 +1,92 @@ +import styled from "styled-components"; + +export const ChatBoxContainer = styled.div` + flex: 3; + display: flex; + flex-direction: column; + height: 100vh; + overflow: hidden; + + @media only screen and (max-width: 780px) { + flex: ${(props) => props.back && "0"}; + } +`; +export const ChatBoxTop = styled.div` + display: flex; + align-items: center; + color: white; + flex-basis: 10%; + flex-grow: 0; + flex-shrink: 0; + background-color: #1d7a6e; + max-height: 60px; + width: 100%; +`; +export const ChatBoxHeader = styled.div` + flex: 1; + display: flex; + padding: 10px 20px 10px 10px; +`; +export const UserInfo = styled.div` + display: flex; + flex: 1; + align-items: center; + justify-content: space-between; +`; +export const UserInfoProfile = styled.div` + display: flex; + align-items: center; + gap: 0; +`; +export const Username = styled.span``; +export const Profile = styled.div` + display: flex; + align-items: center; + gap: 1rem; + ${Username} { + font-weight: 700px; + font-size: 18px; + } +`; +export const Img = styled.img` + width: 50px; + height: 50px; + border-radius: 50%; + object-fit: cover; +`; + +export const ReportBtnContainer = styled.div` + display: flex; + align-items: center; + gap: 10px; +`; +export const CloseBtn = styled.span` + cursor: pointer; + text-transform: uppercase; + @media only screen and (max-width: 780px) { + display: none; + } +`; + +/* Blank Chat Container */ +export const BlankChatContainer = styled.div` + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; + flex: 1; + overflow: hidden; + + > img { + width: 400px; + height: 400px; + object-fit: cover; + opacity: 0.8; + } + + > h1 { + font-weight: 700; + color: gray; + opacity: 0.5; + } +`; diff --git a/src/components/chat/styled/chatInfoStyled.js b/src/components/chat/styled/chatInfoStyled.js new file mode 100644 index 0000000000000000000000000000000000000000..0e567863e0ed67a29e37f27057874261ebc812fb --- /dev/null +++ b/src/components/chat/styled/chatInfoStyled.js @@ -0,0 +1,68 @@ +import styled from "styled-components"; + +export const ChatInfoContainer = styled.div` + display: flex; + align-items: center; + border-bottom: 0.5px solid #1d7a6e; + -webkit-background-clip: padding-box; /* for Safari */ + background-clip: padding-box; /* for IE9+, Firefox 4+, Opera, Chrome */ + padding: 5px; + color: white; + max-height: 70px; + cursor: pointer; + + &:hover { + background-color: #1d7a6e; + } + + @media only screen and (max-width: 780px) { + padding-bottom: 10px; + max-height: 80px; + } +`; + +export const ChatInfoStyled = styled.div` + display: flex; + align-items: center; + justify-content: center; + gap: 0.8rem; + padding: 10px 15px 10px 0px; + cursor: pointer; +`; + +export const ImgContainer = styled.div` + flex: 0.5; +`; +export const Img = styled.img` + width: 50px; + height: 50px; + border-radius: 50%; + object-fit: cover; +`; +export const UserChatInfo = styled.div` + display: flex; + flex-direction: column; + flex: 3; +`; +export const ChatInfoHeader = styled.div` + padding-bottom: 3px; + display: flex; + align-items: center; + justify-content: space-between; +`; +export const Username = styled.span` + font-weight: 600; +`; +export const Time = styled.p` + font-weight: 200; + font-size: 11px; +`; +export const LatestMessage = styled.p` + font-weight: 300; + font-size: 0.8rem; + overflow: hidden; + display: -webkit-box; + -webkit-line-clamp: 1; /* number of lines to show */ + line-clamp: 1; + -webkit-box-orient: vertical; +`; diff --git a/src/components/chat/styled/chatSidebarStyled.js b/src/components/chat/styled/chatSidebarStyled.js new file mode 100644 index 0000000000000000000000000000000000000000..cce4eb81ffb069e77c7f56d6d825469365b1e5fe --- /dev/null +++ b/src/components/chat/styled/chatSidebarStyled.js @@ -0,0 +1,47 @@ +import styled from "styled-components"; + +export const ChatSidebarContainer = styled.div` + display: flex; + flex-direction: column; + background-color: #279686; + flex: 1; + border-right: 1px solid lightgray; + overflow: hidden; + height: 100vh; + + @media only screen and (max-width: 425px) { + flex: ${(props) => props.back && "0"}; + } +`; +export const SidebarTop = styled.div` + display: flex; + flex-basis: 10%; + flex-grow: 0; + flex-shrink: 0; + top: 0; + width: 100vw; + z-index: 9999; + align-items: center; + padding-left: 5px; + color: white; + background-color: #1d7a6e; + max-height: 60px; +`; +export const SidebarBottom = styled.div` + flex-basis: 90%; + flex-grow: 0; + flex-shrink: 0; + max-height: 100vh; + overflow-y: scroll; + -ms-overflow-style: none; /* IE and Edge */ + scrollbar-width: none; /* Firefox */ + + &::-webkit-scrollbar { + display: none; + } +`; +export const BackBtn = styled.span` + cursor: pointer; + font-weight: 400; + font-size: 20px; +`; diff --git a/src/components/chat/styled/inputStyled.js b/src/components/chat/styled/inputStyled.js new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/components/chat/styled/messageStyled.js b/src/components/chat/styled/messageStyled.js new file mode 100644 index 0000000000000000000000000000000000000000..93f44ee06d7852b695c41075210f49ecfb908859 --- /dev/null +++ b/src/components/chat/styled/messageStyled.js @@ -0,0 +1,78 @@ +import styled from "styled-components"; +import ReactPlayer from 'react-player' + +export const Img = styled.img` + width: 40px; + height: 40px; + object-fit: cover; + border-radius: 50%; +`; + +export const Video = styled(ReactPlayer)` + width: 200px; +` +export const Span = styled.span``; +export const MessageInfo = styled.div` + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + color: gray; + font-weight: 300px; + + ${Span} { + font-size: 0.8rem; + } + + @media only screen and (max-width: 780px) { + ${Span} { + font-size: 10px; + } + } +`; + +export const Text = styled.p``; +export const MessageImg = styled.img``; +export const MessageContent = styled.div` + max-width: 80%; + display: flex; + flex-direction: column; + gap: 10px; + ${Text} { + background-color: white; + text-align: justify; + hyphens: auto; + -webkit-hyphens: auto; + word-break: break-all; + max-width: max-content; + margin: 0; + padding: 10px 20px; + border-radius: 0px 10px 10px 10px; + } + ${MessageImg} { + width: 50%; + } + + @media only screen and (max-width: 780px) { + ${Text} { + padding: 10px; + } + } +`; + +export const MessageContainer = styled.div` + display: flex; + gap: 20px; + margin-bottom: 20px; + flex-direction: ${(props) => props.owner && "row-reverse"}; + + ${MessageContent} { + align-items: ${(props) => props.owner && "flex-end"}; + + ${Text} { + background-color: ${(props) => props.owner && "#8da4f1"}; + color: ${(props) => props.owner && "white"}; + border-radius: ${(props) => props.owner && "10px 0px 10px 10px"}; + } + } +`; diff --git a/src/components/chat/styled/messagesStyled.js b/src/components/chat/styled/messagesStyled.js new file mode 100644 index 0000000000000000000000000000000000000000..2cf2812bddd1b0f7678738b0e4a1457fcaa3f4bc --- /dev/null +++ b/src/components/chat/styled/messagesStyled.js @@ -0,0 +1,15 @@ +import styled from "styled-components"; + +export const MessagesContainer = styled.div` + background-color: #ddddf7; + padding: 10px; + width: 100%; + height: 100%; + overflow-y: scroll; + -ms-overflow-style: none; /* IE and Edge */ + scrollbar-width: none; /* Firefox */ + + &::-webkit-scrollbar { + display: none; + } +`; diff --git a/src/components/image_fullscreen/ImageFull.jsx b/src/components/image_fullscreen/ImageFull.jsx new file mode 100644 index 0000000000000000000000000000000000000000..81bc1f58d7e9a9f11e09561fe1ce1dc4ef4124c4 --- /dev/null +++ b/src/components/image_fullscreen/ImageFull.jsx @@ -0,0 +1,15 @@ +import React from "react"; +import "./ImageFulscreen.css"; + +const ImageFull = ({ open, url, onClick }) => { + return ( + <> + <div className={`imageOverlay ${open ? "show" : ""}`}></div> + <div data-testid="image_container" className={`image ${open ? "full" : ""}`} onClick={onClick}> + <img src={url} alt={url} /> + </div> + </> + ); +}; + +export default ImageFull; diff --git a/src/components/image_fullscreen/ImageFulscreen.css b/src/components/image_fullscreen/ImageFulscreen.css new file mode 100644 index 0000000000000000000000000000000000000000..46725a47deed60cafd7c1cacaaeafa3590bad7bd --- /dev/null +++ b/src/components/image_fullscreen/ImageFulscreen.css @@ -0,0 +1,48 @@ +.image { + display: none; +} + +.imageOverlay { + display: none; +} + +.imageOverlay.show { + position: absolute; + overflow: auto; + display: flex; + width: 100vw; + height: 100vh; + z-index: 9999; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.5); +} + +.image.full { + z-index: 99999; + overflow: auto; + position: absolute; + display: flex; + align-items: center; + justify-content: center; + width: 100%; + height: 100%; + top: 0; + left: 0; + right: 0; + bottom: 0; + -ms-overflow-style: none; /* IE and Edge */ + scrollbar-width: none; /* Firefox */ +} + +.image.full > img { + max-width: 100%; + max-height: 100%; + object-fit: cover; +} + +.image.full::-webkit-scrollbar { + display: none; +} \ No newline at end of file diff --git a/src/components/loginForm/LoginForm.jsx b/src/components/loginForm/LoginForm.jsx index 75b0678b4cfc1e3dbb5279c546d80d86a9daada2..5635c90c1bd527b8e6398078f780b8a71162c00f 100644 --- a/src/components/loginForm/LoginForm.jsx +++ b/src/components/loginForm/LoginForm.jsx @@ -43,6 +43,7 @@ const LoginForm = () => { token: response.data["access"], }, }); + console.log(response.data) navigate("/"); } catch (err) { console.log("Error: ", err); diff --git a/src/components/registerForm/RegisterForm.jsx b/src/components/registerForm/RegisterForm.jsx index d806b2c9556e9f180fd4b34b4499ffbb5f2a7c7d..ca755ecea19f69f66969c1e36754607742c3d704 100644 --- a/src/components/registerForm/RegisterForm.jsx +++ b/src/components/registerForm/RegisterForm.jsx @@ -50,7 +50,7 @@ const RegisterForm = () => { formData.append("date_of_birth", values.date_of_birth); formData.append("profile_picture", selectedFile); await axios.post( - "https://peers-backend-dev.up.railway.app/api/auth/register/", + "http://localhost:8000/api/auth/register/", formData, { headers: { diff --git a/src/contexts/ChatContext.js b/src/contexts/ChatContext.js new file mode 100644 index 0000000000000000000000000000000000000000..e9dae1134caf1776892a1e218e6d08f0b3bb3783 --- /dev/null +++ b/src/contexts/ChatContext.js @@ -0,0 +1,36 @@ +import axios from "axios"; +import React, { useEffect, useState, createContext } from "react"; + +export const ChatContext = createContext(); + +const ChatContextProvider = ({ children }) => { + const [currentUser, setCurrentUser] = useState({}); + + useEffect(() => { + const getCurrentUser = async () => { + try { + const response = await axios.get( + `${process.env.REACT_APP_API_URL}/api/auth/user/profile`, + { + headers: { + Authorization: `Bearer ${JSON.parse( + localStorage.getItem("token") + )}`, + }, + } + ); + setCurrentUser(response.data.user); + } catch (err) { + console.error(err); + } + }; + getCurrentUser(); + }, []); + return ( + <ChatContext.Provider value={{ currentUser }}> + {children} + </ChatContext.Provider> + ); +}; + +export default ChatContextProvider; diff --git a/src/firebase.js b/src/firebase.js new file mode 100644 index 0000000000000000000000000000000000000000..ddee304e7a1d5a863020df3768c3f442ad029503 --- /dev/null +++ b/src/firebase.js @@ -0,0 +1,18 @@ +/* istanbul ignore file */ +import { initializeApp } from "firebase/app"; +import { getStorage } from "firebase/storage"; +import { getFirestore } from "firebase/firestore"; +const firebaseConfig = { + apiKey: process.env.REACT_APP_FIREBASE_API_KEY, + authDomain: "peers-staging-9d8ed.firebaseapp.com", + projectId: process.env.REACT_APP_FIREBASE_PROJECT_ID, + storageBucket: process.env.REACT_APP_FIREBASE_STORAGE_BUCKET, + messagingSenderId: process.env.REACT_APP_FIREBASE_MESSAGING_SENDER_ID, + appId: process.env.REACT_APP_FIREBASE_APP_ID, + measurementId: process.env.REACT_APP_FIREBASE_MEASUREMENT_ID +}; + +// Initialize Firebase +export const app = initializeApp(firebaseConfig); +export const storage = getStorage(); +export const db = getFirestore() diff --git a/src/index.js b/src/index.js index 5ca21636afad73f548e58564e2a7d74c7a39ed0b..79ee243364c7dc3bb494e8e44db0c2a3a76e9163 100644 --- a/src/index.js +++ b/src/index.js @@ -1,15 +1,18 @@ /* istanbul ignore file */ -import React from 'react'; -import ReactDOM from 'react-dom/client'; -import './index.css'; -import App from './App'; -import AuthContextProvider from './contexts/AuthContext'; +import React from "react"; +import ReactDOM from "react-dom/client"; +import "./index.css"; +import App from "./App"; +import AuthContextProvider from "./contexts/AuthContext"; +import ChatContextProvider from "./contexts/ChatContext"; -const root = ReactDOM.createRoot(document.getElementById('root')); +const root = ReactDOM.createRoot(document.getElementById("root")); root.render( <React.StrictMode> <AuthContextProvider> - <App /> + <ChatContextProvider> + <App /> + </ChatContextProvider> </AuthContextProvider> </React.StrictMode> -); \ No newline at end of file +); diff --git a/src/pages/Chat.jsx b/src/pages/Chat.jsx deleted file mode 100644 index 6c3d136bb5e5f9e801cb358cb8286efc1a140cb1..0000000000000000000000000000000000000000 --- a/src/pages/Chat.jsx +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react'; - -const Chat = () => { - return ( - <div> - <h1>Chat page</h1> - </div> - ); -}; - -export default Chat; \ No newline at end of file diff --git a/src/pages/Chat/Chat.jsx b/src/pages/Chat/Chat.jsx new file mode 100644 index 0000000000000000000000000000000000000000..3b758a943eceae392b849846fcb81ce540a7bd80 --- /dev/null +++ b/src/pages/Chat/Chat.jsx @@ -0,0 +1,94 @@ +import React, { useState } from "react"; +import { ChatSidebar, ChatBox } from "../../components/chat"; +import { ChatPage, ChatPageWrapper } from "./chatStyled"; + +const Chat = ({ data }) => { + const [open, setOpen] = useState(false); + const [user, setUser] = useState({}); + const [back, setBack] = useState(true); + + const handleOnClickChat = (currentUser) => { + setUser(currentUser); + setOpen(true); + setBack(false); + }; + + /* istanbul ignore next */ + // eslint-disable-next-line no-unused-vars + const getData = () => [ + { + profile_pic: "https://photos.hancinema.net/photos/largephoto1636274.jpg", + username: "Ahn Go Eun", + latest_message: "Hello, how are you doing?", + }, + { + profile_pic: + "data:image/jpeg;base64,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", + username: "Tony", + latest_message: + "Long time no see! how have u been? are you still mad at me?", + }, + { + profile_pic: "", + username: "", + latest_message: "", + }, + { + profile_pic: + "https://akcdn.detik.net.id/visual/2017/06/21/fdac2685-1e5c-424e-8a8f-35b7738d74be_169.jpg?w=650", + username: "Steve", + latest_message: "Assemble!", + }, + { + profile_pic: + "https://assets.pikiran-rakyat.com/crop/0x0:0x0/x/photo/2021/09/04/4102259019.jpg", + username: "Mawar Eva", + latest_message: "Long time no see", + }, + { + profile_pic: + "data:image/jpeg;base64,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", + username: "Kim So Hyun", + latest_message: "Hello, how are you doing?", + }, + { + profile_pic: + "data:image/jpeg;base64,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", + username: "Geewoni", + latest_message: "Long time no see", + }, + { + profile_pic: + "data:image/jpeg;base64,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", + username: "Tom", + latest_message: "I see nobody at the headquarters, where are you guys?", + }, + { + profile_pic: + "https://thumb.intipseleb.com/media/frontend/thumbs3/2022/12/05/638db0f73eb2a-christy-jkt48_663_372.jpg", + username: "Christy", + latest_message: "Hello, how are you doing?", + }, + ]; + + return ( + <ChatPage> + <ChatPageWrapper> + <ChatSidebar + back={!back} + data={data} + onClickChat={(e) => handleOnClickChat(e)} + /> + <ChatBox + back={back} + open={open} + data={user} + onClickBack={() => setBack(true)} + onClose={() => setOpen(false)} + /> + </ChatPageWrapper> + </ChatPage> + ); +}; + +export default Chat; diff --git a/src/pages/Chat/chatStyled.js b/src/pages/Chat/chatStyled.js new file mode 100644 index 0000000000000000000000000000000000000000..39dbf4e5975dea20ac1581b0b0cb745f9a526b80 --- /dev/null +++ b/src/pages/Chat/chatStyled.js @@ -0,0 +1,15 @@ +import styled from "styled-components"; + +export const ChatPage = styled.div` + display: flex; + height: 100vh; + background-color: white; +`; + +export const ChatPageWrapper = styled.div` + display: flex; + flex-direction: row; + justify-content: space-between; + flex: 1; + overflow: hidden; +`; diff --git a/src/pages/__test__/Chat.test.jsx b/src/pages/__test__/Chat.test.jsx new file mode 100644 index 0000000000000000000000000000000000000000..53786eb82ad6236a20666890a1c52548bf007931 --- /dev/null +++ b/src/pages/__test__/Chat.test.jsx @@ -0,0 +1,55 @@ +import { fireEvent, render, screen } from "@testing-library/react"; +import React from "react"; +import { BrowserRouter } from "react-router-dom"; +import AuthContextProvider from "../../contexts/AuthContext"; +import ChatContextProvider from "../../contexts/ChatContext"; +import Chat from "../Chat/Chat"; + +const data = [ + { + profile_pic: + "https://assets.pikiran-rakyat.com/crop/0x0:0x0/x/photo/2021/09/04/4102259019.jpg", + username: "Mawar Eva", + latest_message: "Long time no see", + }, +]; + + +describe("Chat component", () => { + it("renders ChatSidebar and ChatBox", () => { + render(<AuthContextProvider> + <ChatContextProvider> + <Chat/> + </ChatContextProvider> + </AuthContextProvider>, + { wrapper: BrowserRouter }); + expect(screen.getByTestId("chat_sidebar")).toBeInTheDocument(); + expect(screen.getByTestId("chat_box")).toBeInTheDocument(); + }); + + + + it('closes the ChatBox component when the "Close" button is clicked', () => { + render(<AuthContextProvider> + <ChatContextProvider> + <Chat data={data}/> + </ChatContextProvider> + </AuthContextProvider>, + { wrapper: BrowserRouter }); + fireEvent.click(screen.getByText("Mawar Eva")); + fireEvent.click(screen.getByTestId('close_btn')); + expect(screen.queryAllByTestId('messsages')).toHaveLength(0); + }); + + it('opens the ChatBox component when a user is clicked on the ChatSidebar component', () => { + render(<AuthContextProvider> + <ChatContextProvider> + <Chat data={data}/> + </ChatContextProvider> + </AuthContextProvider>, + { wrapper: BrowserRouter }); + + fireEvent.click(screen.getByText("Mawar Eva")); + expect(screen.queryAllByTestId('messages')).toHaveLength(1); + }); +}); diff --git a/src/routes/AuthRoutes.jsx b/src/routes/AuthRoutes.jsx index 7dee154b481b0c88e33a73fa20f046be83dd8d46..ce9fd673526dfb539458057724816a378a9a6bb4 100644 --- a/src/routes/AuthRoutes.jsx +++ b/src/routes/AuthRoutes.jsx @@ -1,7 +1,7 @@ -import React from 'react'; +import React from "react"; import { Routes, Route } from "react-router-dom"; -import Sidebar from '../components/Sidebar.jsx'; -import Chat from "../pages/Chat.jsx"; +import Sidebar from "../components/Sidebar.jsx"; +import Chat from "../pages/Chat/Chat.jsx"; import FindTutor from "../pages/FindTutor.jsx"; import NotFound from "../pages/NotFound"; import TutorDashboard from "../pages/TutorDashboard"; @@ -13,21 +13,15 @@ import TutorDetail from '../pages/TutorDetail/TutorDetail.jsx'; import { AuthContext } from "../contexts/AuthContext"; function AuthRoutes() { - const { state } = React.useContext(AuthContext); - - return ( + return ( <Routes> <Route path="/" element={ <Sidebar> <FindTutor /> </Sidebar>} /> - <Route - path="/chat" element={ - <Sidebar> - <Chat /> - </Sidebar>} /> + <Route path="/chat" element={<Chat />} /> <Route path="/profile" element={ <Sidebar> @@ -36,14 +30,14 @@ function AuthRoutes() { <Route path="/verify" element={ <Sidebar> - <Verification/> + <Verification /> </Sidebar>} /> <Route path="/schedule" element={ - <Sidebar> - <TutorScheduleForm/> - </Sidebar>} /> - + <Sidebar> + <TutorScheduleForm /> + </Sidebar>} /> + <Route path="/tutor" element={ !state.isTutor ? <RegisterTutorForm /> : @@ -52,11 +46,11 @@ function AuthRoutes() { </Sidebar> } /> <Route path="/tutor/:id" element={ - <TutorDetail /> + <TutorDetail /> } /> <Route path="*" element={<NotFound />} /> </Routes> ); } -export default AuthRoutes; \ No newline at end of file +export default AuthRoutes; diff --git a/src/setupTests.js b/src/setupTests.js index 8f2609b7b3e0e3897ab3bcaad13caf6876e48699..7f5de8f6bdf334e847a7048aacab2b0ee2c9d814 100644 --- a/src/setupTests.js +++ b/src/setupTests.js @@ -3,3 +3,16 @@ // expect(element).toHaveTextContent(/react/i) // learn more: https://github.com/testing-library/jest-dom import '@testing-library/jest-dom'; +window.matchMedia = (query) => ({ + matches: false, + media: query, + onchange: null, + addListener: jest.fn(), // deprecated + removeListener: jest.fn(), // deprecated + addEventListener: jest.fn(), + removeEventListener: jest.fn(), + dispatchEvent: jest.fn(), +}) + +let scrollIntoViewMock = jest.fn(); +window.HTMLElement.prototype.scrollIntoView = scrollIntoViewMock; \ No newline at end of file diff --git a/src/tests/contexts/AuthContext.test.js b/src/tests/contexts/AuthContext.test.js index b54b1b2d1f9e3a5bd29827529c53a55aefaf5663..45d24d875d8d5901a2fb76c4a35d3f6678f3c64c 100644 --- a/src/tests/contexts/AuthContext.test.js +++ b/src/tests/contexts/AuthContext.test.js @@ -14,7 +14,7 @@ test('render app with not auth', () => { }); test('render app with auth', () => { - localStorage.setItem("token", "TEST_TOKEN"); + localStorage.setItem("token", JSON.stringify("TEST_TOKEN")); render( <AuthContextProvider> <App /> diff --git a/src/tests/contexts/ChatContext.test.jsx b/src/tests/contexts/ChatContext.test.jsx new file mode 100644 index 0000000000000000000000000000000000000000..fb54b13072ab0a841d234c7dfa271ef5a96b7663 --- /dev/null +++ b/src/tests/contexts/ChatContext.test.jsx @@ -0,0 +1,73 @@ +import axios from "axios"; +import React, { useContext } from "react"; +import { act, render, screen } from "@testing-library/react"; +import ChatContextProvider, { ChatContext } from "../../contexts/ChatContext"; + +jest.mock("axios"); + +describe("ChatContextProvider", () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + it("sets currentUser context value after fetching the user data", async () => { + const user = { name: "John Doe" }; + axios.get.mockResolvedValueOnce({ data: { user } }); + + const TestComponent = () => { + const { currentUser } = useContext(ChatContext); + return <div data-testid="current-user">{currentUser.name}</div>; + }; + + render( + <ChatContextProvider> + <TestComponent /> + </ChatContextProvider> + ); + + await act(async () => { + await new Promise((resolve) => setTimeout(resolve, 0)); // wait for promises to resolve + }); + + expect(screen.getByTestId("current-user")).toHaveTextContent(user.name); + expect(axios.get).toHaveBeenCalledTimes(1); + expect(axios.get).toHaveBeenCalledWith( + `${process.env.REACT_APP_API_URL}/api/auth/user/profile`, + { + headers: { + Authorization: `Bearer ${JSON.parse(localStorage.getItem("token"))}`, + }, + } + ); + }); + + it("handles error in fetching user data", async () => { + axios.get.mockRejectedValueOnce(new Error("Something went wrong!")); + + const TestComponent = () => { + const { currentUser } = useContext(ChatContext); + return <div data-testid="current-user">{currentUser.name}</div>; + }; + + render( + <ChatContextProvider> + <TestComponent /> + </ChatContextProvider> + ); + + await act(async () => { + await new Promise((resolve) => setTimeout(resolve, 0)); // wait for promises to resolve + }); + + expect(screen.getByTestId("current-user")).toHaveTextContent(""); + expect(axios.get).toHaveBeenCalledTimes(1); + expect(axios.get).toHaveBeenCalledWith( + `${process.env.REACT_APP_API_URL}/api/auth/user/profile`, + { + headers: { + Authorization: `Bearer ${JSON.parse(localStorage.getItem("token"))}`, + }, + } + ); + }); +});