├── .gitignore ├── src ├── css │ ├── shared │ │ ├── misc.scss │ │ ├── base.scss │ │ └── mixin.scss │ ├── variables.scss │ ├── call-modal.scss │ ├── app.scss │ ├── main-window.scss │ └── call-window.scss ├── pages │ ├── AdminSign │ │ ├── AdminSign.css │ │ └── AdminSign.js │ ├── Login │ │ ├── Login.css │ │ └── Login.js │ ├── Signup │ │ ├── Signup.css │ │ └── Signup.js │ ├── Chat │ │ ├── socket.js │ │ ├── Emitter.js │ │ ├── CallModal.js │ │ ├── MediaDevice.js │ │ ├── MainWindow.js │ │ ├── CallWindow.js │ │ └── PeerConnection.js │ ├── HomePage │ │ ├── HomePage.css │ │ └── HomePage.js │ ├── UserSign │ │ ├── UserSign.css │ │ └── UserSign.js │ ├── GuestSign │ │ ├── GuestSign.css │ │ └── GuestSign.js │ └── AdminManage │ │ └── AdminManage.css ├── assets │ ├── mdb-react-small.png │ ├── logo.svg │ └── React-icon.svg ├── App │ ├── App.test.js │ ├── App.js │ └── Routes.js ├── dataservice │ ├── request.js │ └── index.js ├── config │ ├── config.js │ └── country.js ├── index.js ├── components │ ├── menuLink.js │ ├── OnTyping │ │ ├── OnTyping.js │ │ └── OnTyping.css │ ├── LoadingModal.js │ ├── NavBar.js │ ├── ErrorModal.js │ ├── docsLink.js │ ├── FullScreenImage │ │ ├── FullScreenImage.css │ │ └── FullScreenImage.js │ ├── SearchSettingBox │ │ ├── SearchSettingBox.css │ │ └── SearchSettingBox.js │ ├── sectionContainer.js │ ├── ReportBox │ │ ├── ReportBox.css │ │ └── ReportBox.js │ ├── CallModal │ │ ├── CallModal.js │ │ └── CallModal.css │ ├── ProfileBox │ │ ├── ProfileBox.css │ │ └── ProfileBox.js │ ├── UserList.js │ └── CallWindow │ │ ├── callWindow_backup.js │ │ ├── CallWindow.css │ │ └── CallWindow.js ├── index.css ├── utils │ └── storage.js └── registerServiceWorker.js ├── public ├── 07.jpg ├── max.png ├── min.png ├── sign-bg.jpg ├── background.jpg ├── chat-icon.png ├── find-user.webp ├── login-bg1.jpg ├── profile │ ├── male.png │ ├── female.png │ ├── 5ec581040366707806390da0.png │ ├── 5ec5810a0366707806390da1.png │ ├── 5ec581120366707806390da2.png │ ├── 5ec581130366707806390da3.png │ ├── 5ec5811d0366707806390da4.png │ ├── 5ec581220366707806390da5.png │ ├── 5ec5d703e969899f26d1d3c9.png │ ├── 5ec5d704e969899f26d1d3ca.png │ ├── 5ec5d884e969899f26d1d3cb.png │ ├── 5ec5d888e969899f26d1d3cc.png │ ├── 5ec5d88ee969899f26d1d3cd.png │ ├── 5ec6d03ae969899f26d1d3ce.png │ └── 5ec6d1b2e969899f26d1d3cf.png ├── manifest.json └── index.html ├── server ├── routes │ ├── index.js │ └── api │ │ ├── attach-file.js │ │ ├── admin-manage.js │ │ ├── report.js │ │ ├── admin-sign.js │ │ ├── profile.js │ │ └── sign-in.js ├── config │ └── config.js ├── models │ ├── UserSession.js │ └── User.js ├── server.js └── socket │ └── index.js ├── .vscode └── launch.json ├── README.txt ├── README.md ├── package.json └── yarn-error.log /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /src/css/shared/misc.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/pages/AdminSign/AdminSign.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/pages/Login/Login.css: -------------------------------------------------------------------------------- 1 | #passwordHelp { 2 | font-size: 10px; 3 | } -------------------------------------------------------------------------------- /public/07.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chris-jin-g/RandomChat-MERN-WebRTC-/HEAD/public/07.jpg -------------------------------------------------------------------------------- /public/max.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chris-jin-g/RandomChat-MERN-WebRTC-/HEAD/public/max.png -------------------------------------------------------------------------------- /public/min.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chris-jin-g/RandomChat-MERN-WebRTC-/HEAD/public/min.png -------------------------------------------------------------------------------- /public/sign-bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chris-jin-g/RandomChat-MERN-WebRTC-/HEAD/public/sign-bg.jpg -------------------------------------------------------------------------------- /public/background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chris-jin-g/RandomChat-MERN-WebRTC-/HEAD/public/background.jpg -------------------------------------------------------------------------------- /public/chat-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chris-jin-g/RandomChat-MERN-WebRTC-/HEAD/public/chat-icon.png -------------------------------------------------------------------------------- /public/find-user.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chris-jin-g/RandomChat-MERN-WebRTC-/HEAD/public/find-user.webp -------------------------------------------------------------------------------- /public/login-bg1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chris-jin-g/RandomChat-MERN-WebRTC-/HEAD/public/login-bg1.jpg -------------------------------------------------------------------------------- /public/profile/male.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chris-jin-g/RandomChat-MERN-WebRTC-/HEAD/public/profile/male.png -------------------------------------------------------------------------------- /public/profile/female.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chris-jin-g/RandomChat-MERN-WebRTC-/HEAD/public/profile/female.png -------------------------------------------------------------------------------- /src/pages/Signup/Signup.css: -------------------------------------------------------------------------------- 1 | #passwordHelp { 2 | font-size: 10px; 3 | } 4 | 5 | #alertBox { 6 | margin-top: 10px; 7 | } -------------------------------------------------------------------------------- /src/assets/mdb-react-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chris-jin-g/RandomChat-MERN-WebRTC-/HEAD/src/assets/mdb-react-small.png -------------------------------------------------------------------------------- /public/profile/5ec581040366707806390da0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chris-jin-g/RandomChat-MERN-WebRTC-/HEAD/public/profile/5ec581040366707806390da0.png -------------------------------------------------------------------------------- /public/profile/5ec5810a0366707806390da1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chris-jin-g/RandomChat-MERN-WebRTC-/HEAD/public/profile/5ec5810a0366707806390da1.png -------------------------------------------------------------------------------- /public/profile/5ec581120366707806390da2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chris-jin-g/RandomChat-MERN-WebRTC-/HEAD/public/profile/5ec581120366707806390da2.png -------------------------------------------------------------------------------- /public/profile/5ec581130366707806390da3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chris-jin-g/RandomChat-MERN-WebRTC-/HEAD/public/profile/5ec581130366707806390da3.png -------------------------------------------------------------------------------- /public/profile/5ec5811d0366707806390da4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chris-jin-g/RandomChat-MERN-WebRTC-/HEAD/public/profile/5ec5811d0366707806390da4.png -------------------------------------------------------------------------------- /public/profile/5ec581220366707806390da5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chris-jin-g/RandomChat-MERN-WebRTC-/HEAD/public/profile/5ec581220366707806390da5.png -------------------------------------------------------------------------------- /public/profile/5ec5d703e969899f26d1d3c9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chris-jin-g/RandomChat-MERN-WebRTC-/HEAD/public/profile/5ec5d703e969899f26d1d3c9.png -------------------------------------------------------------------------------- /public/profile/5ec5d704e969899f26d1d3ca.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chris-jin-g/RandomChat-MERN-WebRTC-/HEAD/public/profile/5ec5d704e969899f26d1d3ca.png -------------------------------------------------------------------------------- /public/profile/5ec5d884e969899f26d1d3cb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chris-jin-g/RandomChat-MERN-WebRTC-/HEAD/public/profile/5ec5d884e969899f26d1d3cb.png -------------------------------------------------------------------------------- /public/profile/5ec5d888e969899f26d1d3cc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chris-jin-g/RandomChat-MERN-WebRTC-/HEAD/public/profile/5ec5d888e969899f26d1d3cc.png -------------------------------------------------------------------------------- /public/profile/5ec5d88ee969899f26d1d3cd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chris-jin-g/RandomChat-MERN-WebRTC-/HEAD/public/profile/5ec5d88ee969899f26d1d3cd.png -------------------------------------------------------------------------------- /public/profile/5ec6d03ae969899f26d1d3ce.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chris-jin-g/RandomChat-MERN-WebRTC-/HEAD/public/profile/5ec6d03ae969899f26d1d3ce.png -------------------------------------------------------------------------------- /public/profile/5ec6d1b2e969899f26d1d3cf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chris-jin-g/RandomChat-MERN-WebRTC-/HEAD/public/profile/5ec6d1b2e969899f26d1d3cf.png -------------------------------------------------------------------------------- /src/pages/Chat/socket.js: -------------------------------------------------------------------------------- 1 | import io from 'socket.io-client'; 2 | import { RESTAPIUrl } from "../../config/config" 3 | 4 | const socket = io.connect(`${RESTAPIUrl}`); 5 | 6 | export default socket; -------------------------------------------------------------------------------- /src/css/variables.scss: -------------------------------------------------------------------------------- 1 | // Variables 2 | $main-color: #FFFFFF; 3 | $secondary-color: darken(#888888, 10%); 4 | $main-font-size: 14px; 5 | 6 | $green: #7FBA00; 7 | $yellow: #FCD116; 8 | $red: #E81123; 9 | $blue: #00AFF0; -------------------------------------------------------------------------------- /src/App/App.test.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import App from "./App"; 4 | 5 | it("renders without crashing", () => { 6 | const div = document.createElement("div"); 7 | ReactDOM.render(, div); 8 | }); 9 | -------------------------------------------------------------------------------- /src/dataservice/request.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | const API_ROOT = process.env.REACT_APP_SERVER_URI 4 | 5 | axios.defaults.baseURL = API_ROOT; 6 | 7 | export const fetchUsers = () => { 8 | return axios.get(`/users`) 9 | .then(res => res.data.data) 10 | } -------------------------------------------------------------------------------- /server/routes/index.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | 4 | module.exports = (app) => { 5 | // API routes 6 | fs.readdirSync(__dirname + '/api/').forEach((file) => { 7 | require(`./api/${file.substr(0, file.indexOf('.'))}`)(app); 8 | }); 9 | }; -------------------------------------------------------------------------------- /src/config/config.js: -------------------------------------------------------------------------------- 1 | // export const RESTAPIUrl = 'http://192.168.100.118:5000'; 2 | // export const SOCKET_URI = 'http://192.168.100.118:5000'; 3 | 4 | export const RESTAPIUrl = 'https://anonymous-video-chat.herokuapp.com'; 5 | export const SOCKET_URI = 'https://anonymous-video-chat.herokuapp.com'; -------------------------------------------------------------------------------- /src/pages/HomePage/HomePage.css: -------------------------------------------------------------------------------- 1 | strong h2 { 2 | font-size: 35px; 3 | font-weight: 500; 4 | } 5 | 6 | .navbar-title { 7 | font-size: 25px; 8 | font-weight: 500; 9 | } 10 | 11 | .btn-group a { 12 | display: inline-block; 13 | } 14 | 15 | .verify-alert { 16 | color: red; 17 | } -------------------------------------------------------------------------------- /src/css/shared/base.scss: -------------------------------------------------------------------------------- 1 | @import "mixin"; 2 | 3 | html, body { 4 | color: $main-color; 5 | font-size: $main-font-size; 6 | letter-spacing: 0.5px; 7 | 8 | .no-animation { 9 | * { 10 | @include no-animation(); 11 | } 12 | } 13 | } 14 | 15 | input { 16 | outline: none; 17 | } 18 | 19 | @import "misc"; -------------------------------------------------------------------------------- /src/App/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { BrowserRouter as Router } from 'react-router-dom'; 3 | import Routes from './Routes'; 4 | 5 | class App extends Component { 6 | 7 | render() { 8 | return ( 9 | 10 | 11 | 12 | ); 13 | } 14 | } 15 | 16 | export default App; 17 | -------------------------------------------------------------------------------- /server/config/config.js: -------------------------------------------------------------------------------- 1 | // module.exports = { 2 | // db: 'mongodb://localhost/catapultSports', 3 | // RESTAPIport: '5000', 4 | // }; 5 | module.exports = { 6 | db: 'mongodb+srv://Robert:Professional@cluster0-m5kjg.mongodb.net/test?authSource=admin&replicaSet=Cluster0-shard-0&readPreference=primary&appname=MongoDB%20Compass%20Community&ssl=true', 7 | RESTAPIport: '5000', 8 | }; -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import "@fortawesome/fontawesome-free/css/all.min.css"; 4 | import "bootstrap-css-only/css/bootstrap.min.css"; 5 | import "mdbreact/dist/css/mdb.css"; 6 | import "./index.css"; 7 | import App from "./App/App"; 8 | 9 | import registerServiceWorker from './registerServiceWorker'; 10 | 11 | ReactDOM.render( , document.getElementById('root')); 12 | 13 | registerServiceWorker(); -------------------------------------------------------------------------------- /server/models/UserSession.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const UserSessionSchema = new mongoose.Schema({ 4 | userId: { 5 | type: String, 6 | default: '', 7 | }, 8 | timestamp: { 9 | type: Date, 10 | default: Date.now(), 11 | }, 12 | isDeleted: { 13 | type: Boolean, 14 | default: false, 15 | }, 16 | }); 17 | 18 | module.exports = mongoose.model('UserSession', UserSessionSchema); -------------------------------------------------------------------------------- /src/components/menuLink.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { MDBIcon, MDBNavLink } from 'mdbreact'; 3 | 4 | const NavLink = ({ to, title }) => { 5 | return ( 6 | 7 |
8 | {title} 9 | 10 |
11 |
12 | ); 13 | }; 14 | 15 | export default NavLink; 16 | -------------------------------------------------------------------------------- /src/components/OnTyping/OnTyping.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import "./OnTyping.css"; 3 | 4 | const OnTyping = () => { 5 | return ( 6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | ); 14 | } 15 | 16 | export default OnTyping; -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | .flyout { 2 | display:flex; 3 | flex-direction: column; 4 | min-height:100vh; 5 | justify-content: space-between; 6 | } 7 | 8 | .home-feature-box .fa { 9 | font-size:6rem; 10 | } 11 | 12 | .home-feature-box span { 13 | display: block; 14 | color:#111; 15 | font-weight:bold; 16 | margin-top:1.5rem; 17 | } 18 | 19 | .example-components-list li > a { 20 | color: #495057; 21 | } 22 | 23 | .example-components-list li:last-child > a { 24 | border-bottom:0; 25 | } 26 | 27 | .example-components-list li > a .fa { 28 | color:rgba(0,0,0,.35); 29 | float:right; 30 | } 31 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Launch Program", 11 | "skipFiles": [ 12 | "/**" 13 | ], 14 | "program": "${workspaceFolder}\\server\\server.js" 15 | } 16 | ] 17 | } -------------------------------------------------------------------------------- /src/utils/storage.js: -------------------------------------------------------------------------------- 1 | export function getFromStorage(key) { 2 | if (!key) { 3 | return null; 4 | } 5 | try { 6 | const valueStr = localStorage.getItem(key); 7 | if (valueStr) { 8 | return JSON.parse(valueStr); 9 | } 10 | return null; 11 | } catch (err) { 12 | return null; 13 | } 14 | } 15 | export function setInStorage(key, obj) { 16 | if (!key) { 17 | console.error('Error: Key is missing'); 18 | } 19 | try { 20 | localStorage.setItem(key, JSON.stringify(obj)); 21 | } catch (err) { 22 | console.error(err); 23 | } 24 | } -------------------------------------------------------------------------------- /src/dataservice/index.js: -------------------------------------------------------------------------------- 1 | import 'whatwg-fetch'; 2 | const RESTAPIUrl = "http://localhost:8080"; 3 | 4 | export default dataService = { 5 | signUpService = (signUpCreds) => { 6 | fetch("/api/account/signup", { 7 | method: 'POST', 8 | headers: { 9 | 'Content-Type': 'application/json' 10 | }, 11 | body: JSON.stringify(signUpCreds), 12 | }) 13 | .then(res => res.json()) 14 | .then(json => { 15 | console.log('json', json); 16 | console.log(res); 17 | return json; 18 | 19 | }) 20 | } 21 | }; -------------------------------------------------------------------------------- /src/components/LoadingModal.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import Glyphicon from "react-bootstrap/lib/Glyphicon"; 3 | import Modal from "react-bootstrap/lib/Modal"; 4 | 5 | /** 6 | * 7 | * Renders a loader modal. 8 | */ 9 | 10 | export default class LoadingModal extends Component { 11 | state = {}; 12 | render() { 13 | return ( 14 | 15 | 16 |

17 | 18 |

19 |
Loading...
20 |
21 |
22 | ); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /README.txt: -------------------------------------------------------------------------------- 1 | Material Design for Bootstrap 2 | 3 | Version: MDB React Pro 4.25.6 4 | 5 | Documentation: 6 | https://mdbootstrap.com/docs/react/ 7 | 8 | Getting started: 9 | https://mdbootstrap.com/docs/react/getting-started/quick-start/ 10 | 11 | FAQ 12 | https://mdbootstrap.com/react/faq/ 13 | 14 | Support: 15 | https://mdbootstrap.com/support/cat/mdb-react/ 16 | 17 | ChangeLog 18 | https://mdbootstrap.com/docs/react/changelog/ 19 | 20 | License: 21 | https://mdbootstrap.com/license/ 22 | 23 | Facebook: https://facebook.com/mdbootstrap 24 | Twitter: https://twitter.com/MDBootstrap 25 | Google+: https://plus.google.com/u/0/+Mdbootstrap/posts 26 | Dribbble: https://dribbble.com/mdbootstrap 27 | 28 | 29 | Contact: 30 | office@mdbootstrap.com 31 | -------------------------------------------------------------------------------- /src/pages/Chat/Emitter.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | 3 | class Emitter { 4 | constructor() { 5 | this.events = {}; 6 | } 7 | 8 | emit(event, ...args) { 9 | if (this.events[event]) { 10 | this.events[event].forEach((fn) => fn(...args)); 11 | } 12 | return this; 13 | } 14 | 15 | on(event, fn) { 16 | if (this.events[event]) this.events[event].push(fn); 17 | else this.events[event] = [fn]; 18 | return this; 19 | } 20 | 21 | off(event, fn) { 22 | if (event && _.isFunction(fn)) { 23 | const listeners = this.events[event]; 24 | const index = listeners.findIndex((_fn) => _fn === fn); 25 | listeners.splice(index, 1); 26 | } else this.events[event] = []; 27 | return this; 28 | } 29 | } 30 | 31 | export default Emitter; 32 | -------------------------------------------------------------------------------- /src/components/NavBar.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import Navbar from "react-bootstrap/lib/Navbar"; 3 | 4 | /** 5 | * 6 | * Renders top navbar and shows the current signed in user. 7 | */ 8 | export default class NavBar extends Component { 9 | state = {}; 10 | render() { 11 | return ( 12 | 13 | 14 | Cool Chat 15 | 16 | 17 | 18 | 19 | Signed in as:  20 | {(this.props.signedInUser || {}).name} 21 | 22 | 23 | 24 | ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/components/ErrorModal.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import Glyphicon from "react-bootstrap/lib/Glyphicon"; 3 | import Modal from "react-bootstrap/lib/Modal"; 4 | 5 | /** 6 | * 7 | * Renders a Error modal if app encounter any error. 8 | */ 9 | 10 | export default class ErrorModal extends Component { 11 | state = {}; 12 | render() { 13 | return ( 14 | 15 | 16 | Error 17 | 18 | 19 | 20 |

21 | 22 |

23 |
{this.props.errorMessage}
24 |
25 |
26 | ); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/css/call-modal.scss: -------------------------------------------------------------------------------- 1 | .call-modal { 2 | position: absolute; 3 | width: 400px; 4 | padding: 20px; 5 | left: calc(50vw - 200px); 6 | top: calc(50vh - 60px); 7 | @include bg-gradient(top, #074055 0%, #030D10 100%); 8 | @include border-radius(5px); 9 | text-align: center; 10 | display: none; 11 | 12 | &.active { 13 | display: block; 14 | z-index: 9999; 15 | @include animation(blinking 3s infinite linear); 16 | } 17 | 18 | .btn-action:not(.hangup) { 19 | background-color: $green; 20 | } 21 | 22 | span.caller { 23 | color: $blue; 24 | } 25 | 26 | p { 27 | font-size: 1.5em; 28 | } 29 | } 30 | 31 | @include keyframes(blinking) { 32 | 25% {@include transform(scale(0.96))} 33 | 50% {@include transform(scale(1))} 34 | 75% {@include transform(scale(0.96))} 35 | 100% {@include transform(scale(1))} 36 | } -------------------------------------------------------------------------------- /src/components/docsLink.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { MDBRow, MDBIcon } from 'mdbreact'; 3 | 4 | const DocsLink = ({ title, href }) => { 5 | return ( 6 | <> 7 | 8 |

9 | {title} 10 |

11 | 17 | 18 | Docs 19 | 20 |
21 |
22 | 23 | ); 24 | }; 25 | 26 | export default DocsLink; 27 | -------------------------------------------------------------------------------- /server/routes/api/attach-file.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | 3 | module.exports = (app) => { 4 | app.post('/api/attach-file', (req, res, next) => { 5 | let attatchFile = req.files.attachFile; 6 | let uploadDir = 'public/uploads/'; 7 | 8 | if (!fs.existsSync(uploadDir)) { 9 | fs.mkdirSync(uploadDir); 10 | } 11 | 12 | let fileType = attatchFile.name.substring(attatchFile.name.indexOf(".") + 1); 13 | let newFileName = new Date().getTime(); 14 | attatchFile.mv(`${uploadDir}${newFileName}.${fileType}`, function(err) { 15 | if (err) { 16 | return res.status(500).send(err); 17 | } 18 | return res.status(200).send({ 19 | status: true, 20 | fileName: `${newFileName}.${fileType}` 21 | }) 22 | }) 23 | }); 24 | } -------------------------------------------------------------------------------- /src/components/FullScreenImage/FullScreenImage.css: -------------------------------------------------------------------------------- 1 | .full-image { 2 | position: fixed; 3 | width: 100vw; 4 | height: 100vh; 5 | z-index: 10; 6 | background-color: #212529; 7 | top: 0px; 8 | left: 0px; 9 | margin: auto; 10 | } 11 | 12 | .close-btn { 13 | position: absolute; 14 | top: 10px; 15 | right: 15px; 16 | font-size: 1rem; 17 | font-weight: 400; 18 | color: white; 19 | cursor: pointer; 20 | } 21 | 22 | .close-btn:hover { 23 | transform: scale(1.2); 24 | } 25 | 26 | .full-image img { 27 | position: absolute; 28 | left: 0; 29 | right: 0; 30 | top: 0; 31 | bottom: 0; 32 | margin: auto; 33 | max-width: 80%; 34 | } 35 | 36 | .download-btn { 37 | position: absolute; 38 | right: 40px; 39 | top: 9px; 40 | color: white; 41 | } 42 | 43 | .download-btn:hover { 44 | transform: scale(1.1); 45 | } -------------------------------------------------------------------------------- /src/pages/UserSign/UserSign.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | animation: App-logo-spin infinite 20s linear; 7 | height: 80px; 8 | } 9 | 10 | .App-header { 11 | background-color: #222; 12 | height: 150px; 13 | padding: 20px; 14 | color: white; 15 | } 16 | 17 | .App-title { 18 | font-size: 1.5em; 19 | } 20 | 21 | .App-intro { 22 | font-size: large; 23 | } 24 | 25 | .loginBoxContainer { 26 | margin-top: 20px; 27 | -webkit-box-shadow: 0 0 20px 1px rgba(0, 0, 0, 0.25); 28 | box-shadow: 0 0 20px 1px rgba(0, 0, 0, 0.25); 29 | -webkit-border-radius: 3px 3px 3px 3px; 30 | border-radius: 3px 3px 3px 3px; 31 | } 32 | 33 | .tabContent { 34 | padding: 10px; 35 | } 36 | 37 | @keyframes App-logo-spin { 38 | from { 39 | transform: rotate(0deg); 40 | } 41 | to { 42 | transform: rotate(360deg); 43 | } 44 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Bootstrap with Material Design 2 | MDBootstrap for React 3 | 4 | ## Getting Started 5 | To test, contribute or just see what we did follow few easy steps: 6 | - clone the repository 7 | - cd to the directory with the repository 8 | - run `yarn install` (or `npm install` if you don't use yarn) 9 | - run the app using `yarn start` (or `npm start`) 10 | - to build project use `yarn run build` (od `npm run build`) 11 | - `yarn run remove-demo` (or `npm run remove-demo`) removes demo app pages 12 | - enjoy! 13 | 14 | ## Bugs 15 | If you want to report a bug or submit your idea feel fre to open an issue 16 | 17 | Before you report a bug, please take your time to find if an issue hasn't been reported yet 18 | 19 | We're also open to pull requests 20 | 21 | ## Something Missing? 22 | If you still have some questions do not hesitate to ask us. Open an issue or [visit our Slack](https://mdbbetatest.slack.com) 23 | -------------------------------------------------------------------------------- /src/css/app.scss: -------------------------------------------------------------------------------- 1 | $fa-font-path: "~font-awesome/fonts"; 2 | @import "~font-awesome/scss/font-awesome"; 3 | 4 | $icon-font-path: "~bootstrap-sass/assets/fonts/bootstrap/"; 5 | @import "~bootstrap-sass/assets/stylesheets/bootstrap"; 6 | 7 | @import "variables"; 8 | @import "shared/base"; 9 | 10 | html { 11 | background: url("https://source.unsplash.com/2560x1440/?forest,mountain") center no-repeat fixed; 12 | } 13 | 14 | body { 15 | background: rgba(#000000, 0.6); 16 | padding: 0px 20px 20px 20px; 17 | min-height: 100vh; 18 | } 19 | 20 | .navbar-brand { 21 | font-size: 1.5em; 22 | img { 23 | display: inline-block; 24 | width: 50px; 25 | } 26 | } 27 | 28 | #root { 29 | h2 { 30 | margin-top: 0px; 31 | } 32 | 33 | h4 { 34 | margin-top: 20px; 35 | } 36 | } 37 | 38 | .btn-action { 39 | outline: none; 40 | border: none; 41 | } 42 | 43 | 44 | @import "main-window"; 45 | @import "call-window"; 46 | @import "call-modal"; 47 | -------------------------------------------------------------------------------- /src/css/main-window.scss: -------------------------------------------------------------------------------- 1 | .main-window { 2 | padding-top: 80px; 3 | font-size: 1.75em; 4 | 5 | @media screen and (max-width: 767px) { 6 | padding-top: 40px; 7 | 8 | .pull-left, .pull-right { 9 | width: 100%; 10 | text-align: center; 11 | } 12 | 13 | .pull-right { 14 | margin-top: 20px; 15 | } 16 | } 17 | 18 | .btn-action { 19 | $height: 60px; 20 | height: $height; 21 | width: $height; 22 | margin: 20px 30px 0px 0px; 23 | line-height: $height; 24 | text-align: center; 25 | border-radius: 50%; 26 | border: solid 2px $main-color; 27 | cursor: pointer; 28 | transition-duration: 0.25s; 29 | background-color: transparent; 30 | 31 | &:hover { 32 | background-color: rgba($main-color, 0.2); 33 | } 34 | } 35 | 36 | .txt-clientId { 37 | height: 40px; 38 | margin: 40px auto 0px 10px; 39 | color: $main-color; 40 | font-size: 0.9em; 41 | background-color: transparent; 42 | border: none; 43 | border-bottom: solid 1px $main-color; 44 | @include input-placeholder(rgba($main-color, 0.8)); 45 | } 46 | 47 | } 48 | 49 | -------------------------------------------------------------------------------- /src/components/SearchSettingBox/SearchSettingBox.css: -------------------------------------------------------------------------------- 1 | .search-container-true { 2 | position: fixed; 3 | margin: 0px; 4 | top: 74px; 5 | right: -15px; 6 | width: 350px; 7 | height: 100%; 8 | z-index: 2; 9 | animation-name: search-container; 10 | animation-duration: 0.5s; 11 | } 12 | 13 | .search-container- { 14 | display: none; 15 | } 16 | 17 | @keyframes search-container { 18 | 0% { 19 | right: -400px 20 | } 21 | 100% { 22 | right: -15px; 23 | } 24 | } 25 | 26 | .search-container-false { 27 | position: fixed; 28 | margin: 0px; 29 | top: 74px; 30 | right: -400px; 31 | width: 350px; 32 | height: 100%; 33 | animation-name: search-container-hide; 34 | animation-duration: 0.5s; 35 | } 36 | 37 | @keyframes search-container-hide { 38 | 0% { 39 | right: -15px 40 | } 41 | 100% { 42 | right: -400px; 43 | } 44 | } 45 | 46 | .jumbotron { 47 | padding: 20px 5px 0px 5px; 48 | height: calc( 100vh - 75px); 49 | } 50 | 51 | .form-label { 52 | margin: 10px 0px; 53 | } 54 | 55 | .form-group { 56 | border: none !important; 57 | } 58 | 59 | .card-title { 60 | margin-top: 20px; 61 | } -------------------------------------------------------------------------------- /src/components/sectionContainer.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { MDBContainer } from 'mdbreact'; 3 | import classNames from 'classnames'; 4 | 5 | const SectionContainer = ({ 6 | children, 7 | className, 8 | dark, 9 | description, 10 | header, 11 | noBorder, 12 | noBottom, 13 | style, 14 | title, 15 | flexCenter, 16 | flexCenterVert, 17 | flexColumn 18 | }) => { 19 | const classes = classNames( 20 | 'section', 21 | !noBottom && 'mb-5', 22 | !noBorder ? 'border p-3' : 'px-0', 23 | dark && 'grey darken-3', 24 | flexCenter && 'd-flex justify-content-center align-items-center', 25 | flexCenterVert && 'd-flex align-items-center', 26 | flexColumn && 'flex-column', 27 | className 28 | ); 29 | 30 | description = description ?

{description}

: ''; 31 | title = title ?

{title}

: ''; 32 | header = header ?

{header}

: ''; 33 | 34 | return ( 35 | <> 36 | {title} 37 | {header} 38 | 39 | {description} 40 | {children} 41 | 42 | 43 | ); 44 | }; 45 | 46 | export default SectionContainer; -------------------------------------------------------------------------------- /src/components/ReportBox/ReportBox.css: -------------------------------------------------------------------------------- 1 | .report-container-true { 2 | position: fixed; 3 | margin: 0px; 4 | top: 74px; 5 | right: -15px; 6 | width: 350px; 7 | height: 100%; 8 | z-index: 2; 9 | animation-name: search-container; 10 | animation-duration: 0.5s; 11 | } 12 | 13 | .report-container- { 14 | display: none; 15 | } 16 | 17 | form { 18 | font-size: 15px; 19 | } 20 | 21 | @keyframes search-container { 22 | 0% { 23 | right: -400px 24 | } 25 | 100% { 26 | right: -15px; 27 | } 28 | } 29 | 30 | .report-container-false { 31 | position: fixed; 32 | margin: 0px; 33 | top: 74px; 34 | right: -400px; 35 | width: 350px; 36 | height: 100%; 37 | animation-name: search-container-hide; 38 | animation-duration: 0.5s; 39 | } 40 | 41 | @keyframes search-container-hide { 42 | 0% { 43 | right: -15px 44 | } 45 | 100% { 46 | right: -400px; 47 | } 48 | } 49 | 50 | .jumbotron { 51 | padding: 20px 5px 0px 5px; 52 | height: calc( 100vh - 75px); 53 | } 54 | 55 | .form-label { 56 | margin: 10px 0px; 57 | } 58 | 59 | .form-group { 60 | border: none !important; 61 | } 62 | 63 | .card-title { 64 | margin-top: 20px; 65 | } 66 | 67 | .report-reason { 68 | padding-left: 30px; 69 | } -------------------------------------------------------------------------------- /src/components/FullScreenImage/FullScreenImage.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import 'rc-slider/assets/index.css'; 3 | 4 | import "./FullScreenImage.css"; 5 | 6 | export default class FullScreenImage extends Component { 7 | constructor(props) { 8 | super(props); 9 | let imageUrl = this.props.imageUrl; 10 | let downloadPath = imageUrl.split("public/")[1]; 11 | this.state = { 12 | downloadPath: downloadPath 13 | }; 14 | } 15 | 16 | componentDidMount() { 17 | } 18 | onShowImageFullScreen() { 19 | this.props.onShowImageFullScreen(); 20 | } 21 | render() { 22 | return ( 23 | 24 |
25 |
26 | 30 | X 31 | 32 | 33 | 34 | 35 | 36 | 37 |
38 |
39 | ); 40 | } 41 | } -------------------------------------------------------------------------------- /server/routes/api/admin-manage.js: -------------------------------------------------------------------------------- 1 | const User = require('../../models/User'); 2 | 3 | module.exports = (app) => { 4 | app.post('/api/admin/manage', (req, res, next) => { 5 | User.find({ role: { $ne: 'admin' } }, 6 | function(err, users) { 7 | if (err) { 8 | console.log(err); 9 | throw err; 10 | } else { 11 | return res.send({ 12 | success: true, 13 | users: users 14 | }); 15 | } 16 | }) 17 | }); 18 | 19 | app.post('/api/admin/enable-user', (req, res, next) => { 20 | User.findOneAndUpdate({ 21 | _id: req.body.user._id, 22 | }, { 23 | $set: { 24 | isDeleted: !req.body.user.isDeleted, 25 | report_number: req.body.user.isDeleted ? 0 : 5, 26 | report_reason: '' 27 | } 28 | }, { new: true }, (err, newUser) => { 29 | if (err) { 30 | return res.send({ 31 | success: false, 32 | message: 'Error: Server Error ' 33 | }) 34 | } 35 | return res.send({ 36 | success: true, 37 | user: newUser, 38 | message: 'User information updated successfully.' 39 | }) 40 | }); 41 | }); 42 | } -------------------------------------------------------------------------------- /src/pages/Chat/CallModal.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import classnames from 'classnames'; 4 | 5 | 6 | function CallModal({ status, callFrom, startCall, rejectCall, userAvatar }) { 7 | const acceptWithVideo = (video) => { 8 | const config = { audio: true, video }; 9 | return () => startCall(false, callFrom, config); 10 | }; 11 | 12 | return ( 13 |
14 |
15 | contact-user 16 |
17 |

18 | {`${callFrom}`} 19 |

20 | Incoming call... 21 |
37 | ); 38 | } 39 | 40 | CallModal.propTypes = { 41 | status: PropTypes.string.isRequired, 42 | callFrom: PropTypes.string.isRequired, 43 | startCall: PropTypes.func.isRequired, 44 | rejectCall: PropTypes.func.isRequired 45 | }; 46 | 47 | export default CallModal; -------------------------------------------------------------------------------- /src/components/CallModal/CallModal.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import classnames from 'classnames'; 4 | import { RESTAPIUrl } from '../../config/config'; 5 | import './CallModal.css'; 6 | 7 | 8 | function CallModal({ status, callFrom, startCall, rejectCall, contactUser, userAvatar }) { 9 | const acceptWithVideo = (video) => { 10 | const config = { audio: true, video }; 11 | return () => startCall(false, callFrom, config); 12 | }; 13 | 14 | return ( 15 |
16 |
17 | contact-user 18 |
19 |

20 | {`${contactUser}`} 21 |

22 | Incoming call... 23 |
39 | ); 40 | } 41 | 42 | CallModal.propTypes = { 43 | status: PropTypes.string.isRequired, 44 | callFrom: PropTypes.string.isRequired, 45 | startCall: PropTypes.func.isRequired, 46 | rejectCall: PropTypes.func.isRequired 47 | }; 48 | 49 | export default CallModal; -------------------------------------------------------------------------------- /src/pages/Chat/MediaDevice.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | import Emitter from './Emitter'; 3 | 4 | /** 5 | * Manage all media devices 6 | */ 7 | class MediaDevice extends Emitter { 8 | /** 9 | * Start media devices and send stream 10 | */ 11 | start() { 12 | const constraints = { 13 | video: { 14 | facingMode: 'user', 15 | height: { min: 360, ideal: 720, max: 1080 } 16 | }, 17 | audio: true 18 | }; 19 | 20 | navigator.mediaDevices 21 | .getUserMedia(constraints) 22 | .then((stream) => { 23 | this.stream = stream; 24 | this.emit('stream', stream); 25 | }) 26 | .catch((err) => { 27 | if (err instanceof DOMException) { 28 | alert('Cannot open webcam and/or microphone'); 29 | } else { 30 | console.log(err); 31 | } 32 | }); 33 | 34 | return this; 35 | } 36 | 37 | /** 38 | * Turn on/off a device 39 | * @param {String} type - Type of the device 40 | * @param {Boolean} [on] - State of the device 41 | */ 42 | toggle(type, on) { 43 | const len = arguments.length; 44 | if (this.stream) { 45 | this.stream[`get${type}Tracks`]().forEach((track) => { 46 | const state = len === 2 ? on : !track.enabled; 47 | _.set(track, 'enabled', state); 48 | }); 49 | } 50 | return this; 51 | } 52 | 53 | /** 54 | * Stop all media track of devices 55 | */ 56 | stop() { 57 | if (this.stream) { 58 | this.stream.getTracks().forEach((track) => track.stop()); 59 | } 60 | return this; 61 | } 62 | } 63 | 64 | export default MediaDevice; 65 | -------------------------------------------------------------------------------- /src/css/call-window.scss: -------------------------------------------------------------------------------- 1 | .call-window { 2 | position: absolute; 3 | top: 0; 4 | left: 0; 5 | width: 100vw; 6 | height: 100vh; 7 | opacity: 0; 8 | z-index: -1; 9 | @include transition(opacity 0.5s); 10 | @include bg-gradient(top, #074055 0%, #030D10 100%); 11 | 12 | &.active { 13 | opacity: 1; 14 | z-index: auto; 15 | 16 | .video-control { 17 | z-index: auto; 18 | @include animation(in-fadeout 3s ease); 19 | } 20 | } 21 | 22 | .video-control { 23 | position: absolute; 24 | bottom: 20px; 25 | height: 72px; 26 | width: 100%; 27 | text-align: center; 28 | opacity: 0; 29 | z-index: -1; 30 | @include transition(opacity 0.5s); 31 | 32 | 33 | &:hover { 34 | opacity: 1; 35 | } 36 | } 37 | 38 | video { 39 | position: absolute; 40 | } 41 | 42 | #localVideo { 43 | bottom: 0; 44 | right: 0; 45 | width: 20%; 46 | height: 20%; 47 | } 48 | 49 | #peerVideo { 50 | width: 100%; 51 | height: 100%; 52 | } 53 | } 54 | 55 | @include keyframes(in-fadeout) { 56 | 0% {opacity: 1} 57 | 75% {opacity: 1} 58 | 100% {opacity: 0} 59 | } 60 | 61 | .video-control, .call-modal { 62 | .btn-action { 63 | $height: 50px; 64 | height: $height; 65 | width: $height; 66 | line-height: $height; 67 | margin: 0px 8px; 68 | font-size: 1.4em; 69 | text-align: center; 70 | border-radius: 50%; 71 | cursor: pointer; 72 | transition-duration: 0.25s; 73 | 74 | &:hover { 75 | opacity: 0.8; 76 | } 77 | 78 | &.hangup { 79 | background-color: $red; 80 | @include transform(rotate(135deg)); 81 | } 82 | 83 | &:not(.hangup) { 84 | background-color: $blue; 85 | 86 | &.disable { 87 | background-color: $red; 88 | } 89 | } 90 | } 91 | } -------------------------------------------------------------------------------- /server/models/User.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const bcrypt = require('bcrypt'); 3 | 4 | const UserSchema = new mongoose.Schema({ 5 | userName: { 6 | type: String, 7 | default: '', 8 | }, 9 | age: { 10 | type: Number, 11 | default: '', 12 | }, 13 | location: { 14 | type: String, 15 | default: '', 16 | }, 17 | gender: { 18 | type: String, 19 | default: '', 20 | }, 21 | profile_image: { 22 | type: String, 23 | default: '', 24 | }, 25 | ip_address: { 26 | type: String, 27 | default: '', 28 | }, 29 | report_reason: [{ 30 | reporter_id: { type: String, max: 100 }, 31 | reason: { type: String, max: 100 }, 32 | }], 33 | report_number: { 34 | type: Number, 35 | default: 0, 36 | }, 37 | isDeleted: { 38 | type: Boolean, 39 | default: false, 40 | }, 41 | online: { 42 | type: Boolean, 43 | default: false, 44 | }, 45 | connected_other: { 46 | type: Boolean, 47 | default: false, 48 | }, 49 | email: { 50 | type: String, 51 | default: '', 52 | }, 53 | password: { 54 | type: String, 55 | default: '', 56 | }, 57 | role: { 58 | type: String, 59 | default: '', 60 | }, 61 | 62 | }); 63 | 64 | UserSchema.methods.generateHash = (password) => 65 | bcrypt.hashSync(password, bcrypt.genSaltSync(8), null); 66 | 67 | UserSchema.methods.validPassword = function(password) { 68 | console.log(this.password); 69 | return bcrypt.compareSync(password, this.password); 70 | }; 71 | 72 | module.exports = mongoose.model('User', UserSchema); -------------------------------------------------------------------------------- /src/App/Routes.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Route, Switch, Redirect } from 'react-router-dom'; 3 | import { getFromStorage } from '../utils/storage'; 4 | 5 | // FREE 6 | import HomePage from '../pages/HomePage/HomePage'; 7 | import UserSign from '../pages/UserSign/UserSign'; 8 | import GuestSign from '../pages/GuestSign/GuestSign'; 9 | import Chat from '../pages/Chat/Chat'; 10 | 11 | import AdminSign from '../pages/AdminSign/AdminSign'; 12 | import AdminManage from '../pages/AdminManage/AdminManage'; 13 | 14 | const fakeAuth = () => { 15 | const obj = getFromStorage('guest_signin'); 16 | 17 | if(obj && obj.token) { 18 | return true; 19 | } 20 | return false; 21 | } 22 | 23 | const PrivateRoute = ({ component: Component, ...rest }) => ( 24 | ( 25 | fakeAuth() 26 | ? 27 | : 28 | )} /> 29 | ) 30 | 31 | const PrivateChatRoute = ({ component: Component, ...rest }) => ( 32 | ( 33 | fakeAuth() 34 | ? 35 | : 36 | )} /> 37 | ) 38 | 39 | class Routes extends React.Component { 40 | 41 | render() { 42 | return ( 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | ); 52 | } 53 | } 54 | 55 | export default Routes; 56 | -------------------------------------------------------------------------------- /src/pages/Chat/MainWindow.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | function MainWindow({ startCall, clientId }) { 5 | const [friendID, setFriendID] = useState(null); 6 | 7 | /** 8 | * Start the call with or without video 9 | * @param {Boolean} video 10 | */ 11 | const callWithVideo = (video) => { 12 | const config = { audio: true, video }; 13 | return () => friendID && startCall(true, friendID, config); 14 | }; 15 | 16 | return ( 17 |
18 |
19 |

20 | Hi,Dagger your ID is 21 | 27 |

28 |

Get started by calling a friend below

29 |
30 |
31 | setFriendID(event.target.value)} 37 | /> 38 |
39 |
50 |
51 |
52 | ); 53 | } 54 | 55 | MainWindow.propTypes = { 56 | clientId: PropTypes.string.isRequired, 57 | startCall: PropTypes.func.isRequired 58 | }; 59 | 60 | export default MainWindow; 61 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | 23 | React App 24 | 25 | 26 | 27 | 30 |
31 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /server/server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const fs = require('fs'); 3 | 4 | const mongoose = require('mongoose'); 5 | const path = require('path'); 6 | 7 | 8 | const logger = require('morgan'); 9 | const cookieParser = require('cookie-parser'); 10 | const bodyParser = require('body-parser'); 11 | const fileUpload = require('express-fileupload'); 12 | const cors = require('cors'); 13 | 14 | const config = require('./config/config'); 15 | const port = config.RESTAPIport; 16 | const socket = require('./socket') 17 | 18 | 19 | // Configuration 20 | // ================================================================================================ 21 | 22 | // Set up Mongoose 23 | mongoose.connect(config.db, { useNewUrlParser: true, useUnifiedTopology: true }); 24 | mongoose.Promise = global.Promise; 25 | 26 | const app = express(); 27 | const server = require("http").Server(app); 28 | 29 | // const io = require("socket.io")(server); 30 | 31 | app.use(express.urlencoded({ extended: true })); 32 | app.use(express.json()); 33 | app.use(logger('dev')); 34 | app.use(cors()); 35 | 36 | app.use(bodyParser.json()); 37 | app.use(bodyParser.urlencoded({ extended: false })); 38 | app.use(cookieParser()); 39 | app.use(fileUpload()); 40 | app.use('/public', express.static('public')); 41 | 42 | app.use('/', express.static(path.resolve(__dirname, '../build'))); 43 | 44 | app.get('*', function(req, res) { 45 | res.sendFile(path.resolve(__dirname, '../build/index.html')); 46 | res.end(); 47 | }); 48 | 49 | // API routes 50 | require('./routes')(app); 51 | 52 | 53 | 54 | 55 | server.listen(process.env.PORT || 5000, (err) => { 56 | if (err) { 57 | console.log(err); 58 | } 59 | socket(server); 60 | // eslint-disable-next-line no-console 61 | console.info('Open http://localhost:%s/ in your browser.', process.env.PORT || 5000); 62 | }); 63 | 64 | 65 | 66 | module.exports = app; -------------------------------------------------------------------------------- /src/css/shared/mixin.scss: -------------------------------------------------------------------------------- 1 | @mixin constructor($property, $specs...) { 2 | -webkit-#{$property}: $specs; 3 | -moz-#{$property}: $specs; 4 | -o-#{$property}: $specs; 5 | #{$property}: $specs; 6 | } 7 | 8 | @mixin border-radius($specs) { 9 | @include constructor(border-radius, #{$specs}) 10 | } 11 | 12 | @mixin box-shadow($specs) { 13 | @include constructor(box-shadow, $specs) 14 | } 15 | 16 | @mixin transition($specs...) { 17 | @include constructor(transition, $specs) 18 | } 19 | 20 | @mixin transform($specs) { 21 | @include constructor(transform, $specs) 22 | } 23 | 24 | @mixin transform-style($specs) { 25 | @include constructor(transform-style, $specs) 26 | } 27 | 28 | @mixin perspective($specs) { 29 | @include constructor(perspective, $specs) 30 | } 31 | 32 | @mixin blur($specs) { 33 | @include constructor(filter, blur($specs)) 34 | } 35 | 36 | @mixin forceGpu() { 37 | @include constructor(transform, translateZ(0)) 38 | } 39 | 40 | @mixin bg-gradient($specs...) { 41 | background: -webkit-linear-gradient($specs); 42 | background: -moz-linear-gradient($specs); 43 | background: -o-linear-gradient($specs); 44 | background: linear-gradient($specs); 45 | } 46 | 47 | @mixin animation($animate...) { 48 | $max: length($animate); 49 | $animations: ''; 50 | 51 | @for $i from 1 through $max { 52 | $animations: #{$animations + nth($animate, $i)}; 53 | 54 | @if $i < $max { 55 | $animations: #{$animations + ", "}; 56 | } 57 | } 58 | -webkit-animation: $animations; 59 | -moz-animation: $animations; 60 | -o-animation: $animations; 61 | animation: $animations; 62 | } 63 | 64 | @mixin keyframes($animationName) { 65 | @-webkit-keyframes #{$animationName} { 66 | @content; 67 | } 68 | @-moz-keyframes #{$animationName} { 69 | @content; 70 | } 71 | @-o-keyframes #{$animationName} { 72 | @content; 73 | } 74 | @keyframes #{$animationName} { 75 | @content; 76 | } 77 | } 78 | 79 | @mixin no-animation() { 80 | @include transition(#{"none !important"}); 81 | @include animation(#{"none !important"}); 82 | 83 | $no-duration: "0s !important"; 84 | } 85 | 86 | @mixin input-placeholder($color) { 87 | &::-webkit-input-placeholder { 88 | color: $color; 89 | } 90 | 91 | &::-moz-placeholder { /* Firefox 19+ */ 92 | color: $color; 93 | } 94 | 95 | &:-ms-input-placeholder { 96 | color: $color; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "your-app", 3 | "version": "1.0.0", 4 | "description": "Random Chat App", 5 | "dependencies": { 6 | "@fortawesome/fontawesome-svg-core": "^1.2.28", 7 | "@fortawesome/free-solid-svg-icons": "^5.13.0", 8 | "@fortawesome/react-fontawesome": "^0.1.9", 9 | "@mdi/js": "^5.2.45", 10 | "@mdi/react": "^1.4.0", 11 | "axios": "^0.19.2", 12 | "babel-runtime": "^6.26.0", 13 | "bcrypt": "^3.0.8", 14 | "body-parser": "^1.19.0", 15 | "cookie-parser": "^1.4.5", 16 | "cors": "^2.8.5", 17 | "draft-js": "^0.11.5", 18 | "draft-js-export-html": "^1.4.1", 19 | "draftjs-to-html": "^0.9.1", 20 | "emoji-mart": "^3.0.0", 21 | "express": "4.17.1", 22 | "express-fileupload": "^1.1.7-alpha.3", 23 | "fs": "0.0.1-security", 24 | "html-to-draftjs": "^1.5.0", 25 | "http": "0.0.1-security", 26 | "jsonwebtoken": "^8.5.1", 27 | "jwt-decode": "^2.2.0", 28 | "lodash": "^4.17.15", 29 | "mdbreact": "4.25.6", 30 | "mongoose": "^5.9.12", 31 | "morgan": "^1.10.0", 32 | "node-sass": "^4.14.1", 33 | "nodemon": "^2.0.3", 34 | "path": "^0.12.7", 35 | "random-ip": "0.0.1", 36 | "rc-slider": "^9.2.4", 37 | "rc-tooltip": "^4.0.3", 38 | "react": "^16.12.0", 39 | "react-bootstrap": "^0.32.4", 40 | "react-chat-elements": "^10.9.2", 41 | "react-color": "^2.18.1", 42 | "react-dom": "^16.12.0", 43 | "react-draft-wysiwyg": "^1.14.5", 44 | "react-feather": "^2.0.8", 45 | "react-notifications": "^1.6.0", 46 | "react-render-html": "^0.6.0", 47 | "react-router-dom": "^5.1.2", 48 | "react-scripts": "^3.4.1", 49 | "socket.io": "^2.3.0", 50 | "socket.io-client": "^2.3.0", 51 | "whatwg-fetch": "^2.0.4" 52 | }, 53 | "scripts": { 54 | "start": "nodemon server/server.js", 55 | "client": "react-scripts start", 56 | "build": "react-scripts build" 57 | }, 58 | "devDependencies": { 59 | "dotenv": "^8.2.0", 60 | "renamer": "^1.0.0", 61 | "rimraf": "^2.7.1" 62 | }, 63 | "engines": { 64 | "node": "12.8.1", 65 | "yarn": "1.22.0" 66 | }, 67 | "browserslist": [ 68 | ">0.2%", 69 | "not dead", 70 | "not ie <= 11", 71 | "not op_mini all" 72 | ] 73 | } 74 | -------------------------------------------------------------------------------- /src/assets/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/pages/GuestSign/GuestSign.css: -------------------------------------------------------------------------------- 1 | .sign-title { 2 | background-color: #33b5e5; 3 | color: white; 4 | margin-bottom: 30px; 5 | } 6 | 7 | .sign-title img { 8 | width: 120px; 9 | padding: 15px; 10 | border: 2px solid white; 11 | border-radius: 50%; 12 | margin: 10px; 13 | } 14 | 15 | .sign-title h5 { 16 | padding: 20px; 17 | margin-bottom: 0px !important; 18 | color: white !important; 19 | } 20 | 21 | .radio { 22 | margin: 0.5rem; 23 | } 24 | 25 | .radio input[type="radio"] { 26 | position: absolute; 27 | opacity: 0; 28 | } 29 | 30 | .radio input[type="radio"]:checked+.radio-label:before { 31 | background-color: #3197EE; 32 | box-shadow: inset 0 0 0 4px #f4f4f4; 33 | } 34 | 35 | .radio input[type="radio"]+.radio-label:before { 36 | content: ''; 37 | background: #f4f4f4; 38 | border-radius: 100%; 39 | border: 1px solid #b4b4b4; 40 | display: inline-block; 41 | width: 1.4em; 42 | height: 1.4em; 43 | position: relative; 44 | top: -0.2em; 45 | margin-right: 1em; 46 | vertical-align: top; 47 | cursor: pointer; 48 | text-align: center; 49 | -webkit-transition: all 250ms ease; 50 | transition: all 250ms ease; 51 | } 52 | 53 | select { 54 | margin-bottom: 1.5rem; 55 | } 56 | 57 | .form-label { 58 | font-size: 13px; 59 | font-weight: 300; 60 | color: #757575; 61 | margin-bottom: 0px; 62 | } 63 | 64 | .react-numeric-input { 65 | margin-bottom: 0rem; 66 | } 67 | 68 | .radio-group { 69 | display: flex; 70 | flex-flow: row wrap; 71 | align-items: center; 72 | } 73 | 74 | .md-form { 75 | border: none !important; 76 | } 77 | 78 | .sign-wrapper { 79 | position: fixed; 80 | left: 0; 81 | top: 0; 82 | width: 100vw; 83 | height: 100vh; 84 | background-image: url('/sign-bg.jpg'); 85 | background-size: cover; 86 | background-repeat: no-repeat; 87 | background-attachment: fixed; 88 | background-position: center; 89 | margin-right: 0px; 90 | margin-left: 0px; 91 | } 92 | 93 | .sign-container { 94 | position: absolute; 95 | left: 50%; 96 | top: 50%; 97 | transform: translate(-50%, -50%); 98 | margin-top: 0px !important; 99 | width: 360px; 100 | height: 600px; 101 | shape-outside: ellipse(20% 50%); 102 | clip-path: ellipse(45% 50%); 103 | margin: 0 auto !important; 104 | } 105 | 106 | .sign-btn-group { 107 | display: flex; 108 | justify-content: center; 109 | } 110 | 111 | .sign-btn-group button { 112 | width: 120px; 113 | padding: 0px; 114 | } -------------------------------------------------------------------------------- /src/components/OnTyping/OnTyping.css: -------------------------------------------------------------------------------- 1 | #circleG { 2 | width: 146px; 3 | } 4 | 5 | .circleG { 6 | background-color: rgb(255, 255, 255); 7 | float: left; 8 | height: 10px; 9 | margin-left: 10px; 10 | width: 10px; 11 | animation-name: bounce_circleG; 12 | -o-animation-name: bounce_circleG; 13 | -ms-animation-name: bounce_circleG; 14 | -webkit-animation-name: bounce_circleG; 15 | -moz-animation-name: bounce_circleG; 16 | animation-duration: 2.24s; 17 | -o-animation-duration: 2.24s; 18 | -ms-animation-duration: 2.24s; 19 | -webkit-animation-duration: 2.24s; 20 | -moz-animation-duration: 2.24s; 21 | animation-iteration-count: infinite; 22 | -o-animation-iteration-count: infinite; 23 | -ms-animation-iteration-count: infinite; 24 | -webkit-animation-iteration-count: infinite; 25 | -moz-animation-iteration-count: infinite; 26 | animation-direction: normal; 27 | -o-animation-direction: normal; 28 | -ms-animation-direction: normal; 29 | -webkit-animation-direction: normal; 30 | -moz-animation-direction: normal; 31 | border-radius: 20px; 32 | -o-border-radius: 20px; 33 | -ms-border-radius: 20px; 34 | -webkit-border-radius: 20px; 35 | -moz-border-radius: 20px; 36 | } 37 | 38 | #circleG_1 { 39 | animation-delay: 0.45s; 40 | -o-animation-delay: 0.45s; 41 | -ms-animation-delay: 0.45s; 42 | -webkit-animation-delay: 0.45s; 43 | -moz-animation-delay: 0.45s; 44 | } 45 | 46 | #circleG_2 { 47 | animation-delay: 1.05s; 48 | -o-animation-delay: 1.05s; 49 | -ms-animation-delay: 1.05s; 50 | -webkit-animation-delay: 1.05s; 51 | -moz-animation-delay: 1.05s; 52 | } 53 | 54 | #circleG_3 { 55 | animation-delay: 1.35s; 56 | -o-animation-delay: 1.35s; 57 | -ms-animation-delay: 1.35s; 58 | -webkit-animation-delay: 1.35s; 59 | -moz-animation-delay: 1.35s; 60 | } 61 | 62 | @keyframes bounce_circleG { 63 | 0% {} 64 | 50% { 65 | background-color: #949aa2; 66 | } 67 | 100% {} 68 | } 69 | 70 | @-o-keyframes bounce_circleG { 71 | 0% {} 72 | 50% { 73 | background-color: #949aa2; 74 | } 75 | 100% {} 76 | } 77 | 78 | @-ms-keyframes bounce_circleG { 79 | 0% {} 80 | 50% { 81 | background-color: #949aa2; 82 | } 83 | 100% {} 84 | } 85 | 86 | @-webkit-keyframes bounce_circleG { 87 | 0% {} 88 | 50% { 89 | background-color: #949aa2; 90 | } 91 | 100% {} 92 | } 93 | 94 | @-moz-keyframes bounce_circleG { 95 | 0% {} 96 | 50% { 97 | background-color: #949aa2; 98 | } 99 | 100% {} 100 | } -------------------------------------------------------------------------------- /src/pages/Chat/CallWindow.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useRef } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import classnames from 'classnames'; 4 | 5 | const getButtonClass = (icon, enabled) => classnames(`btn-action fa ${icon}`, { disable: !enabled }); 6 | 7 | function CallWindow({ peerSrc, localSrc, config, mediaDevice, status, endCall }) { 8 | const peerVideo = useRef(null); 9 | const localVideo = useRef(null); 10 | const [video, setVideo] = useState(config.video); 11 | const [audio, setAudio] = useState(config.audio); 12 | 13 | useEffect(() => { 14 | if (peerVideo.current && peerSrc) peerVideo.current.srcObject = peerSrc; 15 | if (localVideo.current && localSrc) localVideo.current.srcObject = localSrc; 16 | }); 17 | 18 | useEffect(() => { 19 | if (mediaDevice) { 20 | mediaDevice.toggle('Video', video); 21 | mediaDevice.toggle('Audio', audio); 22 | } 23 | }); 24 | 25 | /** 26 | * Turn on/off a media device 27 | * @param {String} deviceType - Type of the device eg: Video, Audio 28 | */ 29 | const toggleMediaDevice = (deviceType) => { 30 | if (deviceType === 'video') { 31 | setVideo(!video); 32 | mediaDevice.toggle('Video'); 33 | } 34 | if (deviceType === 'audio') { 35 | setAudio(!audio); 36 | mediaDevice.toggle('Audio'); 37 | } 38 | }; 39 | 40 | return ( 41 |
42 |
64 | ); 65 | } 66 | 67 | CallWindow.propTypes = { 68 | status: PropTypes.string.isRequired, 69 | localSrc: PropTypes.object, // eslint-disable-line 70 | peerSrc: PropTypes.object, // eslint-disable-line 71 | config: PropTypes.shape({ 72 | audio: PropTypes.bool.isRequired, 73 | video: PropTypes.bool.isRequired 74 | }).isRequired, 75 | mediaDevice: PropTypes.object, // eslint-disable-line 76 | endCall: PropTypes.func.isRequired 77 | }; 78 | 79 | export default CallWindow; 80 | -------------------------------------------------------------------------------- /src/components/ProfileBox/ProfileBox.css: -------------------------------------------------------------------------------- 1 | .profile-container-true { 2 | position: fixed; 3 | margin: 0px; 4 | top: 74px; 5 | right: -15px; 6 | width: 350px; 7 | height: 100%; 8 | z-index: 2; 9 | animation-name: profile-container; 10 | animation-duration: 0.5s; 11 | } 12 | 13 | .profile-container- { 14 | display: none; 15 | } 16 | 17 | @keyframes profile-container { 18 | 0% { 19 | right: -400px 20 | } 21 | 100% { 22 | right: -15px; 23 | } 24 | } 25 | 26 | .profile-container-false { 27 | position: fixed; 28 | margin: 0px; 29 | top: 74px; 30 | right: -400px; 31 | width: 350px; 32 | height: 100%; 33 | animation-name: profile-container-hide; 34 | animation-duration: 0.5s; 35 | } 36 | 37 | @keyframes profile-container-hide { 38 | 0% { 39 | right: -15px 40 | } 41 | 100% { 42 | right: -400px; 43 | } 44 | } 45 | 46 | .jumbotron { 47 | padding: 20px 5px 0px 5px; 48 | height: calc( 100vh - 75px); 49 | overflow: auto; 50 | } 51 | 52 | .card-body { 53 | padding-top: 0px; 54 | padding-bottom: 0px; 55 | text-align: left !important; 56 | } 57 | 58 | .profile-image { 59 | text-align: center; 60 | width: 100%; 61 | position: relative; 62 | } 63 | 64 | .profile-image img { 65 | width: 200px; 66 | height: 200px; 67 | border-radius: 50%; 68 | border: 6px solid #F8F8F8; 69 | box-shadow: 0px 2px 4px 0px rgba(0, 0, 0, 0.1); 70 | } 71 | 72 | .profile-image .upload-button { 73 | position: absolute; 74 | top: 0px; 75 | width: 200px; 76 | height: 200px; 77 | margin-left: calc( 50% - 100px); 78 | border-radius: 50%; 79 | } 80 | 81 | .profile-image .upload-button label { 82 | width: 100%; 83 | height: 100%; 84 | border-radius: 50%; 85 | cursor: pointer; 86 | } 87 | 88 | .profile-image .upload-button input { 89 | display: none; 90 | } 91 | 92 | .profile-image h4 { 93 | padding-top: 20px; 94 | font-weight: 600; 95 | } 96 | 97 | button.close { 98 | position: absolute; 99 | right: 25px; 100 | top: 5px; 101 | } 102 | 103 | .jumbotron::-webkit-scrollbar { 104 | width: 5px; 105 | background: red; 106 | } 107 | 108 | .jumbotron:hover::-webkit-scrollbar-thumb { 109 | display: block; 110 | background: #888; 111 | } 112 | 113 | .jumbotron::-webkit-scrollbar-track { 114 | background: #f1f1f1; 115 | } 116 | 117 | .jumbotron::-webkit-scrollbar-thumb { 118 | /* background: #888; */ 119 | background: transparent; 120 | } 121 | 122 | .jumbotron::-webkit-scrollbar-thumb:hover { 123 | background: #555; 124 | } -------------------------------------------------------------------------------- /server/routes/api/report.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | const User = require('../../models/User'); 3 | 4 | module.exports = (app) => { 5 | app.post('/api/report', (req, res, next) => { 6 | User.find({ 7 | _id: req.body.targetUser._id 8 | }, 9 | (err, prevUser) => { 10 | if (err) { 11 | return res.status(500).send({ 12 | status: false, 13 | message: 'Error: Server Error', 14 | }); 15 | } else { 16 | let reporterList = prevUser[0].report_reason; 17 | let already_report = 0; 18 | for (let i = 0; i < reporterList.length; i++) { 19 | // Check to report already for this signed in user 20 | if (reporterList[i].reporter_id == req.body.reportUser._id) { 21 | already_report++; 22 | } 23 | } 24 | 25 | if (already_report > 0) { 26 | return res.status(200).send({ 27 | status: false, 28 | message: "You have already reported this user before." 29 | }); 30 | } else { 31 | reporterList.push({ reporter_id: req.body.reportUser._id, reason: req.body.reason }); 32 | let report_number = prevUser[0].report_number; 33 | User.findOneAndUpdate({ 34 | _id: req.body.targetUser._id 35 | }, { 36 | $set: { 37 | report_number: Number(prevUser[0].report_number) + 1, 38 | report_reason: reporterList, 39 | isDeleted: report_number == 4 ? true : false 40 | } 41 | }, { 42 | new: true 43 | }, (err, user) => { 44 | if (err) { 45 | console.log(err); 46 | return res.status(500).send({ 47 | status: false, 48 | message: 'Errir: Server Error', 49 | }) 50 | } else { 51 | return res.status(200).send({ 52 | status: true, 53 | message: "You reported successfully" 54 | }); 55 | } 56 | }) 57 | } 58 | 59 | } 60 | } 61 | ) 62 | }); 63 | } -------------------------------------------------------------------------------- /src/components/CallModal/CallModal.css: -------------------------------------------------------------------------------- 1 | .call-modal { 2 | position: absolute; 3 | width: 250px; 4 | padding: 20px; 5 | left: calc(50vw - 125px); 6 | top: calc(50vh - 200px); 7 | background: -webkit-linear-gradient(top, #074055 0%, #030D10 100%); 8 | background: -moz-linear-gradient(top, #074055 0%, #030D10 100%); 9 | background: -o-linear-gradient(top, #074055 0%, #030D10 100%); 10 | background: linear-gradient(to bottom, #074055, #030D10); 11 | background-color: #212529; 12 | -webkit-border-radius: 5px; 13 | -moz-border-radius: 5px; 14 | -o-border-radius: 5px; 15 | border-radius: 5px; 16 | text-align: center; 17 | display: none; 18 | } 19 | 20 | .call-modal.active { 21 | display: block; 22 | background: linear-gradient(to top, #074055 0%, #030D10 100%); 23 | background-color: #212529; 24 | z-index: 9999; 25 | -webkit-animation: blinking 3s infinite linear; 26 | -moz-animation: blinking 3s infinite linear; 27 | -o-animation: blinking 3s infinite linear; 28 | animation: blinking 3s infinite linear; 29 | text-align: center; 30 | text-align: -webkit-center; 31 | } 32 | 33 | @keyframes blinking { 34 | 25% { 35 | transform: scale(0.96) 36 | } 37 | 50% { 38 | transform: scale(1) 39 | } 40 | 75% { 41 | transform: scale(0.96) 42 | } 43 | 100% { 44 | transform: scale(1) 45 | } 46 | } 47 | 48 | .video-control .btn-action, 49 | .call-modal .btn-action { 50 | height: 40px; 51 | width: 40px; 52 | line-height: 10px; 53 | margin: 0px 8px; 54 | font-size: 1.2em; 55 | text-align: center; 56 | border-radius: 50%; 57 | cursor: pointer; 58 | transition-duration: 0.25s; 59 | } 60 | 61 | .btn-action { 62 | outline: none; 63 | border: none; 64 | } 65 | 66 | .btn-action:focus { 67 | outline: none; 68 | border: none; 69 | } 70 | 71 | .hangup { 72 | transform: rotate(-135deg); 73 | } 74 | 75 | .call-modal p { 76 | font-size: 1.5em; 77 | } 78 | 79 | .call-modal span.caller { 80 | color: #00AFF0; 81 | } 82 | 83 | .call-modal .btn-action:not(.hangup) { 84 | background-color: rgb(76, 175, 80); 85 | } 86 | 87 | .call-modal button { 88 | color: white; 89 | } 90 | 91 | .target-avatar { 92 | width: 100px; 93 | height: 100px; 94 | border-radius: 50%; 95 | overflow: hidden; 96 | margin: 30px 0px; 97 | } 98 | 99 | .target-avatar img { 100 | width: 100%; 101 | height: 100%; 102 | } 103 | 104 | .call-modal span.caller { 105 | color: white; 106 | font-size: 18px; 107 | font-weight: 500; 108 | } 109 | 110 | .incoming { 111 | display: block; 112 | font-size: 14px; 113 | color: rgb(150, 150, 150); 114 | margin-bottom: 20px; 115 | } -------------------------------------------------------------------------------- /src/components/UserList.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { ChatList } from "react-chat-elements"; 3 | import FormControl from "react-bootstrap/lib/FormControl"; 4 | import FormGroup from "react-bootstrap/lib/FormGroup"; 5 | 6 | /** 7 | * 8 | * Renders user list 9 | * 10 | * Used on both places Sign-in modal and as ChatList 11 | */ 12 | 13 | export default class UserList extends Component { 14 | state = { 15 | userData: [], 16 | searchQuery: null 17 | }; 18 | componentDidMount() {} 19 | searchInput(e) { 20 | let value = e.target.value; 21 | let searchQuery = null; 22 | if (value) { 23 | searchQuery = value; 24 | } 25 | this.setState({ searchQuery }); 26 | } 27 | /** 28 | * 29 | * Implement filter logic on basis of search query. 30 | */ 31 | getFilteredUserList() { 32 | return !this.state.searchQuery 33 | ? this.props.userData 34 | : this.props.userData.filter(user => 35 | user.name.toLowerCase().includes(this.state.searchQuery.toLowerCase()) 36 | ); 37 | } 38 | render() { 39 | let users = this.getFilteredUserList(); 40 | return ( 41 |
42 | 43 | 48 | 49 | {users.length ? ( 50 | { 53 | let date = null; 54 | let subtitle = ""; 55 | if ( 56 | !this.props.showSignInList && 57 | f.messages && 58 | f.messages.length 59 | ) { 60 | let lastMessage = f.messages[f.messages.length - 1]; 61 | date = new Date(lastMessage.date); 62 | // subtitle = 63 | // (lastMessage.position === "right" ? "You: " : f.name + ": ") + 64 | // lastMessage.text; 65 | subtitle = lastMessage.text; 66 | } 67 | return { 68 | avatar: require(`../static/images/avatar/${f.id}.jpg`), 69 | alt: f.name, 70 | title: f.name, 71 | subtitle: subtitle, 72 | date: date, 73 | unread: f.unread, 74 | user: f, 75 | }; 76 | })} 77 | onClick={ 78 | !this.props.showSignInList 79 | ? this.props.onChatClicked 80 | : this.props.onUserClicked 81 | } 82 | /> 83 | ) : ( 84 |
No users to show.
85 | )} 86 |
87 | ); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/components/CallWindow/callWindow_backup.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useRef } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import classnames from 'classnames'; 4 | import "./CallWindow.css"; 5 | 6 | const getButtonClass = (icon, enabled) => classnames(`btn-action fa ${icon}`, { disable: !enabled }); 7 | 8 | function CallWindow({ peerSrc, localSrc, config, mediaDevice, status, endCall }) { 9 | const peerVideo = useRef(null); 10 | const localVideo = useRef(null); 11 | const [video, setVideo] = useState(config.video); 12 | const [audio, setAudio] = useState(config.audio); 13 | 14 | useEffect(() => { 15 | if (peerVideo.current && peerSrc) peerVideo.current.srcObject = peerSrc; 16 | if (localVideo.current && localSrc) localVideo.current.srcObject = localSrc; 17 | }); 18 | 19 | useEffect(() => { 20 | if (mediaDevice) { 21 | mediaDevice.toggle('Video', video); 22 | mediaDevice.toggle('Audio', audio); 23 | } 24 | }); 25 | 26 | /** 27 | * Turn on/off a media device 28 | * @param {String} deviceType - Type of the device eg: Video, Audio 29 | */ 30 | const toggleMediaDevice = (deviceType) => { 31 | if (deviceType === 'video') { 32 | setVideo(!video); 33 | mediaDevice.toggle('Video'); 34 | } 35 | if (deviceType === 'audio') { 36 | setAudio(!audio); 37 | mediaDevice.toggle('Audio'); 38 | } 39 | }; 40 | 41 | return ( 42 |
43 |
44 |
46 | 47 |
48 |
50 | 51 |
52 |
70 |
71 | ); 72 | } 73 | 74 | CallWindow.propTypes = { 75 | status: PropTypes.string.isRequired, 76 | localSrc: PropTypes.object, // eslint-disable-line 77 | peerSrc: PropTypes.object, // eslint-disable-line 78 | config: PropTypes.shape({ 79 | audio: PropTypes.bool.isRequired, 80 | video: PropTypes.bool.isRequired 81 | }).isRequired, 82 | mediaDevice: PropTypes.object, // eslint-disable-line 83 | endCall: PropTypes.func.isRequired 84 | }; 85 | 86 | export default CallWindow; -------------------------------------------------------------------------------- /src/components/CallWindow/CallWindow.css: -------------------------------------------------------------------------------- 1 | .video-contact { 2 | position: fixed; 3 | width: 300px; 4 | height: auto; 5 | left: 20px; 6 | top: 100px; 7 | object-fit: cover; 8 | overflow: hidden; 9 | z-index: 5; 10 | } 11 | 12 | .call-window #peerVideo { 13 | height: auto; 14 | width: 100%; 15 | border-radius: 6px; 16 | } 17 | 18 | .video-user { 19 | position: fixed; 20 | overflow: hidden; 21 | height: 60px; 22 | width: auto; 23 | right: 60px; 24 | top: 7px; 25 | border-radius: 0px; 26 | } 27 | 28 | .video-user #localVideo { 29 | height: 100%; 30 | width: auto; 31 | max-width: 9.375rem; 32 | border-radius: 6px; 33 | border: 3px solid #fff; 34 | } 35 | 36 | .video-control { 37 | position: absolute; 38 | bottom: 92px; 39 | left: 50%; 40 | transform: translate(-50%); 41 | animation: fade-out 4s; 42 | opacity: 0; 43 | } 44 | 45 | @keyframes fade-out { 46 | 0% { 47 | opacity: 1; 48 | } 49 | 80% { 50 | opacity: 1; 51 | } 52 | 100% { 53 | opacity: 0; 54 | } 55 | } 56 | 57 | .video-control:hover { 58 | animation: fade-in 1s; 59 | opacity: 1; 60 | } 61 | 62 | @keyframes fade-in { 63 | 0% { 64 | opacity: 0; 65 | } 66 | 100% { 67 | opacity: 1; 68 | } 69 | } 70 | 71 | .video-control button { 72 | color: white; 73 | } 74 | 75 | button.fa-video, 76 | button.fa-microphone { 77 | background-color: rgb(76, 175, 80); 78 | } 79 | 80 | button.hangup { 81 | background-color: rgb(244, 67, 54); 82 | } 83 | 84 | .call-modal .btn-action, 85 | .video-control .btn-action { 86 | height: 40px; 87 | width: 40px; 88 | line-height: 10px; 89 | margin: 0 8px; 90 | font-size: 1.2em; 91 | text-align: center; 92 | border-radius: 50%; 93 | cursor: pointer; 94 | transition-duration: .25s; 95 | } 96 | 97 | .btn-action:focus { 98 | outline: none; 99 | border: none; 100 | } 101 | 102 | .video-contact:hover .max-mark { 103 | display: block; 104 | } 105 | 106 | .toggle-contact-video { 107 | position: absolute; 108 | width: 25px; 109 | height: 25px; 110 | border-radius: 50%; 111 | background-color: rgba(23, 35, 34, .75); 112 | cursor: pointer; 113 | } 114 | 115 | .toggle-contact-video:hover { 116 | background-color: rgba(14, 226, 208, 0.75); 117 | } 118 | 119 | .toggle-contact-video img { 120 | width: 100%; 121 | height: 100%; 122 | } 123 | 124 | .max-mark { 125 | top: 3px; 126 | right: 3px; 127 | display: none; 128 | } 129 | 130 | .min-mark { 131 | bottom: 5px; 132 | left: 0px; 133 | } 134 | 135 | .max-video { 136 | opacity: 1; 137 | } 138 | 139 | .min-video { 140 | opacity: 0; 141 | } 142 | 143 | @media only screen and (max-width: 768px) { 144 | .video-user { 145 | right: 47px; 146 | } 147 | } -------------------------------------------------------------------------------- /server/routes/api/admin-sign.js: -------------------------------------------------------------------------------- 1 | const User = require('../../models/User'); 2 | 3 | module.exports = (app) => { 4 | app.post('/api/admin/signin', (req, res, next) => { 5 | 6 | let body = req.body; 7 | let { email, password } = body; 8 | email = email.toLowerCase(); 9 | email = email.trim(); 10 | 11 | User.find({ 12 | email: email 13 | }, (err, users) => { 14 | if (err) { 15 | return res.send({ 16 | success: false, 17 | message: 'Error: server error' 18 | }); 19 | } 20 | 21 | if (users.length != 1) { 22 | return res.send({ 23 | success: false, 24 | message: 'Error: This user does not exist.' 25 | }); 26 | } else { 27 | const user = users[0]; 28 | if (!user.validPassword(password)) { 29 | return res.send({ 30 | success: false, 31 | message: 'Error: Wrong Email or Password' 32 | }); 33 | } else { 34 | return res.send({ 35 | success: true, 36 | message: 'Valid sign in' 37 | }); 38 | } 39 | } 40 | }); 41 | 42 | }); 43 | 44 | app.post('/api/admin/update-profile', (req, res, next) => { 45 | let body = req.body; 46 | let { email, password, confirmPassword } = body; 47 | email = email.toLowerCase(); 48 | email = email.trim(); 49 | 50 | if (password != confirmPassword) { 51 | return res.send({ 52 | success: false, 53 | message: 'Error: Password and confirm password does not match ' 54 | }) 55 | } 56 | 57 | const newUser = new User(); 58 | const hashPassword = newUser.generateHash(password); 59 | 60 | User.findOneAndUpdate({ 61 | role: 'admin' 62 | }, { 63 | $set: { 64 | email: email, 65 | password: hashPassword 66 | } 67 | }, { new: true }, (err, newUser) => { 68 | if (err) { 69 | console.log(err); 70 | return res.send({ 71 | success: false, 72 | message: 'Error: Server Error', 73 | }); 74 | } else { 75 | return res.send({ 76 | success: true, 77 | message: 'Admin information updated successfully', 78 | }); 79 | } 80 | }); 81 | 82 | }); 83 | 84 | app.post('/api/admin/get-info', (req, res, next) => { 85 | 86 | User.find({ 87 | role: 'admin' 88 | }, (err, user) => { 89 | if (err) { 90 | console.log(err); 91 | throw err; 92 | } else { 93 | return res.send({ 94 | success: true, 95 | admin: user[0] 96 | }) 97 | } 98 | }) 99 | }); 100 | } -------------------------------------------------------------------------------- /src/pages/AdminSign/AdminSign.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { MDBRow, MDBCol, MDBInput, MDBBtn, MDBCard, MDBCardBody, MDBModalFooter } from 'mdbreact'; 3 | import 'whatwg-fetch'; 4 | import { 5 | NotificationContainer, 6 | NotificationManager 7 | } from "react-notifications"; 8 | import "react-notifications/lib/notifications.css"; 9 | 10 | import { RESTAPIUrl } from '../../config/config'; 11 | import './AdminSign.css'; 12 | 13 | class AdminSign extends React.Component { 14 | constructor(props) { 15 | super(props); 16 | 17 | this.state = { 18 | email: '' , 19 | password: '', 20 | adminSignLoading: false 21 | } 22 | } 23 | 24 | handleChangeEmail(e) { 25 | console.log("email change", this.state.email) 26 | this.setState({ email: e.target.value }); 27 | } 28 | 29 | handleChangePassword(e) { 30 | this.setState({ password: e.target.value }); 31 | } 32 | 33 | 34 | handleSubmit(event) { 35 | event.preventDefault(); 36 | const { email, password } = this.state; 37 | 38 | this.setState ({ 39 | adminSignLoading: true, 40 | }); 41 | 42 | fetch(RESTAPIUrl + '/api/admin/signin', { 43 | method: 'POST', 44 | headers: { 45 | 'Content-Type': 'application/json' 46 | }, 47 | body: JSON.stringify({ 48 | email, 49 | password 50 | }), 51 | }).then(res =>res.json()) 52 | .then(json => { 53 | console.log('this is json object', json); 54 | if(json.success) { 55 | this.props.history.push("/admin/manage"); 56 | } else { 57 | NotificationManager.error( 58 | `${json.message}` 59 | ); 60 | } 61 | }) 62 | } 63 | render() { 64 | return ( 65 | 66 | 67 | 68 |
69 | 70 |
71 |
Adminiatrator Sign in
72 |
73 | 74 | 75 | 76 | 77 | 78 | 79 |
80 | 85 | {this.state.logInLoading ? 'Authenticating...' : 'SignIn'} 86 | 87 |
88 | 89 |
90 | 91 | 92 | 93 |
94 |
95 |
96 |
97 | ); 98 | } 99 | } 100 | 101 | export default AdminSign; -------------------------------------------------------------------------------- /src/pages/Chat/PeerConnection.js: -------------------------------------------------------------------------------- 1 | import MediaDevice from './MediaDevice'; 2 | import Emitter from './Emitter'; 3 | import socket from './socket'; 4 | 5 | // const PC_CONFIG = { iceServers: [{ urls: ['stun:stun.l.google.com:19302'] }] }; 6 | const PC_CONFIG = { 7 | iceServers: [{ 8 | url: 'turn:numb.viagenie.ca', 9 | credential: 'nothingbutthebest', 10 | username: 'bluesky410219@gmail.com' 11 | }] 12 | }; 13 | 14 | class PeerConnection extends Emitter { 15 | /** 16 | * Create a PeerConnection. 17 | * @param {String} friendID - ID of the friend you want to call. 18 | */ 19 | constructor(friendID) { 20 | super(); 21 | this.pc = new RTCPeerConnection(PC_CONFIG); 22 | this.pc.onicecandidate = (event) => socket.emit('call', { 23 | to: this.friendID, 24 | candidate: event.candidate 25 | }); 26 | this.pc.ontrack = (event) => this.emit('peerStream', event.streams[0]); 27 | 28 | this.mediaDevice = new MediaDevice(); 29 | this.friendID = friendID; 30 | } 31 | 32 | /** 33 | * Starting the call 34 | * @param {Boolean} isCaller 35 | * @param {Object} config - configuration for the call {audio: boolean, video: boolean} 36 | */ 37 | start(isCaller, config) { 38 | this.mediaDevice 39 | .on('stream', (stream) => { 40 | stream.getTracks().forEach((track) => { 41 | this.pc.addTrack(track, stream); 42 | }); 43 | this.emit('localStream', stream); 44 | if (isCaller) socket.emit('request', { to: this.friendID }); 45 | else this.createOffer(); 46 | }) 47 | .start(config); 48 | 49 | return this; 50 | } 51 | 52 | /** 53 | * Stop the call 54 | * @param {Boolean} isStarter 55 | */ 56 | stop(isStarter) { 57 | if (isStarter) { 58 | socket.emit('end', { to: this.friendID }); 59 | } 60 | this.mediaDevice.stop(); 61 | this.pc.close(); 62 | this.pc = null; 63 | this.off(); 64 | return this; 65 | } 66 | 67 | createOffer() { 68 | this.pc.createOffer() 69 | .then(this.getDescription.bind(this)) 70 | .catch((err) => console.log(err)); 71 | return this; 72 | } 73 | 74 | createAnswer() { 75 | this.pc.createAnswer() 76 | .then(this.getDescription.bind(this)) 77 | .catch((err) => console.log(err)); 78 | return this; 79 | } 80 | 81 | getDescription(desc) { 82 | this.pc.setLocalDescription(desc); 83 | socket.emit('call', { to: this.friendID, sdp: desc }); 84 | return this; 85 | } 86 | 87 | /** 88 | * @param {Object} sdp - Session description 89 | */ 90 | setRemoteDescription(sdp) { 91 | const rtcSdp = new RTCSessionDescription(sdp); 92 | this.pc.setRemoteDescription(rtcSdp); 93 | return this; 94 | } 95 | 96 | /** 97 | * @param {Object} candidate - ICE Candidate 98 | */ 99 | addIceCandidate(candidate) { 100 | if (candidate) { 101 | const iceCandidate = new RTCIceCandidate(candidate); 102 | this.pc.addIceCandidate(iceCandidate); 103 | } 104 | return this; 105 | } 106 | } 107 | 108 | export default PeerConnection; -------------------------------------------------------------------------------- /src/assets/React-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/components/CallWindow/CallWindow.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useRef } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import classnames from 'classnames'; 4 | import { RESTAPIUrl } from "../../config/config"; 5 | import "./CallWindow.css"; 6 | 7 | const getButtonClass = (icon, enabled) => classnames(`btn-action fa ${icon}`, { disable: !enabled }); 8 | 9 | function CallWindow({ peerSrc, localSrc, config, mediaDevice, status, endCall }) { 10 | const peerVideo = useRef(null); 11 | const localVideo = useRef(null); 12 | const [video, setVideo] = useState(config.video); 13 | const [audio, setAudio] = useState(config.audio); 14 | const [ toggleVideo, setToggleVideo ] = useState(true); 15 | 16 | useEffect(() => { 17 | if (peerVideo.current && peerSrc) peerVideo.current.srcObject = peerSrc; 18 | if (localVideo.current && localSrc) localVideo.current.srcObject = localSrc; 19 | },[peerSrc, localSrc]); 20 | 21 | useEffect(() => { 22 | if (mediaDevice) { 23 | mediaDevice.toggle('Video', video); 24 | mediaDevice.toggle('Audio', audio); 25 | } 26 | }); 27 | 28 | /** 29 | * Turn on/off a media device 30 | * @param {String} deviceType - Type of the device eg: Video, Audio 31 | */ 32 | const toggleMediaDevice = (deviceType) => { 33 | if (deviceType === 'video') { 34 | setVideo(!video); 35 | mediaDevice.toggle('Video'); 36 | } 37 | if (deviceType === 'audio') { 38 | setAudio(!audio); 39 | mediaDevice.toggle('Audio'); 40 | } 41 | }; 42 | 43 | const toggleVideoContact = () => { 44 | setToggleVideo(!toggleVideo); 45 | } 46 | 47 | return ( 48 |
49 |
50 |
59 | 60 |
61 |
63 | 64 |
65 |
83 |
84 | ); 85 | } 86 | 87 | CallWindow.propTypes = { 88 | status: PropTypes.string.isRequired, 89 | localSrc: PropTypes.object, // eslint-disable-line 90 | peerSrc: PropTypes.object, // eslint-disable-line 91 | config: PropTypes.shape({ 92 | audio: PropTypes.bool.isRequired, 93 | video: PropTypes.bool.isRequired 94 | }).isRequired, 95 | mediaDevice: PropTypes.object, // eslint-disable-line 96 | endCall: PropTypes.func.isRequired 97 | }; 98 | 99 | export default CallWindow; 100 | -------------------------------------------------------------------------------- /yarn-error.log: -------------------------------------------------------------------------------- 1 | Arguments: 2 | C:\Program Files\nodejs\node.exe C:\Program Files (x86)\Yarn\bin\yarn.js add node-sass 3 | 4 | PATH: 5 | C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Windows\System32\OpenSSH\;C:\Program Files\Git\cmd;C:\Program Files\nodejs\;C:\Program Files (x86)\Yarn\bin\;C:\Program Files\Amazon\AWSCLI\bin\;C:\Program Files\PuTTY\;F:\xampp\php;C:\composer;C:\Users\dagger\AppData\Local\Programs\Python\Python37\Scripts\;C:\Users\dagger\AppData\Local\Programs\Python\Python37\;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Windows\System32\OpenSSH\;C:\Program Files\Git\cmd;C:\Program Files\nodejs\;C:\Program Files (x86)\Yarn\bin\;C:\Program Files\Amazon\AWSCLI\bin\;C:\Program Files\PuTTY\;C:\Users\dagger\AppData\Local\Microsoft\WindowsApps;C:\Users\dagger\AppData\Roaming\npm;C:\Users\dagger\AppData\Local\Programs\Microsoft VS Code\bin;C:\Users\dagger\AppData\Local\Yarn\bin;C:\Program Files\Git;C:\Program Files\Amazon\AWSCLI;C:\Python34\Scripts;C:\Program Files\JetBrains\PyCharm 2019.3.3\bin;;C:\Users\dagger\AppData\Roaming\Composer\vendor\bin;F:\xampp\mysql\bin; 6 | 7 | Yarn version: 8 | 1.19.2 9 | 10 | Node version: 11 | 10.16.2 12 | 13 | Platform: 14 | win32 x64 15 | 16 | Trace: 17 | Error: EPERM: operation not permitted, unlink 'E:\Work\React\MDB react\node_modules\bcrypt\lib\binding\bcrypt_lib.node' 18 | 19 | npm manifest: 20 | { 21 | "name": "your-app", 22 | "version": "0.1.0", 23 | "private": true, 24 | "// If You want to install mdbreact from our GitLab repository, please replace '4.25.6' with gitURL provided here ->": "git+https://oauth2:@git.mdbootstrap.com/mdb/react/re-pro.git", 25 | "dependencies": { 26 | "@fortawesome/fontawesome-svg-core": "^1.2.28", 27 | "@fortawesome/free-solid-svg-icons": "^5.13.0", 28 | "@fortawesome/react-fontawesome": "^0.1.9", 29 | "axios": "^0.19.2", 30 | "bcrypt": "^3.0.8", 31 | "body-parser": "^1.19.0", 32 | "cookie-parser": "^1.4.5", 33 | "cors": "^2.8.5", 34 | "draft-js": "^0.11.5", 35 | "draftjs-to-html": "^0.9.1", 36 | "emoji-mart": "^3.0.0", 37 | "express-fileupload": "^1.1.7-alpha.3", 38 | "html-to-draftjs": "^1.5.0", 39 | "http": "0.0.1-security", 40 | "jsonwebtoken": "^8.5.1", 41 | "jwt-decode": "^2.2.0", 42 | "mdbreact": "4.25.6", 43 | "mongoose": "^5.4.4", 44 | "morgan": "^1.10.0", 45 | "nodemon": "^2.0.3", 46 | "random-ip": "0.0.1", 47 | "rc-slider": "^9.2.4", 48 | "rc-tooltip": "^4.0.3", 49 | "react": "^16.12.0", 50 | "react-bootstrap": "^0.32.4", 51 | "react-chat-elements": "^10.8.0", 52 | "react-dom": "^16.12.0", 53 | "react-draft-wysiwyg": "^1.14.5", 54 | "react-feather": "^2.0.4", 55 | "react-notifications": "^1.6.0", 56 | "react-render-html": "^0.6.0", 57 | "react-router-dom": "^5.1.2", 58 | "react-scripts": "3.4.0", 59 | "socket.io": "^2.3.0", 60 | "socket.io-client": "^2.3.0", 61 | "whatwg-fetch": "^2.0.4" 62 | }, 63 | "scripts": { 64 | "start": "react-scripts start", 65 | "server": "nodemon server/server", 66 | "build": "react-scripts build", 67 | "test": "react-scripts test --env=jsdom", 68 | "eject": "react-scripts eject", 69 | "rename": "renamer --find App-clear.js --replace App.js ./src/App-clear.js && renamer --find style.css --replace index.css ./src/style.css ", 70 | "remove-demo": "rimraf ./src/assets ./src/components ./src/pages ./src/Routes.js ./src/App.js ./src/index.css && npm run rename" 71 | }, 72 | "devDependencies": { 73 | "dotenv": "^8.2.0", 74 | "renamer": "^1.0.0", 75 | "rimraf": "^2.6.2" 76 | }, 77 | "browserslist": [ 78 | ">0.2%", 79 | "not dead", 80 | "not ie <= 11", 81 | "not op_mini all" 82 | ] 83 | } 84 | 85 | yarn manifest: 86 | No manifest 87 | 88 | Lockfile: 89 | No lockfile 90 | -------------------------------------------------------------------------------- /src/config/country.js: -------------------------------------------------------------------------------- 1 | export const countries = [ 2 | "Afghanistan", 3 | "Albania", 4 | "Algeria", 5 | "Andorra", 6 | "Angola", 7 | "Antigua and Barbuda", 8 | "Argentina", 9 | "Armenia", 10 | "Australia", 11 | "Austria", 12 | "Azerbaijan", 13 | "Bahamas", 14 | "Bahrain", 15 | "Bangladesh", 16 | "Barbados", 17 | "Belarus", 18 | "Belgium", 19 | "Belize", 20 | "Benin", 21 | "Bhutan", 22 | "Bolivia", 23 | "Bosnia and Herzegovina", 24 | "Botswana", 25 | "Brazil", 26 | "Brunei", 27 | "Bulgaria", 28 | "Burkina Faso", 29 | "Burundi", 30 | "Cabo Verde", 31 | "Cambodia", 32 | "Cameroon", 33 | "Canada", 34 | "Central African Republic (CAR)", 35 | "Chad", 36 | "Chile", 37 | "China", 38 | "Colombia", 39 | "Comoros", 40 | "Costa Rica", 41 | "Cote d'Ivoire", 42 | "Croatia", 43 | "Cuba", 44 | "Cyprus", 45 | "Czech Republic", 46 | "Denmark", 47 | "Djibouti", 48 | "Dominica", 49 | "Dominican Republic", 50 | "Ecuador", 51 | "Egypt", 52 | "El Salvador", 53 | "Equatorial Guinea", 54 | "Eritrea", 55 | "Estonia", 56 | "Ethiopia", 57 | "Fiji", 58 | "Finland", 59 | "France", 60 | "Gabon", 61 | "Gambia", 62 | "Georgia", 63 | "Germany", 64 | "Ghana", 65 | "Greece", 66 | "Grenada", 67 | "Guatemala", 68 | "Guinea", 69 | "Guinea-Bissau", 70 | "Guyana", 71 | "Haiti", 72 | "Honduras", 73 | "Hungary", 74 | "Iceland", 75 | "India", 76 | "Indonesia", 77 | "Iran", 78 | "Iraq", 79 | "Ireland", 80 | "Israel", 81 | "Italy", 82 | "Jamaica", 83 | "Japan", 84 | "Jordan", 85 | "Kazakhstan", 86 | "Kenya", 87 | "Kiribati", 88 | "Kosovo", 89 | "Kuwait", 90 | "Kyrgyzstan", 91 | "Laos", 92 | "Latvia", 93 | "Lebanon", 94 | "Lesotho", 95 | "Liberia", 96 | "Libya", 97 | "Liechtenstein", 98 | "Lithuania", 99 | "Luxembourg", 100 | "Macedonia (FYROM)", 101 | "Madagascar", 102 | "Malawi", 103 | "Malaysia", 104 | "Maldives", 105 | "Mali", 106 | "Malta", 107 | "Marshall Islands", 108 | "Mauritania", 109 | "Mauritius", 110 | "Mexico", 111 | "Micronesia", 112 | "Moldova", 113 | "Monaco", 114 | "Mongolia", 115 | "Montenegro", 116 | "Morocco", 117 | "Mozambique", 118 | "Myanmar (Burma)", 119 | "Namibia", 120 | "Nauru", 121 | "Nepal", 122 | "Netherlands", 123 | "New Zealand", 124 | "Nicaragua", 125 | "Niger", 126 | "Nigeria", 127 | "North Korea", 128 | "Norway", 129 | "Oman", 130 | "Pakistan", 131 | "Palau", 132 | "Palestine", 133 | "Panama", 134 | "Papua New Guinea", 135 | "Paraguay", 136 | "Peru", 137 | "Philippines", 138 | "Poland", 139 | "Portugal", 140 | "Qatar", 141 | "Romania", 142 | "Russia", 143 | "Rwanda", 144 | "Saint Kitts and Nevis", 145 | "Saint Lucia", 146 | "Saint Vincent and the Grenadines", 147 | "Samoa", 148 | "San Marino", 149 | "Sao Tome and Principe", 150 | "Saudi Arabia", 151 | "Senegal", 152 | "Serbia", 153 | "Seychelles", 154 | "Sierra Leone", 155 | "Singapore", 156 | "Slovakia", 157 | "Slovenia", 158 | "Solomon Islands", 159 | "Somalia", 160 | "South Africa", 161 | "South Korea", 162 | "South Sudan", 163 | "Spain", 164 | "Sri Lanka", 165 | "Sudan", 166 | "Suriname", 167 | "Swaziland", 168 | "Sweden", 169 | "Switzerland", 170 | "Syria", 171 | "Taiwan", 172 | "Tajikistan", 173 | "Tanzania", 174 | "Thailand", 175 | "Timor-Leste", 176 | "Togo", 177 | "Tonga", 178 | "Trinidad and Tobago", 179 | "Tunisia", 180 | "Turkey", 181 | "Turkmenistan", 182 | "Tuvalu", 183 | "Uganda", 184 | "Ukraine", 185 | "United Arab Emirates (UAE)", 186 | "United Kingdom (UK)", 187 | "United States of America (USA)", 188 | "Uruguay", 189 | "Uzbekistan", 190 | "Vanuatu", 191 | "Vatican City (Holy See)", 192 | "Venezuela", 193 | "Vietnam", 194 | "Yemen", 195 | "Zambia", 196 | "Zimbabwe" 197 | ]; -------------------------------------------------------------------------------- /src/registerServiceWorker.js: -------------------------------------------------------------------------------- 1 | // In production, we register a service worker to serve assets from local cache. 2 | 3 | // This lets the app load faster on subsequent visits in production, and gives 4 | // it offline capabilities. However, it also means that developers (and users) 5 | // will only see deployed updates on the "N+1" visit to a page, since previously 6 | // cached resources are updated in the background. 7 | 8 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy. 9 | // This link also includes instructions on opting out of this behavior. 10 | 11 | const isLocalhost = Boolean( 12 | window.location.hostname === 'localhost' || 13 | // [::1] is the IPv6 localhost address. 14 | window.location.hostname === '[::1]' || 15 | // 127.0.0.1/8 is considered localhost for IPv4. 16 | window.location.hostname.match( 17 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 18 | ) 19 | ); 20 | 21 | export default function register() { 22 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 23 | // The URL constructor is available in all browsers that support SW. 24 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location); 25 | if (publicUrl.origin !== window.location.origin) { 26 | // Our service worker won't work if PUBLIC_URL is on a different origin 27 | // from what our page is served on. This might happen if a CDN is used to 28 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374 29 | return; 30 | } 31 | 32 | window.addEventListener('load', () => { 33 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 34 | 35 | if (!isLocalhost) { 36 | // Is not local host. Just register service worker 37 | registerValidSW(swUrl); 38 | } else { 39 | // This is running on localhost. Lets check if a service worker still exists or not. 40 | checkValidServiceWorker(swUrl); 41 | } 42 | }); 43 | } 44 | } 45 | 46 | function registerValidSW(swUrl) { 47 | navigator.serviceWorker 48 | .register(swUrl) 49 | .then(registration => { 50 | registration.onupdatefound = () => { 51 | const installingWorker = registration.installing; 52 | installingWorker.onstatechange = () => { 53 | if (installingWorker.state === 'installed') { 54 | if (navigator.serviceWorker.controller) { 55 | // At this point, the old content will have been purged and 56 | // the fresh content will have been added to the cache. 57 | // It's the perfect time to display a "New content is 58 | // available; please refresh." message in your web app. 59 | console.log('New content is available; please refresh.'); 60 | } else { 61 | // At this point, everything has been precached. 62 | // It's the perfect time to display a 63 | // "Content is cached for offline use." message. 64 | console.log('Content is cached for offline use.'); 65 | } 66 | } 67 | }; 68 | }; 69 | }) 70 | .catch(error => { 71 | console.error('Error during service worker registration:', error); 72 | }); 73 | } 74 | 75 | function checkValidServiceWorker(swUrl) { 76 | // Check if the service worker can be found. If it can't reload the page. 77 | fetch(swUrl) 78 | .then(response => { 79 | // Ensure service worker exists, and that we really are getting a JS file. 80 | if ( 81 | response.status === 404 || 82 | response.headers.get('content-type').indexOf('javascript') === -1 83 | ) { 84 | // No service worker found. Probably a different app. Reload the page. 85 | navigator.serviceWorker.ready.then(registration => { 86 | registration.unregister().then(() => { 87 | window.location.reload(); 88 | }); 89 | }); 90 | } else { 91 | // Service worker found. Proceed as normal. 92 | registerValidSW(swUrl); 93 | } 94 | }) 95 | .catch(() => { 96 | console.log( 97 | 'No internet connection found. App is running in offline mode.' 98 | ); 99 | }); 100 | } 101 | 102 | export function unregister() { 103 | if ('serviceWorker' in navigator) { 104 | navigator.serviceWorker.ready.then(registration => { 105 | registration.unregister(); 106 | }); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /server/routes/api/profile.js: -------------------------------------------------------------------------------- 1 | const jwt = require('jsonwebtoken'); 2 | var fs = require('fs'); 3 | const User = require('../../models/User'); 4 | 5 | module.exports = (app) => { 6 | app.post('/api/profile/image', (req, res, next) => { 7 | 8 | let profileImage = req.files.file; 9 | let uploadDir = 'public/profile/'; 10 | 11 | let prevFileFullName = req.body.fileName; 12 | let prevFileName = prevFileFullName.substring(0, prevFileFullName.indexOf(".")); 13 | let prevFileType = prevFileFullName.substring(prevFileFullName.indexOf(".") + 1); 14 | 15 | // Create directory for upload file when profile directory doesn't exist in public directory 16 | if (!fs.existsSync(uploadDir)) { 17 | fs.mkdirSync(uploadDir); 18 | } 19 | // Delete prev file. 20 | if (fs.existsSync(`${uploadDir}${prevFileFullName}`)) { 21 | fs.unlink(`${uploadDir}${prevFileFullName}`, function() { 22 | // Get the file type of uploaded file. 23 | let fileType = profileImage.name.substring(profileImage.name.indexOf(".") + 1); 24 | 25 | profileImage.mv(`${uploadDir}${prevFileName}.${fileType}`, function(err) { 26 | if (err) { 27 | return res.status(500).send(err); 28 | } 29 | // When profile image's filetype changed, then should change token information 30 | User.findOneAndUpdate({ 31 | _id: prevFileName 32 | }, { 33 | $set: { 34 | profile_image: `${prevFileName}.${fileType}` 35 | } 36 | }, { 37 | new: true 38 | }, (err, newUser) => { 39 | let token = jwt.sign({ user: newUser }, 40 | 'secret', { 41 | expiresIn: 31556926 // 1 year in second 42 | }, 43 | (err, token) => { 44 | if (err) { 45 | console.log('Failed to create token', err); 46 | } else { 47 | return res.status(200).send({ 48 | status: true, 49 | message: "Updated profile image successfully", 50 | token: token 51 | }); 52 | } 53 | } 54 | ) 55 | }); 56 | // res.json({ file: `${uploadDir}${prevFileName}.${fileType}` }); 57 | }); 58 | 59 | }); 60 | } else { 61 | console.log("File does not exist") 62 | } 63 | 64 | }); 65 | 66 | app.post('/api/profile/update', (req, res, next) => { 67 | if (req.body.age > 99 || req.body.age < 13) { 68 | return res.status(500).send({ 69 | status: false, 70 | message: 'Validation Error: Specified attribute is not between the expected ages of 13 and 99.', 71 | }); 72 | } 73 | // When profile image's filetype changed, then should change token information 74 | User.findOneAndUpdate({ 75 | _id: req.body._id 76 | }, { 77 | $set: { 78 | userName: req.body.userName, 79 | gender: req.body.gender, 80 | age: req.body.age, 81 | location: req.body.location 82 | } 83 | }, { 84 | new: true 85 | }, (err, newUser) => { 86 | let token = jwt.sign({ user: newUser }, 87 | 'secret', { 88 | expiresIn: 31556926 // 1 year in second 89 | }, 90 | (err, token) => { 91 | if (err) { 92 | console.log('Failed to create token', err); 93 | return res.status(500).send({ 94 | status: false, 95 | message: 'Error: Server error.', 96 | }); 97 | } else { 98 | return res.status(200).send({ 99 | status: true, 100 | message: "Updated profile successfully", 101 | token: token 102 | }); 103 | } 104 | } 105 | ) 106 | }); 107 | 108 | 109 | }); 110 | }; -------------------------------------------------------------------------------- /src/pages/HomePage/HomePage.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { MDBNavbar, MDBNavbarBrand, MDBNavbarNav, MDBNavbarToggler, MDBCollapse, MDBNavItem, MDBNavLink, MDBContainer, MDBMask, MDBView, MDBBtn, MDBIcon } from 'mdbreact'; 3 | import { RESTAPIUrl} from "../../config/config"; 4 | import './HomePage.css'; 5 | 6 | class FullPageIntroWithFixedTransparentNavbar extends React.Component { 7 | constructor(props) { 8 | super(props); 9 | this.state = { 10 | collapse: false, 11 | isWideEnough: false, 12 | restricted: false 13 | }; 14 | this.onClick = this.onClick.bind(this); 15 | } 16 | 17 | componentDidMount() { 18 | this.verifyAccount(); 19 | } 20 | 21 | verifyAccount() { 22 | fetch(RESTAPIUrl + '/api/guest/ipverify', { 23 | method: 'POST', 24 | headers: { 25 | 'Content-Type': 'application/json' 26 | }, 27 | body: JSON.stringify({ 28 | }), 29 | }) 30 | .then(res =>res.json()) 31 | .then(json => { 32 | if(!json.status) { 33 | this.setState({restricted: true}); 34 | }else { 35 | this.setState({restricted: false}); 36 | } 37 | }) 38 | } 39 | 40 | onClick() { 41 | this.setState({ 42 | collapse: !this.state.collapse, 43 | }); 44 | } 45 | 46 | render() { 47 | return ( 48 |
49 |
50 | 51 | 52 | 53 | 54 | Chat With Stranger 55 | 56 | {!this.state.isWideEnough && } 57 | 58 | 59 | 60 | 64 | GUEST 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 |

Random Video, Text & Audio Chat with Strangers



76 |
It provides free random chat with cool people in private chat rooms.
77 |

Chat with strangers & send pictures, videos in private free chat rooms. Meet & talk to strangers from all over the world.


78 |
79 | 82 | 87 | Chat as a guest 88 | 89 | 90 |
91 | {this.state.restricted ?

Your ip address is restricted from this chat

: null} 92 |
93 |
94 |
95 | 96 |
97 | 98 |

Texting strangers might come naturally to some but others may have goose bumps when they are trying to text strangers. When you text strangers you are opening a window into a great text chat with strangers. The first few texts are the most difficult ones, once you have started texting and managed to move past opening texts the rest is easy peasy lemon squeezy. Sending a text to strangers is just like letter writing which was back then known as making pen pals. However, these days text to strangers chat room is a modern way of connecting with strangers. When you text strangers you are at the first step of a great conversation that could blossom into a good friendship or even a relationship. There are many examples in the world where people have found their soulmates when they were random texting strangers online. Many people online text with strangers in order to find some company and to talk to someone. When you send a text to strangers what happens next is a total mystery. It might end abruptly a couple of times whereas other times it might turn into a beautiful conversation and a pleasant experience.

99 |
100 |
101 |
102 | ); 103 | } 104 | } 105 | 106 | export default FullPageIntroWithFixedTransparentNavbar; -------------------------------------------------------------------------------- /src/pages/UserSign/UserSign.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import './UserSign.css'; 3 | import { 4 | Grid, 5 | Row, 6 | Col, 7 | Tabs, 8 | Tab, 9 | Button 10 | } from 'react-bootstrap'; 11 | import SignUp from '../Signup/Signup'; 12 | import LogIn from '../Login/Login'; 13 | import { 14 | getFromStorage, 15 | } from '../../utils/storage'; 16 | import { RESTAPIUrl } from '../../config/config'; 17 | 18 | class UserSign extends Component { 19 | constructor( props, context) { 20 | super(props, context); 21 | 22 | this.stateChanger = this.stateChanger.bind(this); 23 | this.logOutClicked = this.logOutClicked.bind(this); 24 | 25 | this.state = { 26 | loggedIn: false, 27 | token: '', 28 | loggedInName: 'Unnamed Ashok Kumar', 29 | logOutButtonStatus: 'warning', 30 | logOutLoadingMessage: 'Log Out', 31 | logOutLoading: false, 32 | 33 | 34 | 35 | } 36 | } 37 | 38 | stateChanger(newState) { 39 | this.setState(newState); 40 | } 41 | 42 | componentDidMount() { 43 | const obj = getFromStorage('the_login_n_signup'); 44 | if (obj && obj.token) { 45 | const { token } = obj; 46 | const { name } = obj; 47 | // Verify token 48 | fetch( RESTAPIUrl + '/api/account/verify?token=' + token) 49 | .then(res => res.json()) 50 | .then(json => { 51 | if (json.success) { 52 | this.setState({ 53 | token, 54 | loggedInName: name, 55 | logOutLoading: false, 56 | loggedIn: true, 57 | }); 58 | } else { 59 | this.setState({ 60 | logOutLoading: false, 61 | }); 62 | } 63 | }); 64 | } else { 65 | this.setState({ 66 | logOutLoading: false, 67 | }); 68 | } 69 | } 70 | 71 | logOutClicked() { 72 | this.setState({ 73 | logOutLoading: true, 74 | logOutLoadingMessage: 'Logging Out...', 75 | logOutButtonStatus: 'info', 76 | }); 77 | const obj = getFromStorage('the_login_n_signup'); 78 | if (obj && obj.token) { 79 | const { token } = obj; 80 | // Verify token 81 | fetch(RESTAPIUrl + '/api/account/logout?token=' + token) 82 | .then(res => { 83 | //console.log(res); 84 | return res.json(); 85 | }) 86 | .then(json => { 87 | if (json.success) { 88 | this.setState({ 89 | token: '', 90 | logOutLoading: false, 91 | loggedIn: false, 92 | }); 93 | } else { 94 | this.setState({ 95 | logOutLoading: false, 96 | }); 97 | } 98 | }); 99 | } else { 100 | this.setState({ 101 | logOutLoading: false, 102 | }); 103 | } 104 | 105 | } 106 | 107 | 108 | render() { 109 | return ( 110 |
111 |
112 | 113 |

Login Or Sign-Up

114 |
115 | 116 | 117 | 118 | 119 | {this.state.loggedIn ? 120 |

Welcome {this.state.loggedInName}!

121 | 129 |
130 | : 131 | 132 |
133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 |
} 142 | 143 | 144 | 145 |
146 |
147 |
148 | ); 149 | } 150 | } 151 | 152 | export default UserSign; 153 | -------------------------------------------------------------------------------- /src/pages/GuestSign/GuestSign.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { MDBRow, MDBCol, MDBInput, MDBBtn, MDBCard, MDBCardBody, MDBModalFooter, MDBInputSelect } from 'mdbreact'; 3 | import 'whatwg-fetch'; 4 | import { 5 | NotificationContainer, 6 | NotificationManager 7 | } from "react-notifications"; 8 | import { setInStorage } from '../../utils/storage'; 9 | import { RESTAPIUrl } from '../../config/config'; 10 | import { countries } from "../../config/country"; 11 | import './GuestSign.css'; 12 | 13 | class GuestSign extends React.Component { 14 | constructor(props, context) { 15 | super(props, context); 16 | this.handleChangeName = this.handleChangeName.bind(this); 17 | this.handleChangeLocation = this.handleChangeLocation.bind(this); 18 | this.handleChangeAge = this.handleChangeAge.bind(this); 19 | this.handleChangeGender = this.handleChangeGender.bind(this); 20 | this.handleSubmit = this.handleSubmit.bind(this); 21 | 22 | this.state = { 23 | userName: '', 24 | location: 'Canada', 25 | age: 28, 26 | gender: 'Male', 27 | loginLoading: false, 28 | } 29 | } 30 | 31 | handleChangeName(e) { 32 | this.setState({ userName: e.target.value }); 33 | } 34 | 35 | handleChangeLocation(e) { 36 | this.setState({ location: e.target.value }); 37 | } 38 | 39 | handleChangeAge(value) { 40 | this.setState({ age: value }); 41 | } 42 | 43 | handleChangeGender(e) { 44 | this.setState({ gender: e.target.value }); 45 | } 46 | 47 | handleSubmit(event) { 48 | event.preventDefault(); 49 | const { userName, location, age, gender } = this.state; 50 | 51 | this.setState ({ 52 | loginLoading: true, 53 | }); 54 | 55 | fetch(RESTAPIUrl + '/api/guest/signin', { 56 | method: 'POST', 57 | headers: { 58 | 'Content-Type': 'application/json' 59 | }, 60 | body: JSON.stringify({ 61 | userName, 62 | location, 63 | age, 64 | gender 65 | }), 66 | }).then(res =>res.json()) 67 | .then(json => { 68 | if(json.status) { 69 | setInStorage('guest_signin', {token:json.token}); 70 | this.props.history.push("/chat"); 71 | } else { 72 | NotificationManager.error( 73 | `${json.message}` 74 | ); 75 | } 76 | }) 77 | } 78 | render() { 79 | return ( 80 | 81 | 82 | 83 |
84 | 85 |
86 | chat-icon 87 |
88 | 89 | 90 | 91 | 92 | 100 | 101 | 102 | 103 | 104 | 105 |
106 |
107 | 108 | 109 |
110 | 111 |
112 | 113 | 114 |
115 |
116 | 117 |
118 | 123 | {this.state.logInLoading ? 'Authenticating...' : 'Start Chat'} 124 | 125 |
126 | 127 |
128 | 129 | 130 | 131 |
132 |
133 |
134 |
135 | ); 136 | } 137 | } 138 | 139 | export default GuestSign; -------------------------------------------------------------------------------- /src/pages/Login/Login.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import './Login.css'; 3 | import { 4 | FormGroup, 5 | FormControl, 6 | Button, 7 | Alert, 8 | } from 'react-bootstrap'; 9 | import 'whatwg-fetch'; 10 | import { 11 | setInStorage, 12 | } from '../../utils/storage'; 13 | import { RESTAPIUrl } from '../../config/config'; 14 | 15 | 16 | class LogIn extends Component { 17 | 18 | constructor(props, context) { 19 | super(props, context); 20 | 21 | this.handleChange = this.handleChange.bind(this); 22 | 23 | this.handleChangeEmail = this.handleChangeEmail.bind(this); 24 | 25 | this.logInClicked = this.logInClicked.bind(this); 26 | 27 | this.handleDismiss = this.handleDismiss.bind(this); 28 | 29 | this.handleShow = this.handleShow.bind(this); 30 | 31 | 32 | this.displayAlert = this.displayAlert.bind(this); 33 | 34 | this.state = { 35 | password: '', 36 | email: '', 37 | logInLoading: false, 38 | signInError: '', 39 | show: false, 40 | logInStatus: 'danger', 41 | 42 | }; 43 | } 44 | 45 | validateEmail() { 46 | if(this.state.email.length ===0) return null; 47 | // eslint-disable-next-line 48 | var re = /^(([^<>()\[\]\.,;:\s@\"]+(\.[^<>()\[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i; 49 | return re.test(String(this.state.email).toLowerCase())?'success':'error'; 50 | } 51 | 52 | getValidationState() { 53 | const length = this.state.password.length; 54 | if (length > 8) return 'success'; 55 | else if (length > 5) return 'warning'; 56 | else if (length > 0) return 'error'; 57 | return null; 58 | } 59 | 60 | handleChange(e) { 61 | this.setState({ password: e.target.value }); 62 | } 63 | 64 | handleChangeEmail(e) { 65 | this.setState({ email: e.target.value }); 66 | } 67 | 68 | logInClicked() { 69 | const { 70 | email, 71 | password, 72 | } = this.state; 73 | 74 | this.setState({ 75 | logInLoading: true, 76 | }); 77 | 78 | fetch( RESTAPIUrl + '/api/account/signin', { 79 | method: 'POST', 80 | headers: { 81 | 'Content-Type': 'application/json' 82 | }, 83 | body: JSON.stringify({ 84 | email, 85 | password, 86 | }), 87 | }).then(res => res.json()) 88 | .then(json => { 89 | console.log('json', json); 90 | if (json.success) { 91 | setInStorage('the_login_n_signup', { token: json.token, name: json.name }); 92 | this.setState({ 93 | signInError: 'Welcome ' + json.name + '!', 94 | logInLoading: false, 95 | password: '', 96 | email: '', 97 | token: json.token, 98 | show: true, 99 | logInStatus: 'success', 100 | }); 101 | 102 | this.props.stateChanger({ 103 | loggedIn: true, 104 | token: json.token, 105 | loggedInName: json.name, 106 | logOutButtonStatus: 'warning', 107 | }); 108 | } else { 109 | this.setState({ 110 | signInError: json.message, 111 | logInLoading: false, 112 | show: true, 113 | logInStatus: 'danger', 114 | }); 115 | } 116 | 117 | }) 118 | 119 | } 120 | 121 | 122 | handleDismiss() { 123 | this.setState({ show: false }); 124 | } 125 | 126 | handleShow() { 127 | this.setState({ show: true }); 128 | } 129 | 130 | displayAlert() { 131 | return ( 132 | 133 | 134 |

135 | { this.state.signInError } 136 |

137 | 138 |
139 | 140 | ); 141 | } 142 | 143 | 144 | 145 | 146 | 147 | render() { 148 | return ( 149 |
150 |
151 | 155 | 156 | 162 | 163 | 164 | 165 | 166 | 167 | 171 | 172 | 178 | 179 | 180 | 181 | 182 | 183 | 184 |
185 | 186 | 194 | 195 | { this.state.show ? this.displayAlert() : null} 196 | 197 | 198 |
199 | 200 | ); 201 | 202 | } 203 | } 204 | 205 | export default LogIn; 206 | 207 | -------------------------------------------------------------------------------- /src/components/ReportBox/ReportBox.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { 3 | MDBJumbotron, 4 | MDBBtn, 5 | MDBRow, 6 | MDBCol, 7 | MDBCardTitle, 8 | MDBCardBody, 9 | MDBCloseIcon, 10 | } from 'mdbreact'; 11 | import { 12 | NotificationManager 13 | } from "react-notifications"; 14 | 15 | import { RESTAPIUrl } from "../../config/config"; 16 | import "./ReportBox.css"; 17 | 18 | export default class ReportBox extends Component { 19 | constructor(props) { 20 | super(props); 21 | this.state = { 22 | reportList: ['Spam', 'Inappropriate or Vulgar Content', 'Illegal Content', 'Abusive behaviour', 'Other'], 23 | reportReason: '', 24 | inputTextActive: false, 25 | } 26 | 27 | } 28 | 29 | handleReport(e) { 30 | e.preventDefault(); 31 | if( typeof this.props.targetUser._id == 'undefined' ) { 32 | NotificationManager.error( 33 | "Please select target user" 34 | ); 35 | return false; 36 | } 37 | fetch(RESTAPIUrl + '/api/report', { 38 | method: 'POST', 39 | headers: { 40 | 'Content-Type': 'application/json' 41 | }, 42 | body: JSON.stringify({ 43 | reportUser: this.props.signedInUser, 44 | targetUser: this.props.targetUser, 45 | reason: this.state.reportReason 46 | }), 47 | }).then(res =>res.json()) 48 | .then(json => { 49 | if(json.status) { 50 | NotificationManager.success( 51 | json.message 52 | ); 53 | } else { 54 | NotificationManager.error( 55 | json.message 56 | ); 57 | } 58 | }); 59 | 60 | } 61 | 62 | handleChangeReportSetting(e) { 63 | if(e.target.value !== "Other") { 64 | this.setState({ 65 | reportReason: e.target.value, 66 | inputTextActive: false, 67 | }) 68 | } else { 69 | this.setState({inputTextActive: true}); 70 | } 71 | } 72 | 73 | handleOtherReason(e) { 74 | this.setState({reportReason: e.target.value}); 75 | } 76 | 77 | onReportModalShow() { 78 | this.props.onReportModalShow(false); 79 | } 80 | 81 | render() { 82 | return ( 83 |
84 | 85 | 86 | 87 | 88 | 89 | 90 | Report User 91 | 92 |
93 | 94 |
95 |
96 |
97 | {this.state.reportList.map((object, i) => 98 | { 99 | return ( 100 |
101 | 109 | 110 | 111 |
112 | ) 113 | }) 114 | } 115 |
116 | 117 |
118 | {this.state.inputTextActive? 119 |
120 |