├── .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 |
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 |
16 |
17 |
18 | {`${callFrom}`}
19 |
20 |
Incoming call...
21 |
26 |
31 |
36 |
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 |
18 |
19 |
20 | {`${contactUser}`}
21 |
22 |
Incoming call...
23 |
28 |
33 |
38 |
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 |
44 |
49 |
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 |
28 | You need to enable JavaScript to run this app.
29 |
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 |
43 |
44 |
45 | toggleMediaDevice('video')}
50 | />
51 | toggleMediaDevice('audio')}
56 | />
57 | endCall(true)}
61 | />
62 |
63 |
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 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 | toggleMediaDevice('video')}
57 | />
58 | toggleMediaDevice('audio')}
63 | />
64 | endCall(true)}
68 | />
69 |
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 |
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 |
51 |
52 |
toggleVideoContact()}
55 | >
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 | toggleMediaDevice('video')}
70 | />
71 | toggleMediaDevice('audio')}
76 | />
77 | endCall(true)}
81 | />
82 |
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 |
127 | {this.state.logOutButtonStatus ? 'Log Out' : 'Logging Out'}
128 |
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 |
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 |
185 |
186 |
192 | {this.state.logInLoading ? 'Authenticating...' : 'Log In'}
193 |
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 |
153 |
154 |
155 |
156 |
157 |
158 |
159 | );
160 | }
161 | }
--------------------------------------------------------------------------------
/src/components/SearchSettingBox/SearchSettingBox.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 Slider from 'rc-slider';
12 | import 'rc-slider/assets/index.css';
13 |
14 | import { countries } from "../../config/country";
15 | import "./SearchSettingBox.css";
16 |
17 | const createSliderWithTooltip = Slider.createSliderWithTooltip;
18 | const Range = createSliderWithTooltip(Slider.Range);
19 |
20 | export default class SearchSettingBox extends Component {
21 | constructor(props) {
22 | super(props);
23 | const temp = this.props.searchSetting
24 | this.state = {
25 | searchSetting: temp,
26 | }
27 |
28 | this.handleChangeGender = this.handleChangeGender.bind(this);
29 | }
30 |
31 | handleChangeSearchSetting(e) {
32 | e.preventDefault();
33 |
34 | }
35 |
36 | handleChangeGender(e) {
37 | let searchSetting = this.state.searchSetting;
38 | searchSetting.gender = e.target.value;
39 | this.setState({ searchSetting });
40 | }
41 |
42 | handleChangeLocation(e) {
43 | let searchSetting = this.state.searchSetting;
44 | searchSetting.location = e.target.value;
45 | this.setState({ searchSetting });
46 | }
47 |
48 | handleChangeAge(value) {
49 | let searchSetting = this.state.searchSetting;
50 | searchSetting.ageMin = value[0];
51 | searchSetting.ageMax = value[1];
52 | this.setState({ searchSetting });
53 |
54 | // this.setState({
55 | // ageMin: value[0],
56 | // ageMax: value[1],
57 | // })
58 | }
59 | onSearchSettingModalShow() {
60 | this.props.onSearchSettingModalShow(false);
61 | }
62 |
63 | render() {
64 | return (
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 | Search Filter Setting
73 |
74 |
75 |
76 |
77 |
78 |
79 | Location
80 |
81 | All
82 | {countries.map((object, i) =>
83 | {
84 | return { object }
85 | })
86 | }
87 |
88 |
89 |
90 |
91 | Age Range
92 | `${value}` } onChange = { this.handleChangeAge.bind(this) }/>
93 |
94 |
95 |
132 |
133 |
134 |
135 |
140 | Close
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 | );
152 | }
153 | }
--------------------------------------------------------------------------------
/src/pages/Signup/Signup.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import './Signup.css';
3 | import {
4 | FormGroup,
5 | FormControl,
6 | HelpBlock,
7 | Button,
8 | Alert,
9 | } from 'react-bootstrap';
10 | import 'whatwg-fetch';
11 | import { RESTAPIUrl } from '../../config/config';
12 |
13 | class SignUp extends Component {
14 |
15 | constructor(props, context) {
16 | super(props, context);
17 |
18 | this.handleChange = this.handleChange.bind(this);
19 |
20 | this.handleChangeEmail = this.handleChangeEmail.bind(this);
21 |
22 | this.handleChangeConfirm = this.handleChangeConfirm.bind(this);
23 |
24 | this.handleChangeName = this.handleChangeName.bind(this);
25 |
26 | this.handleDismiss = this.handleDismiss.bind(this);
27 |
28 | this.handleShow = this.handleShow.bind(this);
29 |
30 |
31 | this.signUpClicked = this.signUpClicked.bind(this);
32 |
33 | this.displayAlert = this.displayAlert.bind(this);
34 |
35 | this.state = {
36 | password: '',
37 | email: '',
38 | confPass: '',
39 | name: '',
40 | signInLoading: false,
41 | show: false,
42 | signupStatus: 'success',
43 | signUpMessage: 'You have signed up successfully. Proceed to login.',
44 |
45 | };
46 | }
47 |
48 | validateEmail() {
49 | if(this.state.email.length ===0) return null;
50 | // eslint-disable-next-line
51 | var re = /^(([^<>()\[\]\.,;:\s@\"]+(\.[^<>()\[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i;
52 | return re.test(String(this.state.email).toLowerCase())?'success':'error';
53 | }
54 |
55 | getValidationState() {
56 | const length = this.state.password.length;
57 | if (length > 8) return 'success';
58 | else if (length > 5) return 'warning';
59 | else if (length > 0) return 'error';
60 | return null;
61 | }
62 |
63 | getValidationStateConf() {
64 | const length = this.state.confPass.length;
65 | if(length === 0 ) return null;
66 |
67 | else if( this.state.password === this.state.confPass ) return 'success';
68 |
69 | else return 'error';
70 |
71 |
72 | }
73 |
74 | handleChange(e) {
75 | this.setState({ password: e.target.value });
76 | }
77 |
78 | handleChangeEmail(e) {
79 | this.setState({ email: e.target.value });
80 | }
81 |
82 | handleChangeConfirm(e) {
83 | this.setState({ confPass: e.target.value });
84 | }
85 |
86 | handleChangeName(e) {
87 | this.setState({ name: e.target.value });
88 | }
89 |
90 | handleDismiss() {
91 | this.setState({ show: false });
92 | }
93 |
94 | handleShow() {
95 | this.setState({ show: true });
96 | }
97 |
98 | displayAlert() {
99 | return (
100 |
101 |
102 |
103 | { this.state.signUpMessage }
104 |
105 |
106 |
107 |
108 | );
109 | }
110 |
111 |
112 | signUpClicked(e) {
113 | this.setState({ signInLoading : true });
114 | //console.log(e);
115 | const newUser = {
116 | password: this.state.password,
117 | name: this.state.name,
118 | email: this.state.email,
119 | };
120 |
121 | fetch( RESTAPIUrl+"/api/account/signup", {
122 | method: 'POST',
123 | headers: {
124 | 'Content-Type': 'application/json'
125 | },
126 | body: JSON.stringify(newUser),
127 | })
128 | .then(res => res.json())
129 | .then(json => {
130 | console.log('json', json);
131 |
132 | if(json.message === "Signed Up") {
133 | this.setState({
134 | signInLoading: false,
135 | show: true,
136 | signupStatus: 'success',
137 | signUpMessage: 'You have signed up successfully. Proceed to login.',
138 | name: '',
139 | password: '',
140 | confPass: '',
141 | email: '',
142 |
143 | });
144 | } else if ( json.message === 'Error: Account Already Exists') {
145 | this.setState({
146 | signInLoading: false,
147 | show: true,
148 | signupStatus: 'warning',
149 | signUpMessage: 'Account already Exists. Procced to login',
150 | name: '',
151 | password: '',
152 | confPass: '',
153 | email: '',
154 | });
155 | } else if( json.message === 'Error: Server Error') {
156 | this.setState({
157 | signInLoading: false,
158 | show: true,
159 | signupStatus: 'danger',
160 | signUpMessage: 'Unexpected error. Please try again later.',
161 | name: '',
162 | password: '',
163 | confPass: '',
164 | email: '',
165 |
166 | });
167 | }
168 |
169 |
170 | })
171 |
172 | }
173 |
174 |
175 |
176 |
177 | render() {
178 | return (
179 |
180 |
181 |
183 |
190 |
191 |
192 |
193 |
194 |
198 |
199 |
205 |
206 |
207 |
208 |
209 |
210 |
214 |
215 |
221 |
222 | Password must be minimum 8 characters long
223 |
224 |
225 |
229 |
230 |
236 |
237 |
238 |
239 |
240 |
246 | {this.state.signInLoading ? 'Processing...' : 'Sign Up'}
247 |
248 |
249 |
250 |
251 |
252 | { this.state.show ? this.displayAlert() : null}
253 |
254 |
255 |
256 |
257 |
258 | );
259 |
260 | }
261 | }
262 |
263 | export default SignUp;
264 |
265 |
--------------------------------------------------------------------------------
/server/socket/index.js:
--------------------------------------------------------------------------------
1 | const io = require('socket.io');
2 | const User = require('../models/User');
3 |
4 | /**
5 | * Initialize when a connection is made
6 | * @param {SocketIO.Socket} socket
7 | */
8 |
9 | function initSocket(client) {
10 | let id;
11 | let target_id;
12 | client.on("create_room", e => {
13 | id = e._id;
14 | client.join(e._id);
15 |
16 | User.findOneAndUpdate({
17 | ip_address: e.ip_address
18 | }, {
19 | $set: {
20 | online: true,
21 | connected_other: false,
22 | }
23 | }, {
24 | new: true
25 | }, function() {
26 | client.broadcast.to(e._id).emit('remove_old_session');
27 | });
28 | });
29 |
30 | // Find target user with blackUsers list and filter settings
31 | client.on("find_target", e => {
32 | var blackUsersList = e.blackUsersList;
33 | var signedInUser = e.signedInUser;
34 | var prevTargetUser = e.prevTargetUser;
35 |
36 | if (e.searchSetting.location == '') {
37 | var location = {};
38 | } else {
39 | var location = { location: e.searchSetting.location };
40 | }
41 |
42 | if (e.searchSetting.gender == '') {
43 | var gender = {};
44 | } else {
45 | var gender = { gender: e.searchSetting.gender };
46 | }
47 | ageMin = { age: { $gte: e.searchSetting.ageMin } };
48 | ageMax = { age: { $lte: e.searchSetting.ageMax } };
49 |
50 | let online = { online: true };
51 | let isDeleted = { isDeleted: false };
52 | let connected_other = { connected_other: false };
53 |
54 | User
55 | .find({
56 | $or: [
57 | { _id: signedInUser._id },
58 | { _id: prevTargetUser._id }
59 | ]
60 | }).updateMany({
61 | $set: {
62 | connected_other: false,
63 | }
64 | }, (err, user) => {
65 | client.to(prevTargetUser._id).emit('ignore', { status: 'ignore' });
66 | });
67 |
68 | User.find({
69 | $and: [
70 | location,
71 | gender,
72 | ageMin,
73 | ageMax,
74 | online,
75 | isDeleted,
76 | connected_other
77 | ]
78 | },
79 | function(err, docs) {
80 | if (!err) {
81 | console.log("Filtered user's number:", docs.length);
82 |
83 | if (docs.length == 0) {
84 | // Send message that there is none to find
85 | client.emit('search-none');
86 | } else {
87 | if (docs.length == 1 && docs[0]._id == signedInUser._id) {
88 | client.emit('search-none');
89 | } else {
90 | var available_user = [];
91 |
92 | console.log("Black user list length", blackUsersList.length, blackUsersList);
93 | for (let i = 0; i < docs.length; i++) {
94 | let blackNum = 0;
95 | for (let j = 0; j < blackUsersList.length; j++) {
96 | if (docs[i]._id == blackUsersList[j])
97 | blackNum = 1;
98 | }
99 | if (blackNum == 0) {
100 | available_user.push(docs[i]);
101 |
102 | }
103 | }
104 |
105 | console.log("available user Number", available_user.length);
106 | if (available_user.length != 0) {
107 | let targetUser = available_user[Math.floor(Math.random() * available_user.length)];
108 |
109 | User
110 | .find({
111 | $or: [
112 | { _id: signedInUser._id },
113 | { _id: targetUser._id }
114 | ]
115 | }).updateMany({
116 | $set: {
117 | connected_other: true,
118 | }
119 | }, (err, user) => {
120 | client.emit('find_target', targetUser);
121 | client.to(targetUser._id).emit('find_target', signedInUser);
122 | });
123 |
124 | } else {
125 | client.emit('available-none');
126 | }
127 | }
128 | // Remove users who contacted before
129 |
130 |
131 | }
132 | } else {
133 | throw err;
134 | }
135 | });
136 | });
137 |
138 | // Set target user for global variable in backend
139 | client.on("confirm-find_target", e => {
140 | target_id = e._id;
141 | });
142 |
143 | // Let target user know about typing now
144 | client.on("on-typing", e => {
145 | client.to(e._id).emit('on-typing');
146 | });
147 |
148 | client.on("sign-in", e => {
149 | let user_id = e.id;
150 | if (!user_id) return;
151 | client.user_id = user_id;
152 | if (clients[user_id]) {
153 | clients[user_id].push(client);
154 | } else {
155 | clients[user_id] = [client];
156 | }
157 | });
158 |
159 | client.on("message", e => {
160 | let targetId = e.to;
161 | // let sourceId = client.user_id;
162 | // client.to(sourceId).emit('message', e);
163 | client.emit('message', e);
164 | client.to(targetId).emit('message', e);
165 | // if (targetId && clients[targetId]) {
166 | // clients[targetId].forEach(cli => {
167 | // cli.emit("message", e);
168 | // });
169 | // }
170 |
171 | // if (sourceId && clients[sourceId]) {
172 | // clients[sourceId].forEach(cli => {
173 | // cli.emit("message", e);
174 | // });
175 | // }
176 | });
177 |
178 | client.on("confirm_remove_old_session", e => {
179 |
180 | client.leave(e._id);
181 |
182 | User.findOneAndUpdate({
183 | _id: target_id
184 | }, {
185 | $set: {
186 | connected_other: false,
187 | }
188 | }, {
189 | new: true
190 | }, function() {
191 | client.to(target_id).emit('target-logout');
192 | });
193 | });
194 |
195 | client.on("log-out", e => {
196 | User.findOneAndUpdate({
197 | _id: id
198 | }, {
199 | $set: {
200 | online: false,
201 | connected_other: false,
202 | }
203 | }, {
204 | new: true
205 | }, function() {
206 | User.findOneAndUpdate({
207 | _id: target_id
208 | }, {
209 | $set: {
210 | connected_other: false,
211 | }
212 | }, {
213 | new: true
214 | }, function() {
215 | client.emit('log-out', e);
216 | client.to(target_id).emit('target-logout');
217 | target_id = '';
218 | });
219 | // BroadCast socket for logout and disconnect
220 | });
221 | });
222 |
223 | client.on("confirm-target-logout", () => {
224 | target_id = '';
225 | });
226 |
227 | client.on('disconnect', e => {
228 | User.findOneAndUpdate({
229 | _id: id
230 | }, {
231 | $set: {
232 | online: false,
233 | connected_other: false,
234 | }
235 | }, {
236 | new: true
237 | }, function() {
238 | User.findOneAndUpdate({
239 | _id: target_id
240 | }, {
241 | $set: {
242 | connected_other: false,
243 | }
244 | }, {
245 | new: true
246 | }, function() {
247 | client.to(target_id).emit('target-disconnect');
248 | target_id = '';
249 | });
250 | });
251 | });
252 |
253 | client.on("confirm-target-disconnect", () => {
254 | target_id = '';
255 | })
256 |
257 | // client.on("disconnect", function() {
258 | // if (!client.user_id || !clients[client.user_id]) {
259 | // return;
260 | // }
261 | // let targetClients = clients[client.user_id];
262 | // for (let i = 0; i < targetClients.length; ++i) {
263 | // if (targetClients[i] == client) {
264 | // targetClients.splice(i, 1);
265 | // }
266 | // }
267 | // });
268 |
269 | // Socket for video chat
270 |
271 | // client.on('init', async() => {
272 | // id = await users.create(socket);
273 | // client.emit('init', { id });
274 | // });
275 | client.on('request', (data) => {
276 | // const receiver = users.get(data.to);
277 | // if (receiver) {
278 | // receiver.emit('request', { from: id });
279 | // }
280 | client.to(data.to).emit('request', { from: id });
281 | });
282 | client.on('call', (data) => {
283 | // const receiver = users.get(data.to);
284 | // if (receiver) {
285 | // receiver.emit('call', {...data, from: id });
286 | // } else {
287 | // socket.emit('failed');
288 | // }
289 | client.to(data.to).emit('call', {...data, from: id });
290 | })
291 | client.on('end', (data) => {
292 | // const receiver = users.get(data.to);
293 | // if (receiver) {
294 | // receiver.emit('end');
295 | // }
296 | client.to(data.to).emit('end');
297 | })
298 | // client.on('disconnect', () => {
299 | // // users.remove(id);
300 | // console.log(id, 'disconnected');
301 | // });
302 | }
303 |
304 | module.exports = (client) => {
305 | io({ serveClient: false })
306 | .listen(client, { log: true })
307 | .on('connection', initSocket);
308 | };
--------------------------------------------------------------------------------
/src/components/ProfileBox/ProfileBox.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import {
3 | MDBJumbotron,
4 | MDBBtn,
5 | MDBRow,
6 | MDBCol,
7 | MDBCardBody,
8 | MDBInput,
9 | MDBInputSelect,
10 | MDBCloseIcon
11 | } from 'mdbreact';
12 |
13 | import {
14 | NotificationContainer,
15 | NotificationManager
16 | } from "react-notifications";
17 | import jwt_decode from "jwt-decode";
18 | import { RESTAPIUrl } from '../../config/config';
19 | import { setInStorage } from "../../utils/storage";
20 | import { countries } from "../../config/country";
21 |
22 | import "./ProfileBox.css";
23 |
24 |
25 |
26 |
27 | export default class ChatBox extends Component {
28 | constructor(props) {
29 | super(props);
30 |
31 | this.state = {
32 | imageURL: `${RESTAPIUrl}/public/profile/${this.props.profileInfo.profile_image}`,
33 | userName: this.props.profileInfo.userName,
34 | age: this.props.profileInfo.age,
35 | location: this.props.profileInfo.location,
36 | gender: this.props.profileInfo.gender,
37 | imageHash: this.props.imageHash
38 | }
39 |
40 | this.handleUploadImage = this.handleUploadImage.bind(this);
41 | this.handleChangeProfile = this.handleChangeProfile.bind(this);
42 | }
43 | componentWillReceiveProps() {
44 | this.setState({
45 | imageURL: `${RESTAPIUrl}/public/profile/${this.props.profileInfo.profile_image}`
46 | });
47 | }
48 | handleUploadImage(ev) {
49 | ev.preventDefault();
50 |
51 | const data = new FormData();
52 | data.append('file', this.uploadInput.files[0]);
53 | data.append('fileName', this.fileName.value);
54 |
55 | fetch(`${RESTAPIUrl}/api/profile/image`, {
56 | method: 'POST',
57 | body: data,
58 | })
59 | .then(res =>res.json())
60 | .then(json => {
61 | if(json.status) {
62 | setInStorage('guest_signin', {token:json.token});
63 | this.props.updateProfile();
64 |
65 | // this.setState({imageHash: Date.now()});
66 |
67 | let decoded_token = jwt_decode(json.token);
68 | let signedInUser = decoded_token.user;
69 | // this.props.onChangeProfile(signedInUser);
70 | this.setState({
71 | imageURL: `${RESTAPIUrl}/public/profile/${signedInUser.profile_image}`,
72 | imageHash: Date.now()
73 | });
74 | } else {
75 | alert("Server Error");
76 | }
77 | })
78 |
79 |
80 | // .then((response) => {
81 | // response.json().then((body) => {
82 | // this.setState({ imageURL: `http://localhost:5000/${body.file}` });
83 | // });
84 | // });
85 | }
86 |
87 | handleChangeProfile(e) {
88 | e.preventDefault();
89 | const { userName, location, age, gender } = this.state;
90 | const { _id } = this.props.profileInfo;
91 | this.setState ({
92 | loginLoading: true,
93 | });
94 |
95 | fetch(RESTAPIUrl + '/api/profile/update', {
96 | method: 'POST',
97 | headers: {
98 | 'Content-Type': 'application/json'
99 | },
100 | body: JSON.stringify({
101 | _id,
102 | userName,
103 | location,
104 | age,
105 | gender
106 | }),
107 | }).then(res =>res.json())
108 | .then(json => {
109 | if(json.status) {
110 | setInStorage('guest_signin', {token:json.token});
111 | this.props.updateProfile();
112 | this.props.onProfileModalShow(false);
113 | NotificationManager.success(
114 | `${json.message}`
115 | );
116 | } else {
117 | NotificationManager.error(
118 | `${json.message}`
119 | );
120 | }
121 | });
122 | }
123 |
124 | onProfileModalShow() {
125 | this.props.onProfileModalShow(false);
126 | }
127 |
128 | handleChangeName(e) {
129 | this.setState({ userName: e.target.value });
130 | }
131 |
132 | handleChangeLocation(e) {
133 | this.setState({ location: e.target.value });
134 | }
135 |
136 | handleChangeAge(value) {
137 | this.setState({ age: value });
138 | }
139 |
140 | handleChangeGender(e) {
141 | this.setState({ gender: e.target.value });
142 | }
143 |
144 | render() {
145 | return (
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
162 |
163 |
164 | { this.uploadInput = ref; }}
166 | onChange={this.handleUploadImage.bind(this)}
167 | type="file"
168 | id="imageUpload"
169 | accept=".png, .jpg, .jpeg"
170 | />
171 | { this.fileName = ref; }}
172 | value={this.props.profileInfo.profile_image}
173 | type="hidden"
174 | />
175 |
176 |
{this.props.profileInfo.userName}
177 |
178 | {`${this.props.profileInfo.gender} ${this.props.profileInfo.age} ${this.props.profileInfo.location}`}
179 |
180 |
181 |
182 |
183 |
184 |
{ this.userName = ref; }}
187 | group type="text"
188 | onChange={this.handleChangeName.bind(this)}
189 | validate error="wrong"
190 | success="right"
191 | value={this.state.userName }
192 | required
193 | disabled
194 | />
195 |
196 | Your Location
197 |
204 | Choose your location
205 | {
206 | countries.map((object, i) => {
207 | return {object}
208 | })
209 | }
210 |
211 |
212 | Your Age
213 |
222 |
223 | Your Gender
224 |
235 |
236 |
237 |
242 | Apply
243 |
244 |
245 |
248 | Close
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 | );
261 | }
262 | }
--------------------------------------------------------------------------------
/server/routes/api/sign-in.js:
--------------------------------------------------------------------------------
1 | const jwt = require('jsonwebtoken');
2 | const randomip = require('random-ip');
3 | const jwt_decode = require("jwt-decode");
4 | const fs = require('fs');
5 | const User = require('../../models/User');
6 | const UserSession = require('../../models/UserSession');
7 |
8 | module.exports = (app) => {
9 | //SignUp
10 | app.post('/api/account/signup', (req, res, next) => {
11 | const body = req.body;
12 | console.log(body);
13 |
14 | const {
15 | password
16 | } = body;
17 |
18 | const {
19 | name
20 | } = body;
21 |
22 | let {
23 | email
24 | } = body;
25 |
26 | if (!email) {
27 | return res.send({
28 | success: false,
29 | message: 'Error: Email cannot be blank.'
30 | });
31 | }
32 | if (!password) {
33 | return res.send({
34 | success: false,
35 | message: 'Error: Password cannot be blank.'
36 | });
37 | }
38 |
39 | email = email.toLowerCase();
40 | email = email.trim();
41 |
42 | User.find({
43 | email: email,
44 | },
45 | (err, prevUsers) => {
46 | console.log(prevUsers);
47 | if (err) {
48 | return res.status(500).send({
49 | message: 'Error: Server Error',
50 | });
51 | } else if (prevUsers.length != 0) {
52 | return res.status(200).send({
53 | message: 'Error: Account Already Exists'
54 | })
55 | } else {
56 | const newUser = new User();
57 |
58 | newUser.email = email;
59 | newUser.password = newUser.generateHash(password);
60 | newUser.name = name;
61 | console.log(newUser.password);
62 | newUser.save((err, user) => {
63 | if (err) {
64 | return res.status(500).send({
65 | message: 'Error: Server Error',
66 | });
67 | } else {
68 | return res.status(200).send({
69 | message: "Signed Up",
70 | });
71 | }
72 |
73 | });
74 | }
75 | });
76 | });
77 |
78 | app.post('/api/account/signin', (req, res, next) => {
79 | const { body } = req;
80 |
81 | const {
82 | password
83 | } = body;
84 |
85 | let {
86 | email
87 | } = body;
88 |
89 | if (!email) {
90 | return res.status(300).send({
91 | message: 'Error: Email cannot be blank.',
92 | respId: 'LIE1'
93 | });
94 | }
95 |
96 | if (!password) {
97 | return res.send({
98 | success: false,
99 | message: 'Error Password cannot be blank.',
100 | respId: 'LIE2',
101 | });
102 | }
103 |
104 | email = email.toLowerCase();
105 | email = email.trim();
106 |
107 | User.find({
108 | email: email
109 | }, (err, users) => {
110 | if (err) {
111 | console.log('err2:', err);
112 | return res.send({
113 | success: false,
114 | message: 'Error: server error',
115 | respId: 'LIE3',
116 | });
117 | }
118 |
119 | if (users.length != 1) {
120 | return res.send({
121 | success: false,
122 | message: 'Error: Invalid',
123 | respId: 'LIE4',
124 | });
125 | } else {
126 | const user = users[0];
127 | console.log(password, user.password);
128 | if (!user.validPassword(password)) {
129 | return res.send({
130 | success: false,
131 | message: 'Error: Wrong Email or Password',
132 | respId: 'LIE5',
133 | });
134 | } else {
135 | const userSession = new UserSession();
136 | userSession.userId = user._id;
137 | userSession.save((err, doc) => {
138 | if (err) {
139 | console.log(err);
140 | return res.send({
141 | success: false,
142 | message: 'Error: Server Error',
143 | respId: 'LIE6',
144 | });
145 | } else {
146 | return res.send({
147 | success: true,
148 | message: 'Valid sign in',
149 | token: doc._id,
150 | name: user.name,
151 | respId: 'LIS',
152 | });
153 | }
154 |
155 | });
156 | }
157 | }
158 |
159 |
160 |
161 | });
162 |
163 | });
164 |
165 | app.get('/api/account/logout', (req, res, next) => {
166 | const { query } = req;
167 | const { token } = query;
168 |
169 | UserSession.findOneAndUpdate({
170 | _id: token,
171 | isDeleted: false,
172 | }, {
173 | $set: {
174 | isDeleted: true,
175 | }
176 | }, null, (err, sessions) => {
177 | if (err) {
178 | console.log(err);
179 | return res.send({
180 | success: false,
181 | message: 'Error: Server Error',
182 | });
183 | } else {
184 | return res.send({
185 | success: true,
186 | message: 'Logged Out',
187 | });
188 | }
189 | });
190 |
191 | });
192 |
193 | app.get('/api/account/verify', (req, res, next) => {
194 | // Get the token
195 | const { query } = req;
196 | const { token } = query;
197 | // ?token=test
198 | // Verify the token is one of a kind and it's not deleted.
199 | UserSession.find({
200 | _id: token,
201 | isDeleted: false
202 | }, (err, sessions) => {
203 | if (err) {
204 | console.log(err);
205 | return res.send({
206 | success: false,
207 | message: 'Error: Server error'
208 | });
209 | }
210 | if (sessions.length != 1) {
211 | return res.send({
212 | success: false,
213 | message: 'Error: Invalid'
214 | });
215 | } else {
216 | // DO ACTION
217 | return res.send({
218 | success: true,
219 | message: 'Good'
220 | });
221 | }
222 | });
223 | });
224 |
225 | app.post('/api/guest/signin', (req, res, next) => {
226 | // Get the client's IP Address
227 | var ip_address = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
228 | ip_address = ip_address.substr(ip_address.lastIndexOf(":") + 1);
229 |
230 | // var ip_address = randomip('192.168.2.0', 24);
231 | console.log("sign in ip address")
232 |
233 | const { userName, age, gender, location } = req.body;
234 |
235 | if (age > 99 || age < 13) {
236 | return res.status(500).send({
237 | status: false,
238 | message: 'Validation Error: Specified attribute is not between the expected ages of 13 and 99.',
239 | });
240 | }
241 |
242 | User.find({
243 | ip_address: ip_address,
244 | },
245 | (err, prevUsers) => {
246 | if (err) {
247 | return res.status(500).send({
248 | status: false,
249 | message: 'Error: Server Error',
250 | });
251 | }
252 | // When user already exist with same ip address
253 | else if (prevUsers.length != 0) {
254 | // Find user with specified ip address and update user's information
255 | User.findOneAndUpdate({
256 | ip_address: ip_address
257 | }, {
258 | $set: {
259 | userName: userName,
260 | age: age,
261 | gender: gender,
262 | location: location,
263 | ip_address: ip_address,
264 | }
265 | }, {
266 | new: true
267 | }, (err, user) => {
268 | if (err) {
269 | console.log(err);
270 | return res.status(500).send({
271 | status: false,
272 | message: 'Error: Server Error',
273 | })
274 | } else {
275 | console.log(user);
276 | let token = jwt.sign({ user: user },
277 | 'secret', {
278 | expiresIn: 31556926 // 1 year in second
279 | },
280 | (err, token) => {
281 | if (err) {
282 | console.log('Failed to create token', err);
283 | } else {
284 | return res.status(200).send({
285 | status: true,
286 | message: "SignIn success but user information changed a little",
287 | token: token
288 | });
289 | }
290 | }
291 | )
292 | }
293 | });
294 |
295 | }
296 | // Create new user with client's ip address
297 | else {
298 | const newUser = new User();
299 |
300 | newUser.userName = userName;
301 | newUser.age = age;
302 | newUser.gender = gender;
303 | newUser.location = location;
304 | newUser.ip_address = ip_address;
305 |
306 | newUser.save((err, user) => {
307 | if (err) {
308 | return res.status(500).send({
309 | message: 'Error: Server Error',
310 | });
311 | } else {
312 | // File copy as default png file according to gender(Male or Female)
313 | if (user.gender == "Male") {
314 | fs.copyFile('public/profile/male.png', `public/profile/${user.id}.png`, (err) => {
315 | if (err) throw err;
316 | });
317 | } else if (user.gender == "Female") {
318 | fs.copyFile('public/profile/female.png', `public/profile/${user.id}.png`, (err) => {
319 | if (err) throw err;
320 | });
321 | }
322 |
323 |
324 | User.findOneAndUpdate({
325 | _id: user.id
326 | }, {
327 | $set: {
328 | profile_image: `${user.id}.png`
329 | }
330 | }, {
331 | new: true
332 | }, (err, newUser) => {
333 | let token = jwt.sign({ user: newUser },
334 | 'secret', {
335 | expiresIn: 31556926 // 1 year in second
336 | },
337 | (err, token) => {
338 | if (err) {
339 | console.log('Failed to create token', err);
340 | } else {
341 | console.log(token);
342 | return res.status(200).send({
343 | status: true,
344 | message: "SignIn success",
345 | token: token
346 | });
347 | }
348 | }
349 | )
350 | });
351 | }
352 | });
353 | }
354 | }
355 | )
356 |
357 | });
358 |
359 | app.post('/api/guest/verify', (req, res, next) => {
360 | var decoded_token = jwt_decode(req.body.token);
361 | User.find({
362 | _id: decoded_token.user._id,
363 | },
364 | (err, user) => {
365 | if (user.length > 0) {
366 | if (user[0].isDeleted == false) {
367 | return res.send({
368 | status: true
369 | });
370 | }
371 | return res.send({
372 | status: false
373 | });
374 | }
375 | return res.send({
376 | status: false
377 | })
378 |
379 | });
380 |
381 | });
382 |
383 | app.post('/api/guest/ipverify', (req, res, next) => {
384 | var ip_address = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
385 | ip_address = ip_address.substr(ip_address.lastIndexOf(":") + 1);
386 | User.find({
387 | ip_address: ip_address
388 | },
389 | (err, user) => {
390 | if (user.length != 0) {
391 | if (user[0].isDeleted == false) {
392 | console.log("user's information", user[0]);
393 | return res.send({
394 | status: true
395 | });
396 | }
397 | return res.send({
398 | status: false
399 | });
400 | } else {
401 | return res.send({
402 | status: true,
403 | message: 'Not match'
404 | });
405 | }
406 |
407 | });
408 |
409 | });
410 |
411 |
412 | };
--------------------------------------------------------------------------------
/src/pages/AdminManage/AdminManage.css:
--------------------------------------------------------------------------------
1 | a {
2 | transition: background 0.2s, color 0.2s;
3 | }
4 |
5 | .container {
6 | display: flex;
7 | justify-content: center;
8 | }
9 |
10 | table tr {
11 | height: 40px;
12 | }
13 |
14 | .table-responsive>table>tbody>tr>td {
15 | padding-top: 7px;
16 | padding-bottom: 8px;
17 | }
18 |
19 | table tr img {
20 | width: 35px;
21 | height: 35px;
22 | border-radius: 50%;
23 | }
24 |
25 | .admin-profile {
26 | position: absolute;
27 | top: 35%;
28 | left: 50%;
29 | transform: translate(-50%, -50%);
30 | }
31 |
32 | .user-profile {
33 | position: absolute;
34 | top: 30px;
35 | left: 50%;
36 | transform: translate(-50%, 0%);
37 | }
38 |
39 | .avatar-container {
40 | position: relative;
41 | background-image: url('/07.jpg');
42 | height: 11rem;
43 | background-position: 50%;
44 | background-size: cover;
45 | background-repeat: no-repeat;
46 | padding: 0 1.875rem;
47 | }
48 |
49 | .avatar-container img {
50 | position: absolute;
51 | left: 50%;
52 | top: 45%;
53 | transform: translate(-50%, -50%);
54 | width: 120px;
55 | height: 120px;
56 | border-radius: 50%;
57 | border: 3px solid white;
58 | }
59 |
60 | .avatar-container h2 {
61 | position: absolute;
62 | bottom: 0px;
63 | left: 50%;
64 | transform: translate(-50%, 0);
65 | color: white;
66 | font-size: 1.375rem;
67 | font-weight: 300;
68 | }
69 |
70 | .contact-title,
71 | .report-title {
72 | margin-top: 2rem;
73 | padding: .625rem 1.25rem;
74 | color: #a2a2a2;
75 | }
76 |
77 | .contact-body {}
78 |
79 | .contact-sub-body,
80 | .report-sub-body {
81 | display: flex;
82 | justify-content: space-between;
83 | border-bottom: 1px solid #dadce0;
84 | padding: 1rem 1.25rem;
85 | align-items: center;
86 | }
87 |
88 | .contact-sub-left {
89 | font-weight: 600;
90 | font-size: 15px;
91 | }
92 |
93 | .contact-sub-left i {
94 | width: 20px;
95 | margin-right: 10px;
96 | color: #949aa2;
97 | }
98 |
99 | .contact-sub-right {}
100 |
101 | .report-sub-left img {
102 | display: inline;
103 | width: 30px;
104 | height: 30px;
105 | border-radius: 50%;
106 | margin-right: 10px;
107 | }
108 |
109 |
110 | /* Switch button style */
111 |
112 | .button-switch {
113 | font-size: 1em;
114 | height: 1.875em;
115 | position: relative;
116 | width: 4.5em;
117 | }
118 |
119 | .button-switch .lbl-off,
120 | .button-switch .lbl-on {
121 | cursor: pointer;
122 | display: block;
123 | font-size: 0.9em;
124 | font-weight: bold;
125 | line-height: 1em;
126 | position: absolute;
127 | top: 0.5em;
128 | -webkit-transition: opacity 0.25s ease-out 0.1s;
129 | transition: opacity 0.25s ease-out 0.1s;
130 | text-transform: uppercase;
131 | }
132 |
133 | .button-switch .lbl-off {
134 | right: 0.4375em;
135 | }
136 |
137 | .button-switch .lbl-on {
138 | color: #fefefe;
139 | opacity: 0;
140 | left: 0.4375em;
141 | }
142 |
143 | .button-switch .switch {
144 | -webkit-appearance: none;
145 | -moz-appearance: none;
146 | appearance: none;
147 | height: 0;
148 | font-size: 1em;
149 | left: 0;
150 | line-height: 0;
151 | outline: none;
152 | position: absolute;
153 | top: 0;
154 | width: 0;
155 | }
156 |
157 | .button-switch .switch:before,
158 | .button-switch .switch:after {
159 | content: "";
160 | font-size: 1em;
161 | position: absolute;
162 | }
163 |
164 | .button-switch .switch:before {
165 | border-radius: 1.25em;
166 | background: #bdc3c7;
167 | height: 1.875em;
168 | left: -0.25em;
169 | top: -0.1875em;
170 | -webkit-transition: background-color 0.25s ease-out 0.1s;
171 | transition: background-color 0.25s ease-out 0.1s;
172 | width: 4.5em;
173 | }
174 |
175 | .button-switch .switch:after {
176 | box-shadow: 0 0.0625em 0.375em 0 #666;
177 | border-radius: 50%;
178 | background: #fefefe;
179 | height: 1.5em;
180 | -webkit-transform: translate(0, 0);
181 | transform: translate(0, 0);
182 | -webkit-transition: -webkit-transform 0.25s ease-out 0.1s;
183 | transition: -webkit-transform 0.25s ease-out 0.1s;
184 | transition: transform 0.25s ease-out 0.1s;
185 | transition: transform 0.25s ease-out 0.1s, -webkit-transform 0.25s ease-out 0.1s;
186 | width: 1.5em;
187 | }
188 |
189 | .button-switch .switch:checked:after {
190 | -webkit-transform: translate(2.5em, 0);
191 | transform: translate(2.5em, 0);
192 | }
193 |
194 | .button-switch .switch:checked~.lbl-off {
195 | opacity: 0;
196 | }
197 |
198 | .button-switch .switch:checked~.lbl-on {
199 | opacity: 1;
200 | }
201 |
202 | .button-switch .switch#switch-orange:checked:before {
203 | background: #e67e22;
204 | }
205 |
206 | .button-switch .switch#switch-blue:checked:before {
207 | background: #3498db;
208 | }
209 |
210 |
211 | /* Loading Icon style */
212 |
213 | @keyframes rotate-loading {
214 | 0% {
215 | transform: rotate(0deg);
216 | -ms-transform: rotate(0deg);
217 | -webkit-transform: rotate(0deg);
218 | -o-transform: rotate(0deg);
219 | -moz-transform: rotate(0deg);
220 | }
221 | 100% {
222 | transform: rotate(360deg);
223 | -ms-transform: rotate(360deg);
224 | -webkit-transform: rotate(360deg);
225 | -o-transform: rotate(360deg);
226 | -moz-transform: rotate(360deg);
227 | }
228 | }
229 |
230 | @-moz-keyframes rotate-loading {
231 | 0% {
232 | transform: rotate(0deg);
233 | -ms-transform: rotate(0deg);
234 | -webkit-transform: rotate(0deg);
235 | -o-transform: rotate(0deg);
236 | -moz-transform: rotate(0deg);
237 | }
238 | 100% {
239 | transform: rotate(360deg);
240 | -ms-transform: rotate(360deg);
241 | -webkit-transform: rotate(360deg);
242 | -o-transform: rotate(360deg);
243 | -moz-transform: rotate(360deg);
244 | }
245 | }
246 |
247 | @-webkit-keyframes rotate-loading {
248 | 0% {
249 | transform: rotate(0deg);
250 | -ms-transform: rotate(0deg);
251 | -webkit-transform: rotate(0deg);
252 | -o-transform: rotate(0deg);
253 | -moz-transform: rotate(0deg);
254 | }
255 | 100% {
256 | transform: rotate(360deg);
257 | -ms-transform: rotate(360deg);
258 | -webkit-transform: rotate(360deg);
259 | -o-transform: rotate(360deg);
260 | -moz-transform: rotate(360deg);
261 | }
262 | }
263 |
264 | @-o-keyframes rotate-loading {
265 | 0% {
266 | transform: rotate(0deg);
267 | -ms-transform: rotate(0deg);
268 | -webkit-transform: rotate(0deg);
269 | -o-transform: rotate(0deg);
270 | -moz-transform: rotate(0deg);
271 | }
272 | 100% {
273 | transform: rotate(360deg);
274 | -ms-transform: rotate(360deg);
275 | -webkit-transform: rotate(360deg);
276 | -o-transform: rotate(360deg);
277 | -moz-transform: rotate(360deg);
278 | }
279 | }
280 |
281 | @keyframes rotate-loading {
282 | 0% {
283 | transform: rotate(0deg);
284 | -ms-transform: rotate(0deg);
285 | -webkit-transform: rotate(0deg);
286 | -o-transform: rotate(0deg);
287 | -moz-transform: rotate(0deg);
288 | }
289 | 100% {
290 | transform: rotate(360deg);
291 | -ms-transform: rotate(360deg);
292 | -webkit-transform: rotate(360deg);
293 | -o-transform: rotate(360deg);
294 | -moz-transform: rotate(360deg);
295 | }
296 | }
297 |
298 | @-moz-keyframes rotate-loading {
299 | 0% {
300 | transform: rotate(0deg);
301 | -ms-transform: rotate(0deg);
302 | -webkit-transform: rotate(0deg);
303 | -o-transform: rotate(0deg);
304 | -moz-transform: rotate(0deg);
305 | }
306 | 100% {
307 | transform: rotate(360deg);
308 | -ms-transform: rotate(360deg);
309 | -webkit-transform: rotate(360deg);
310 | -o-transform: rotate(360deg);
311 | -moz-transform: rotate(360deg);
312 | }
313 | }
314 |
315 | @-webkit-keyframes rotate-loading {
316 | 0% {
317 | transform: rotate(0deg);
318 | -ms-transform: rotate(0deg);
319 | -webkit-transform: rotate(0deg);
320 | -o-transform: rotate(0deg);
321 | -moz-transform: rotate(0deg);
322 | }
323 | 100% {
324 | transform: rotate(360deg);
325 | -ms-transform: rotate(360deg);
326 | -webkit-transform: rotate(360deg);
327 | -o-transform: rotate(360deg);
328 | -moz-transform: rotate(360deg);
329 | }
330 | }
331 |
332 | @-o-keyframes rotate-loading {
333 | 0% {
334 | transform: rotate(0deg);
335 | -ms-transform: rotate(0deg);
336 | -webkit-transform: rotate(0deg);
337 | -o-transform: rotate(0deg);
338 | -moz-transform: rotate(0deg);
339 | }
340 | 100% {
341 | transform: rotate(360deg);
342 | -ms-transform: rotate(360deg);
343 | -webkit-transform: rotate(360deg);
344 | -o-transform: rotate(360deg);
345 | -moz-transform: rotate(360deg);
346 | }
347 | }
348 |
349 | @keyframes loading-text-opacity {
350 | 0% {
351 | opacity: 0
352 | }
353 | 20% {
354 | opacity: 0
355 | }
356 | 50% {
357 | opacity: 1
358 | }
359 | 100% {
360 | opacity: 0
361 | }
362 | }
363 |
364 | @-moz-keyframes loading-text-opacity {
365 | 0% {
366 | opacity: 0
367 | }
368 | 20% {
369 | opacity: 0
370 | }
371 | 50% {
372 | opacity: 1
373 | }
374 | 100% {
375 | opacity: 0
376 | }
377 | }
378 |
379 | @-webkit-keyframes loading-text-opacity {
380 | 0% {
381 | opacity: 0
382 | }
383 | 20% {
384 | opacity: 0
385 | }
386 | 50% {
387 | opacity: 1
388 | }
389 | 100% {
390 | opacity: 0
391 | }
392 | }
393 |
394 | @-o-keyframes loading-text-opacity {
395 | 0% {
396 | opacity: 0
397 | }
398 | 20% {
399 | opacity: 0
400 | }
401 | 50% {
402 | opacity: 1
403 | }
404 | 100% {
405 | opacity: 0
406 | }
407 | }
408 |
409 | .loading {
410 | position: absolute;
411 | height: 70px;
412 | width: 70px;
413 | left: calc( 50% - 35px);
414 | top: calc( 50% - 35px);
415 | transform: translate(-50%, -50%);
416 | border-radius: 100%;
417 | }
418 |
419 | .loading-container {
420 | position: absolute;
421 | width: 100%;
422 | height: calc(100vh - 300px);
423 | left: 0;
424 | top: 0px;
425 | /* top: calc( 40% - 50px); */
426 | border-radius: 100%;
427 | margin: 40px auto
428 | }
429 |
430 | .loading {
431 | border: 2px solid transparent;
432 | border-color: transparent grey transparent grey;
433 | -moz-animation: rotate-loading 1.5s linear 0s infinite normal;
434 | -moz-transform-origin: 50% 50%;
435 | -o-animation: rotate-loading 1.5s linear 0s infinite normal;
436 | -o-transform-origin: 50% 50%;
437 | -webkit-animation: rotate-loading 1.5s linear 0s infinite normal;
438 | -webkit-transform-origin: 50% 50%;
439 | animation: rotate-loading 1.5s linear 0s infinite normal;
440 | transform-origin: 50% 50%;
441 | }
442 |
443 | .loading-container:hover .loading {
444 | border-color: transparent grey transparent grey;
445 | }
446 |
447 | .loading-container:hover .loading,
448 | .loading-container .loading {
449 | -webkit-transition: all 0.5s ease-in-out;
450 | -moz-transition: all 0.5s ease-in-out;
451 | -ms-transition: all 0.5s ease-in-out;
452 | -o-transition: all 0.5s ease-in-out;
453 | transition: all 0.5s ease-in-out;
454 | }
455 |
456 | #loading-text {
457 | -moz-animation: loading-text-opacity 2s linear 0s infinite normal;
458 | -o-animation: loading-text-opacity 2s linear 0s infinite normal;
459 | -webkit-animation: loading-text-opacity 2s linear 0s infinite normal;
460 | animation: loading-text-opacity 2s linear 0s infinite normal;
461 | color: grey;
462 | font-family: "Helvetica Neue, "Helvetica", ""arial";
463 | font-size: 10px;
464 | font-weight: bold;
465 | opacity: 0;
466 | position: absolute;
467 | text-align: center;
468 | text-transform: uppercase;
469 | top: calc( 50% - 10px);
470 | left: calc( 50% - 50px);
471 | width: 100px;
472 | height: 20px;
473 | }
474 |
475 |
476 | /* Origin style */
477 |
478 | a:hover,
479 | a:focus {
480 | text-decoration: none;
481 | }
482 |
483 | #wrapper {
484 | width: 100vw;
485 | height: 100vh;
486 | padding-left: 0;
487 | transition: all 0.5s ease;
488 | position: relative;
489 | }
490 |
491 | #sidebar-wrapper {
492 | z-index: 2;
493 | position: fixed;
494 | left: 0px;
495 | width: 0;
496 | height: 100%;
497 | overflow-y: auto;
498 | overflow-x: hidden;
499 | background: #31353D;
500 | transition: all 0.5s ease;
501 | }
502 |
503 | #wrapper.toggled #sidebar-wrapper {
504 | width: 250px;
505 | }
506 |
507 | .sidebar-brand {
508 | position: absolute;
509 | top: 0;
510 | width: 250px;
511 | padding: 20px 70px;
512 | }
513 |
514 | .sidebar-nav {
515 | position: absolute;
516 | top: 75px;
517 | width: 250px;
518 | margin: 0;
519 | padding: 0;
520 | list-style: none;
521 | }
522 |
523 | .sidebar-nav>li {
524 | text-indent: 10px;
525 | line-height: 42px;
526 | }
527 |
528 | .sidebar-nav>li a {
529 | display: block;
530 | text-decoration: none;
531 | color: #757575;
532 | font-weight: 600;
533 | font-size: 18px;
534 | }
535 |
536 | .sidebar-nav>li>a:hover,
537 | .sidebar-nav>li.active>a {
538 | text-decoration: none;
539 | color: #fff;
540 | }
541 |
542 | .sidebar-nav>li>a i.fa {
543 | font-size: 24px;
544 | width: 60px;
545 | }
546 |
547 |
548 | /* Wrapper Style */
549 |
550 | #navbar-wrapper {
551 | width: 100%;
552 | position: absolute;
553 | z-index: 2;
554 | }
555 |
556 | #wrapper.toggled #navbar-wrapper {
557 | position: absolute;
558 | margin-right: -250px;
559 | }
560 |
561 | #navbar-wrapper .navbar {
562 | border-width: 0 0 0 0;
563 | background-color: #eee;
564 | font-size: 24px;
565 | margin-bottom: 0;
566 | border-radius: 0;
567 | }
568 |
569 | #navbar-wrapper .navbar a {
570 | color: #757575;
571 | }
572 |
573 | #navbar-wrapper .navbar a:hover {
574 | color: #F8BE12;
575 | }
576 |
577 | .content-wrapper {
578 | width: 100%;
579 | position: absolute;
580 | padding: 15px;
581 | top: 100px;
582 | height: calc( 100% - 56px);
583 | }
584 |
585 | #wrapper.toggled .content-wrapper {
586 | position: absolute;
587 | margin-right: -250px;
588 | }
589 |
590 |
591 | /* Toggle Wrapper */
592 |
593 | #wrapper.expand-wrapper {
594 | padding-left: 60px;
595 | }
596 |
597 | #wrapper.shrink-wrapper {
598 | padding-left: 250px;
599 | }
600 |
601 | #sidebar-wrapper.expand-sidebar {
602 | width: 250px;
603 | }
604 |
605 | #sidebar-wrapper.shrink-sidebar {
606 | width: 60px;
607 | }
608 |
609 | @media (min-width: 992px) {
610 | #wrapper {
611 | padding-left: 250px;
612 | }
613 | #wrapper.toggled {
614 | padding-left: 60px;
615 | }
616 | #sidebar-wrapper {
617 | width: 250px;
618 | }
619 | #wrapper.toggled #sidebar-wrapper {
620 | width: 60px;
621 | }
622 | #wrapper.toggled #navbar-wrapper {
623 | position: absolute;
624 | margin-right: -190px;
625 | }
626 | #wrapper.toggled .content-wrapper {
627 | position: absolute;
628 | margin-right: -190px;
629 | }
630 | #navbar-wrapper {
631 | position: relative;
632 | }
633 | #wrapper.toggled {
634 | padding-left: 60px;
635 | }
636 | .content-wrapper {
637 | position: relative;
638 | top: 0;
639 | }
640 | #wrapper.toggled #navbar-wrapper,
641 | #wrapper.toggled .content-wrapper {
642 | position: relative;
643 | margin-right: 60px;
644 | }
645 | }
646 |
647 | @media (min-width: 768px) and (max-width: 991px) {
648 | #wrapper {
649 | padding-left: 60px;
650 | }
651 | #sidebar-wrapper {
652 | width: 60px;
653 | }
654 | #wrapper.toggled #navbar-wrapper {
655 | position: absolute;
656 | margin-right: -250px;
657 | }
658 | #wrapper.toggled .content-wrapper {
659 | position: absolute;
660 | margin-right: -250px;
661 | }
662 | #navbar-wrapper {
663 | position: relative;
664 | }
665 | #wrapper.toggled {
666 | padding-left: 250px;
667 | }
668 | .content-wrapper {
669 | position: relative;
670 | top: 0;
671 | }
672 | #wrapper.toggled #navbar-wrapper,
673 | #wrapper.toggled .content-wrapper {
674 | position: relative;
675 | margin-right: 250px;
676 | }
677 | }
678 |
679 | @media (max-width: 767px) {
680 | #wrapper {
681 | padding-left: 0;
682 | }
683 | #sidebar-wrapper {
684 | width: 0;
685 | }
686 | #wrapper.toggled #sidebar-wrapper {
687 | width: 250px;
688 | }
689 | #wrapper.toggled #navbar-wrapper {
690 | position: absolute;
691 | margin-right: -250px;
692 | }
693 | #wrapper.toggled .content-wrapper {
694 | position: absolute;
695 | margin-right: -250px;
696 | }
697 | #navbar-wrapper {
698 | position: relative;
699 | }
700 | #wrapper.toggled {
701 | padding-left: 250px;
702 | }
703 | .content-wrapper {
704 | position: relative;
705 | top: 0;
706 | }
707 | #wrapper.toggled #navbar-wrapper,
708 | #wrapper.toggled .content-wrapper {
709 | position: relative;
710 | margin-right: 250px;
711 | }
712 | #wrapper.expand-wrapper {
713 | padding-left: 0px;
714 | }
715 | #wrapper.shrink-wrapper {
716 | padding-left: 0px;
717 | }
718 | #sidebar-wrapper.expand-sidebar {
719 | width: 60px;
720 | }
721 | #sidebar-wrapper.shrink-sidebar {
722 | width: 0px;
723 | }
724 | }
--------------------------------------------------------------------------------